summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@redhat.com>2008-05-28 11:07:39 -0400
committerMichael DeHaan <mdehaan@redhat.com>2008-05-28 11:07:39 -0400
commit36fd29894dc5859fa295aac08d2726cf77667088 (patch)
tree75ec187193a029af50a898878c9fe9f56735075b
parent60e7b2048cb810082d9f5da189d827d0dec3747c (diff)
parent2963016f5c143a6f3c3bc711a2b6d1ed07d65abe (diff)
downloadthird_party-cobbler-36fd29894dc5859fa295aac08d2726cf77667088.tar.gz
third_party-cobbler-36fd29894dc5859fa295aac08d2726cf77667088.tar.xz
third_party-cobbler-36fd29894dc5859fa295aac08d2726cf77667088.zip
Merge branch 'devel'
Conflicts: cobbler.spec cobbler/item_system.py cobbler/webui/master.py
-rw-r--r--AUTHORS6
-rw-r--r--CHANGELOG63
-rw-r--r--MANIFEST.in12
-rw-r--r--Makefile20
-rw-r--r--cobbler.spec38
-rw-r--r--cobbler/action_buildiso.py181
-rw-r--r--cobbler/action_check.py126
-rw-r--r--cobbler/action_import.py37
-rw-r--r--cobbler/action_litesync.py77
-rw-r--r--cobbler/action_replicate.py160
-rw-r--r--cobbler/action_reposync.py55
-rw-r--r--cobbler/action_status.py291
-rw-r--r--cobbler/action_sync.py977
-rw-r--r--cobbler/action_validate.py48
-rw-r--r--cobbler/api.py84
-rw-r--r--cobbler/cexceptions.py3
-rwxr-xr-xcobbler/cobbler.py39
-rw-r--r--cobbler/cobblerd.py41
-rw-r--r--cobbler/collection.py61
-rw-r--r--cobbler/collection_distros.py2
-rw-r--r--cobbler/collection_profiles.py2
-rw-r--r--cobbler/collection_repos.py2
-rw-r--r--cobbler/collection_systems.py2
-rw-r--r--cobbler/commands.py60
-rw-r--r--cobbler/config.py2
-rw-r--r--cobbler/demo_connect.py21
-rw-r--r--cobbler/item.py26
-rw-r--r--cobbler/item_distro.py19
-rw-r--r--cobbler/item_profile.py178
-rw-r--r--cobbler/item_repo.py39
-rw-r--r--cobbler/item_system.py120
-rw-r--r--cobbler/kickgen.py279
-rw-r--r--cobbler/manage_ctrl.py76
-rw-r--r--cobbler/module_loader.py2
-rw-r--r--cobbler/modules/authn_configfile.py2
-rw-r--r--cobbler/modules/authn_denyall.py43
-rw-r--r--cobbler/modules/authn_kerberos.py81
-rw-r--r--cobbler/modules/authn_ldap.py118
-rw-r--r--cobbler/modules/authn_passthru.py49
-rw-r--r--cobbler/modules/authn_testing.py42
-rw-r--r--cobbler/modules/authz_allowall.py2
-rw-r--r--cobbler/modules/authz_configfile.py64
-rw-r--r--cobbler/modules/authz_ownership.py178
-rw-r--r--cobbler/modules/cli_distro.py40
-rw-r--r--cobbler/modules/cli_misc.py61
-rw-r--r--cobbler/modules/cli_profile.py53
-rw-r--r--cobbler/modules/cli_repo.py25
-rw-r--r--cobbler/modules/cli_system.py49
-rw-r--r--cobbler/modules/manage_bind.py283
-rw-r--r--cobbler/modules/manage_dnsmasq.py194
-rw-r--r--cobbler/modules/manage_isc.py252
-rw-r--r--cobbler/modules/serializer_shelve.py2
-rw-r--r--cobbler/modules/serializer_yaml.py7
-rw-r--r--cobbler/pxegen.py324
-rw-r--r--cobbler/remote.py326
-rw-r--r--cobbler/serializable.py2
-rw-r--r--cobbler/serializer.py6
-rw-r--r--cobbler/services.py96
-rw-r--r--cobbler/settings.py43
-rw-r--r--cobbler/templar.py189
-rw-r--r--cobbler/utils.py416
-rw-r--r--cobbler/webui/CobblerWeb.py154
-rw-r--r--cobbler/webui/master.py262
-rw-r--r--cobbler/yaml/__init__.py6
-rw-r--r--cobbler/yaml/dump.py7
-rw-r--r--cobbler/yaml/implicit.py6
-rw-r--r--cobbler/yaml/inline.py6
-rw-r--r--cobbler/yaml/klass.py6
-rw-r--r--cobbler/yaml/load.py6
-rw-r--r--cobbler/yaml/ordered_dict.py7
-rw-r--r--cobbler/yaml/redump.py6
-rw-r--r--cobbler/yaml/stream.py6
-rw-r--r--cobbler/yaml/timestamp.py7
-rw-r--r--cobbler/yaml/ypath.py7
-rw-r--r--cobbler/yumgen.py110
-rw-r--r--config/cobbler_svc.conf12
-rwxr-xr-xconfig/cobblerd19
-rw-r--r--config/cobblerd_rotate11
-rw-r--r--config/modules.conf10
-rw-r--r--config/settings163
-rw-r--r--config/users.conf28
-rw-r--r--docs/cobbler.dot118
-rw-r--r--docs/cobbler.pod80
-rw-r--r--docs/pyyaml-license.htm78
-rw-r--r--docs/wui.html25
-rw-r--r--kickstarts/sample.ks1
-rw-r--r--kickstarts/sample_end.ks55
-rwxr-xr-xlegacy/change_profile.cgi (renamed from scripts/change_profile.cgi)0
-rwxr-xr-xlegacy/findks.cgi (renamed from scripts/findks.cgi)0
-rw-r--r--legacy/install_trigger.cgi (renamed from scripts/post_install_trigger.cgi)29
-rwxr-xr-xlegacy/nopxe.cgi (renamed from scripts/nopxe.cgi)0
-rwxr-xr-xlegacy/register_mac.cgi (renamed from scripts/register_mac.cgi)2
-rwxr-xr-xlegacy/watcher.py (renamed from scripts/watcher.py)0
-rw-r--r--loaders/menu.c32bin26496 -> 51788 bytes
-rw-r--r--scripts/cobbler_auth_help55
-rwxr-xr-xscripts/index.py29
-rwxr-xr-xscripts/services.py90
-rw-r--r--setup.py33
-rw-r--r--templates/dhcp.template3
-rw-r--r--templates/named.template18
-rw-r--r--templates/pxedefault.template6
-rw-r--r--templates/zone.template13
-rw-r--r--tests/tests.py308
-rw-r--r--triggers/restart-services.trigger12
-rw-r--r--triggers/status_post.trigger15
-rw-r--r--triggers/status_pre.trigger15
-rw-r--r--website/codepush.sh3
-rw-r--r--website/new/about.html11
-rwxr-xr-xwebsite/new/cobbler-dhcp.php44
-rwxr-xr-xwebsite/new/cobbler-import.php44
-rwxr-xr-xwebsite/new/cobbler-manpage.php44
-rwxr-xr-xwebsite/new/cobbler-repos.php44
-rw-r--r--website/new/communicate.html27
-rwxr-xr-xwebsite/new/communicate.php48
-rw-r--r--website/new/css/style.css143
-rw-r--r--website/new/docs/cobbler.html14
-rw-r--r--website/new/download.html27
-rw-r--r--website/new/faq.html66
-rwxr-xr-xwebsite/new/faq.php48
-rwxr-xr-xwebsite/new/feed.php31
-rw-r--r--website/new/footer.html35
-rw-r--r--website/new/img/et_logo.pngbin0 -> 5194 bytes
-rw-r--r--website/new/img/footer_corner.pngbin0 -> 2359 bytes
-rw-r--r--website/new/img/footer_pattern.pngbin0 -> 817 bytes
-rwxr-xr-xwebsite/new/koan-manpage.php44
-rwxr-xr-xwebsite/new/nav.php41
-rw-r--r--website/new/news-feed.php0
-rw-r--r--website/new/news.html1
-rwxr-xr-xwebsite/new/rss-aggregator.php73
-rwxr-xr-xwebsite/new/rss-parser.php221
-rw-r--r--website/push.sh3
-rw-r--r--webui_templates/distro_edit.tmpl30
-rw-r--r--webui_templates/enoaccess.tmpl12
-rw-r--r--webui_templates/ksfile_edit.tmpl56
-rw-r--r--webui_templates/ksfile_list.tmpl3
-rw-r--r--webui_templates/ksfile_new.tmpl48
-rw-r--r--webui_templates/ksfile_view.tmpl6
-rw-r--r--webui_templates/master.tmpl1
-rw-r--r--webui_templates/profile_edit.tmpl32
-rw-r--r--webui_templates/profile_list.tmpl19
-rw-r--r--webui_templates/repo_edit.tmpl58
-rw-r--r--webui_templates/system_edit.tmpl161
-rw-r--r--webui_templates/system_list.tmpl24
143 files changed, 6226 insertions, 3156 deletions
diff --git a/AUTHORS b/AUTHORS
index c6d954c..9230de5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -7,12 +7,17 @@ Cobbler is written & maintained by:
Patches and other contributions from:
+ David Brown <dmlb2000@gmail.com>
James Bowes <jbowes@redhat.com>
C. Daniel Chase <dan@cdchase.com>
Máirín Duffy <duffy@redhat.com>
+ John Eckersberg <jeckersb@redhat.com>
+ Scott Henson <shenson@redhat.com>
Tru Huynh <tru@pasteur.fr>
Matt Hyclak <hyclak@math.ohiou.edu>
+ Pablo Iranzo Gómez <pablo.iranzo@redhat.com>
Mihai Ibanescu <mihai.ibanescu@gmail.com>
+ Vito Laurenza <vitolaurenza@gmail.com>
Adrian Likins <alikins@redhat.com>
David Lutterkort <dlutter@redhat.com>
Lester M. <needwork@gmail.com>
@@ -25,6 +30,7 @@ Patches and other contributions from:
Christophe Sahut <csahut@nogoa.org>
Scott Seago <sseago@redhat.com>
Al Tobey <tobert@gmail.com>
+ Ronald van den Blink <ronald@a61.nl>
Tim Verhoeven <tim.verhoeven.be@gmail.com>
[...send patches to get your name here...]
diff --git a/CHANGELOG b/CHANGELOG
index c3ea738..d43d434 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,14 +1,75 @@
Cobbler CHANGELOG
(all entries mdehaan@redhat.com unless noted otherwise)
-* Tue Apr 08 2008 - 0.8.3
+- Mon May 12 2008 - 0.9.2
+- run createrepo with less preconditions during cobbler reposync
+- doc upgrades and error handling for "cobbler replicate"
+- improved error message that occurs when copying from nfs w/ rootsquash
+- mac duplication checking improvements for CLI
+- add warning to cobbler check if selinux is on and Apache boolean not set
+- added warning to cobbler check if templates use the default password
+- setting per-system kickstart template to "" or "delete" restores inheritance
+- if repos in profiles no longer exist, remove noisy warning, move to "check"
+- move warning about reposync to check also (check is more useful at runtime now)
+- build pxe trees for systems even if interface0 is undefined
+- add sync() back into XMLRPC API, missing in 0.9.1
+- added 'distro_name', 'profile_name', and 'system_name' to generated template vars
+- it's now possible to undefine a --ksmeta or kopts symbol defined in a parent with "!foo"
+- log errors while rendering kickstarts
+- comments added to the config file, neat!
+- settings file is now /etc/cobbler/settings
+
+- Fri May 09 2008 - 0.9.1
+- patch to allow yumopts to override gpgcheck
+- applied patch to send hostname from ISC
+- added patch to allow --kopts/--ksmeta items to be cleared with --kopts=delete
+- tftpboot location is now inferred from xinetd config (added for F9 compat)
+- added authn_ldap and stub for authz_configfile
+- authz_configfile allows filtering ldap/other users by config file
+- WebUI now has checkbox on distro/profile for deleting child objects
+- cli has different semantics between "add" and "edit" now for safety reasons
+- cobbler wants to keep IPs/MACs unique now in configuration (can be disabled)
+- added --clobber option to allow add to overwrite existing objects (for scripts)
+- updated/tested kerberos support for those needing to auth against it
+- update menu.c32 to 3.62 to allow for timeouts during menu (and future submenu)
+- update PXE defaults to invoke menu.c32 automatically w/ timeout
+- removed dependency on rhpl
+- import can now take an --arch (and is recommended usage)
+- now possible to override snippets on a profile/system specific basis
+- provide a different default sample kickstart for imports of F8 and later
+- support for kerberos authentication
+- revamped pre/post install triggers system (triggered via cgi from kickstart wget)
+- logrotate should not send emails to root when restarting services
+- default core (but not repo add) repos to priority 1 (lowest) if using priorities plugin
+- change default authentication to deny_all, xmlrpc_rw_enabled now on by default
+- additional fix for mod_python select box submissions
+- set repo arch if found in the URL and no --arch is specified
+- CGI scripts have been moved under mod_python for speed/consolidation
+- kickstart templates are now evaluated dynamically
+- optional MAC registration is now built-in to requesting kickstarts
+- legacy static file generation from /var/www/cobbler removed
+- implement "cobbler ___ dumpvars --name=X" feature to show template vars
+- validateks now works against all URLs as opposed to rendered local files
+- now possible to create new kickstarts in webui, and delete unused ones
+- support for OMAPI for avoid dhcp restarts
+- support for managing BIND
+- xen kernel (PV) distros do not get added to PXE menus as they won't boot there
+- cobbler buildiso command to build non live ISOs
+- cobbler replicate command
+- added cobbler repo option --mirror-locally to reference external repos without mirroring
+- all virt parameters on profiles can now be overriden on cobbler profile objects
+- added some additional links for kickstart viewing/editing to the web page
+
+- ??? - 0.8.3
- Make createrepo get run for local cobbler reposync invocations as needed
- fix WebUI documentation URL
- fix bug in /etc/cobbler/modules.conf regarding pluggable authn/z
- fix default flags for yumdownloader
- fix for RHEL 4u6 DVD/tree import x86_64 arch detection
+- fix for dnsmasq template file host config path
- fix dnsmasq template to point at the correct hosts file
- force all names to be alphanumeric
+- all mod python pieces now happy with Unicode output
* Fri Feb 22 2008 - 0.8.2
- fix to webui to allow repos to be edited there on profile page
diff --git a/MANIFEST.in b/MANIFEST.in
index 357cdcc..3216946 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,6 +2,7 @@ include loaders/COPYING_ELILO
include loaders/elilo-3.6-ia64.efi
include loaders/menu.c32
include config/cobbler.conf
+include config/cobbler_svc.conf
include config/rsync.exclude
include config/cobblerd
include config/cobblerd_rotate
@@ -10,21 +11,14 @@ include config/modules.conf
include config/auth.conf
include config/settings
include config/users.digest
+include config/users.conf
recursive-include templates *.template
recursive-include kickstarts *.ks
include docs/cobbler.1.gz
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 scripts/*.py
include snippets/*
recursive-include po *.pot
recursive-include po *.po
diff --git a/Makefile b/Makefile
index c40b4ed..d7fd78c 100644
--- a/Makefile
+++ b/Makefile
@@ -36,21 +36,27 @@ install: clean manpage
python setup.py install -f
devinstall:
- cp /var/lib/cobbler/settings /tmp/cobbler_settings
- cp /etc/cobbler/modules.conf /tmp/cobbler_modules.conf
+ -cp /etc/cobbler/settings /tmp/cobbler_settings
+ -cp /etc/cobbler/modules.conf /tmp/cobbler_modules.conf
+ -cp /etc/httpd/conf.d/cobbler.conf /tmp/cobbler_http.conf
+ -cp /etc/cobbler/users.conf /tmp/cobbler_users.conf
-cp /etc/cobbler/users.digest /tmp/cobbler_users.digest
make install
- cp /tmp/cobbler_settings /var/lib/cobbler/settings
- cp /tmp/cobbler_modules.conf /etc/cobbler/modules.conf
+ -cp /tmp/cobbler_settings /etc/cobbler/settings
+ -cp /tmp/cobbler_modules.conf /etc/cobbler/modules.conf
+ -cp /tmp/cobbler_users.conf /etc/cobbler/users.conf
-cp /tmp/cobbler_users.digest /etc/cobbler/users.digest
+ -cp /tmp/cobbler_http.conf /etc/httpd/conf.d/cobbler.conf
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
+ chmod -R +x /var/www/cobbler/svc
webtest: devinstall
/sbin/service cobblerd restart
/sbin/service httpd restart
+ chmod +x /var/www/cgi-bin/cobbler/*.cgi
sdist: clean messages updatewui
python setup.py sdist
@@ -60,7 +66,7 @@ messages: cobbler/*.py
sed -i'~' -e 's/SOME DESCRIPTIVE TITLE/cobbler/g' -e 's/YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/2007 Red Hat, Inc. /g' -e 's/FIRST AUTHOR <EMAIL@ADDRESS>, YEAR/Michael DeHaan <mdehaan@redhat.com>, 2007/g' -e 's/PACKAGE VERSION/cobbler $(VERSION)-$(RELEASE)/g' -e 's/PACKAGE/cobbler/g' $(MESSAGESPOT)
-rpms: clean manpage sdist
+rpms: clean updatewui manpage sdist
mkdir -p rpm-build
cp dist/*.gz rpm-build/
rpmbuild --define "_topdir %(pwd)/rpm-build" \
@@ -94,3 +100,7 @@ eraseconfig:
-rm /var/lib/cobbler/profiles*
-rm /var/lib/cobbler/systems*
-rm /var/lib/cobbler/repos*
+
+graphviz:
+ dot -Tpdf docs/cobbler.dot -o cobbler.pdf
+
diff --git a/cobbler.spec b/cobbler.spec
index f40af0a..c2a1375 100644
--- a/cobbler.spec
+++ b/cobbler.spec
@@ -2,8 +2,9 @@
Summary: Boot server configurator
Name: cobbler
AutoReq: no
-Version: 0.8.3
-Release: 3%{?dist}
+Version: 1.0.0
+Release: 1%{?dist}
+>>>>>>> devel:cobbler.spec
Source0: %{name}-%{version}.tar.gz
License: GPLv2+
Group: Applications/System
@@ -14,7 +15,6 @@ Requires: python-devel
Requires: createrepo
Requires: mod_python
Requires: python-cheetah
-Requires: rhpl
Requires: rsync
Requires(post): /sbin/chkconfig
Requires(preun): /sbin/chkconfig
@@ -41,9 +41,9 @@ 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.
+mirroring, and built-in DHCP/DNS Management. Cobbler has
+a Python and XMLRPC API for integration with other
+applications.
%prep
%setup -q
@@ -84,8 +84,8 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%defattr(755,apache,apache)
%dir /var/www/cobbler/web/
/var/www/cobbler/web/*.py*
-%dir /var/www/cgi-bin/cobbler/
-/var/www/cgi-bin/cobbler/*.cgi
+%dir /var/www/cobbler/svc/
+/var/www/cobbler/svc/*.py*
%defattr(755,apache,apache)
%dir /usr/share/cobbler/webui_templates
@@ -97,17 +97,12 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%dir /var/log/cobbler/kicklog
%dir /var/www/cobbler/
%dir /var/www/cobbler/localmirror
-%dir /var/www/cobbler/kickstarts
-%dir /var/www/cobbler/kickstarts_sys
%dir /var/www/cobbler/repo_mirror
%dir /var/www/cobbler/repos_profile
%dir /var/www/cobbler/repos_system
%dir /var/www/cobbler/ks_mirror
%dir /var/www/cobbler/ks_mirror/config
%dir /var/www/cobbler/images
-%dir /var/www/cobbler/distros
-%dir /var/www/cobbler/profiles
-%dir /var/www/cobbler/systems
%dir /var/www/cobbler/links
%defattr(755,apache,apache)
%dir /var/www/cobbler/webui
@@ -121,13 +116,13 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%dir /tftpboot/images
%{_bindir}/cobbler
%{_bindir}/cobblerd
-%{_bindir}/cobbler_auth_help
%dir /etc/cobbler
%config(noreplace) /etc/cobbler/*.ks
%config(noreplace) /etc/cobbler/*.template
%config(noreplace) /etc/cobbler/rsync.exclude
%config(noreplace) /etc/logrotate.d/cobblerd_rotate
%config(noreplace) /etc/cobbler/modules.conf
+%config(noreplace) /etc/cobbler/users.conf
%dir %{python_sitelib}/cobbler
%dir %{python_sitelib}/cobbler/yaml
%dir %{python_sitelib}/cobbler/modules
@@ -140,6 +135,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%{_mandir}/man1/cobbler.1.gz
/etc/init.d/cobblerd
%config(noreplace) /etc/httpd/conf.d/cobbler.conf
+%config(noreplace) /etc/httpd/conf.d/cobbler_svc.conf
%dir /var/log/cobbler/syslog
%defattr(755,root,root)
@@ -164,14 +160,17 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%dir /var/lib/cobbler/triggers/delete/repo/post
%dir /var/lib/cobbler/triggers/sync/pre
%dir /var/lib/cobbler/triggers/sync/post
+%dir /var/lib/cobbler/triggers/install/pre
%dir /var/lib/cobbler/triggers/install/post
%dir /var/lib/cobbler/snippets/
%defattr(744,root,root)
%config(noreplace) /var/lib/cobbler/triggers/sync/post/restart-services.trigger
+%config(noreplace) /var/lib/cobbler/triggers/install/pre/status_pre.trigger
+%config(noreplace) /var/lib/cobbler/triggers/install/post/status_post.trigger
%defattr(664,root,root)
-%config(noreplace) /var/lib/cobbler/settings
+%config(noreplace) /etc/cobbler/settings
%config(noreplace) /var/lib/cobbler/snippets/partition_select
/var/lib/cobbler/elilo-3.6-ia64.efi
/var/lib/cobbler/menu.c32
@@ -190,6 +189,15 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%changelog
+* Fri May 16 2008 Michael DeHaan <mdehaan@redhat.com> - 0.9.2-2
+- Upstream changes (see CHANGELOG)
+- moved /var/lib/cobbler/settings to /etc/cobbler/settings
+
+* Fri May 09 2008 Michael DeHaan <mdehaan@redhat.com> - 0.9.1-1
+- Upstream changes (see CHANGELOG)
+- packaged /etc/cobbler/users.conf
+- remaining CGI replaced with mod_python
+
* Tue Apr 08 2008 Michael DeHaan <mdehaan@redhat.com> - 0.8.3-2
- Upstream changes (see CHANGELOG)
diff --git a/cobbler/action_buildiso.py b/cobbler/action_buildiso.py
new file mode 100644
index 0000000..639bd6b
--- /dev/null
+++ b/cobbler/action_buildiso.py
@@ -0,0 +1,181 @@
+"""
+Builds non-live bootable CD's that have PXE-equivalent behavior
+for all cobbler profiles currently in memory.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <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 os
+import os.path
+import shutil
+import sub_process
+import sys
+import traceback
+import shutil
+import sub_process
+
+import utils
+from cexceptions import *
+from utils import _
+
+# FIXME: lots of overlap with pxegen.py, should consolidate
+# FIXME: disable timeouts and remove local boot for this?
+HEADER = """
+
+DEFAULT menu
+PROMPT 0
+MENU TITLE Cobbler | http://cobbler.et.redhat.com
+TIMEOUT 200
+TOTALTIMEOUT 6000
+ONTIMEOUT local
+
+LABEL local
+ MENU LABEL (local)
+ MENU DEFAULT
+ LOCALBOOT -1
+
+"""
+
+class BuildIso:
+ """
+ Handles conversion of internal state to the tftpboot tree layout
+ """
+
+ def __init__(self,config,verbose=False):
+ """
+ Constructor
+ """
+ self.verbose = verbose
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.distmap = {}
+ self.distctr = 0
+
+ def make_shorter(self,distname):
+ if self.distmap.has_key(distname):
+ return self.distmap[distname]
+ else:
+ self.distctr = self.distctr + 1
+ self.distmap[distname] = str(self.distctr)
+ return str(self.distctr)
+
+ def run(self,iso=None,tempdir=None,profiles=None):
+
+ # verify we can find isolinux.bin
+
+ if iso is None:
+ iso = "kickstart.iso"
+
+ isolinuxbin = "/usr/lib/syslinux/isolinux.bin"
+ if not os.path.exists(isolinuxbin):
+ raise CX(_("Required file not found: %s") % isolinuxbin)
+
+ # if iso is none, create it in . as "cobbler.iso"
+ if tempdir is None:
+ tempdir = os.path.join(os.getcwd(), "buildiso")
+ print _("- using/creating tempdir: %s") % tempdir
+ if not os.path.exists(tempdir):
+ os.makedirs(tempdir)
+
+ # if base of tempdir does not exist, fail
+ # create all profiles unless filtered by "profiles"
+ imagesdir = os.path.join(tempdir, "images")
+ isolinuxdir = os.path.join(tempdir, "isolinux")
+
+ print _("- building tree for isolinux")
+ if not os.path.exists(imagesdir):
+ os.makedirs(imagesdir)
+ if not os.path.exists(isolinuxdir):
+ os.makedirs(isolinuxdir)
+
+ print _("- copying miscellaneous files")
+ utils.copyfile(isolinuxbin, os.path.join(isolinuxdir, "isolinux.bin"))
+ menu = "/var/lib/cobbler/menu.c32"
+ files = [ isolinuxbin, menu ]
+ for f in files:
+ if not os.path.exists(f):
+ raise CX(_("Required file not found: %s") % f)
+ utils.copyfile(f, os.path.join(isolinuxdir, os.path.basename(f)))
+
+ print _("- copying kernels and initrds")
+ # copy all images in included profiles to images dir
+ for x in self.api.profiles():
+ use_this = True
+ if profiles is not None:
+ which_profiles = profiles.split(",")
+ if not use_this in which_profiles:
+ use_this = False
+ dist = x.get_conceptual_parent()
+ if dist.name.find("-xen") != -1:
+ continue
+ distname = self.make_shorter(dist.name)
+ # tempdir/isolinux/$distro/vmlinuz, initrd.img
+ # FIXME: this will likely crash on non-Linux breeds
+ shutil.copyfile(dist.kernel, os.path.join(isolinuxdir, "%s.krn" % distname))
+ shutil.copyfile(dist.initrd, os.path.join(isolinuxdir, "%s.img" % distname))
+
+ # generate isolinux.cfg
+ print _("- generating a isolinux.cfg")
+ isolinuxcfg = os.path.join(isolinuxdir, "isolinux.cfg")
+ cfg = open(isolinuxcfg, "w+")
+ cfg.write(HEADER) # fixme, use template
+
+ for x in self.api.profiles():
+ # FIXME
+ use_this = True
+ if profiles is not None:
+ which_profiles = profiles.split(",")
+ if not use_this in which_profiles:
+ use_this = False
+ if use_this:
+ dist = x.get_conceptual_parent()
+ if dist.name.find("-xen") != -1:
+ continue
+ data = utils.blender(self.api, True, x)
+ distname = self.make_shorter(dist.name)
+
+ cfg.write("\n")
+ cfg.write("LABEL %s\n" % x.name)
+ cfg.write(" MENU LABEL %s\n" % x.name)
+ cfg.write(" kernel %s.krn\n" % distname)
+
+ if data["kickstart"].startswith("/"):
+ data["kickstart"] = "http://%s/cblr/svc/op/ks/profile/%s" % (
+ data["server"],
+ x.name
+ )
+
+ append_line = " append initrd=%s.img" % distname
+ append_line = append_line + " ks=%s " % data["kickstart"]
+ append_line = append_line + " %s\n" % data["kernel_options"]
+ cfg.write(append_line)
+
+ print _("- done writing config")
+ cfg.write("\n")
+ cfg.write("MENU END\n")
+ cfg.close()
+
+ cmd = "mkisofs -o %s -r -b isolinux/isolinux.bin -c isolinux/boot.cat" % iso
+ cmd = cmd + " -no-emul-boot -boot-load-size 4 "
+ cmd = cmd + " -boot-info-table -V Cobbler\ Install -R -J -T %s" % tempdir
+
+ print _("- running: %s") % cmd
+ rc = sub_process.call(cmd, shell=True)
+ if rc:
+ raise CX(_("mkisofs failed"))
+
+ print _("ISO build complete")
+ print _("You may wish to delete: %s") % tempdir
+ print _("The output file is: %s") % iso
+
+
diff --git a/cobbler/action_check.py b/cobbler/action_check.py
index f7bc9d9..74af0c6 100644
--- a/cobbler/action_check.py
+++ b/cobbler/action_check.py
@@ -17,8 +17,8 @@ import os
import re
import sub_process
import action_sync
-from rhpl.translate import _, N_, textdomain, utf8
-
+import utils
+from utils import _
class BootCheck:
def __init__(self,config):
@@ -36,8 +36,9 @@ class BootCheck:
"""
status = []
self.check_name(status)
+ self.check_selinux(status)
if self.settings.manage_dhcp:
- mode = self.settings.manage_dhcp_mode.lower()
+ mode = self.config.api.get_sync().dhcp.what()
if mode == "isc":
self.check_dhcpd_bin(status)
self.check_dhcpd_conf(status)
@@ -45,8 +46,16 @@ class BootCheck:
elif mode == "dnsmasq":
self.check_dnsmasq_bin(status)
self.check_service(status,"dnsmasq")
- else:
- status.append(_("manage_dhcp_mode in /var/lib/cobbler/settings should be 'isc' or 'dnsmasq'"))
+
+ if self.settings.manage_dns:
+ mode = self.config.api.get_sync().dns.what()
+ if mode == "bind":
+ self.check_bind_bin(status)
+ self.check_service(status,"named")
+ elif mode == "dnsmasq" and not self.settings.manage_dhcp:
+ self.check_dnsmasq_bin(status)
+ self.check_service(status,"dnsmasq")
+
self.check_service(status, "cobblerd")
self.check_bootloaders(status)
@@ -56,14 +65,25 @@ class BootCheck:
self.check_httpd(status)
self.check_iptables(status)
self.check_yum(status)
+ self.check_for_default_password(status)
+ self.check_for_unreferenced_repos(status)
+ self.check_for_unsynced_repos(status)
return status
def check_service(self, status, which):
- if os.path.exists("/etc/rc.d/init.d/%s" % which):
- rc = sub_process.call("/sbin/service %s status >/dev/null 2>/dev/null" % which, shell=True)
- if rc != 0:
- status.append(_("service %s is not running") % which)
+ if utils.check_dist() == "redhat":
+ if os.path.exists("/etc/rc.d/init.d/%s" % which):
+ rc = sub_process.call("/sbin/service %s status >/dev/null 2>/dev/null" % which, shell=True)
+ if rc != 0:
+ status.append(_("service %s is not running") % which)
+ elif utils.check_dist() == "debian":
+ if os.path.exists("/etc/init.d/%s" % which):
+ rc = sub_process.call("/etc/init.d/%s status /dev/null 2>/dev/null" % which, shell=True)
+ if rc != 0:
+ status.append(_("service %s is not running") % which)
+ else:
+ status.append(_("Unknown distribution type, cannot check for running service %s" % which))
def check_iptables(self, status):
if os.path.exists("/etc/rc.d/init.d/iptables"):
@@ -86,18 +106,67 @@ class BootCheck:
parameters.
"""
if self.settings.server == "127.0.0.1":
- status.append(_("The 'server' field in /var/lib/cobbler/settings must be set to something other than localhost, or kickstarting features will not work. This should be a resolvable hostname or IP for the boot server as reachable by all machines that will use it."))
+ status.append(_("The 'server' field in /etc/cobbler/settings must be set to something other than localhost, or kickstarting features will not work. This should be a resolvable hostname or IP for the boot server as reachable by all machines that will use it."))
if self.settings.next_server == "127.0.0.1":
- status.append(_("For PXE to be functional, the 'next_server' field in /var/lib/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network."))
+ status.append(_("For PXE to be functional, the 'next_server' field in /etc/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network."))
+
+ def check_selinux(self,status):
+ prc = sub_process.Popen("/usr/sbin/getenforce",shell=True,stdout=sub_process.PIPE)
+ data = prc.communicate()[0]
+ if data.lower().find("disabled") == -1:
+ # permissive or enforcing or something else
+ prc2 = sub_process.Popen("/usr/sbin/getsebool -a",shell=True,stdout=sub_process.PIPE)
+ data2 = prc2.communicate()[0]
+ for line in data2.split("\n"):
+ if line.find("httpd_can_network_connect ") != -1:
+ if line.find("off") != -1:
+ status.append(_("Must enable selinux boolean to enable Apache and web services components, run: setsebool -P httpd_can_network_connect true"))
+
+
+ def check_for_default_password(self,status):
+ templates = utils.get_kickstart_templates(self.config.api)
+ files = []
+ for t in templates:
+ fd = open(t)
+ data = fd.read()
+ fd.close()
+ if data.find("\$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac.") != -1:
+ files.append(t)
+ if len(files) > 0:
+ status.append(_("One or more kickstart templates references default password 'cobbler' and should be changed for security reasons: %s") % ", ".join(files))
+
+
+ def check_for_unreferenced_repos(self,status):
+ repos = []
+ referenced = []
+ not_found = []
+ for r in self.config.api.repos():
+ repos.append(r.name)
+ for p in self.config.api.profiles():
+ my_repos = p.repos
+ referenced.extend(my_repos)
+ for r in referenced:
+ if r not in repos:
+ not_found.append(r)
+ if len(not_found) > 0:
+ status.append(_("One or more repos referenced by profile objects is no longer defined in cobbler: %s") % ", ".join(not_found))
+
+ def check_for_unsynced_repos(self,status):
+ need_sync = []
+ for r in self.config.repos():
+ if r.mirror_locally == 1:
+ lookfor = os.path.join(self.settings.webdir, "repo_mirror", r.name)
+ if not os.path.exists(lookfor):
+ need_sync.append(r.name)
+ if len(need_sync) > 0:
+ status.append(_("One or more repos need to be processed by cobbler reposync for the first time before kickstarting against them: %s") % ", ".join(need_sync))
+
def check_httpd(self,status):
"""
Check if Apache is installed.
"""
- if not os.path.exists(self.settings.httpd_bin):
- status.append(_("Apache doesn't appear to be installed"))
- else:
- self.check_service(status,"httpd")
+ self.check_service(status,"httpd")
def check_dhcpd_bin(self,status):
@@ -105,14 +174,22 @@ class BootCheck:
Check if dhcpd is installed
"""
if not os.path.exists(self.settings.dhcpd_bin):
- status.append(_("dhcpd isn't installed, but is enabled in /var/lib/cobbler/settings"))
+ status.append(_("dhcpd isn't installed, but management is enabled in /etc/cobbler/settings"))
def check_dnsmasq_bin(self,status):
"""
Check if dnsmasq is installed
"""
if not os.path.exists(self.settings.dnsmasq_bin):
- status.append(_("dnsmasq isn't installed, but is enabled in /var/lib/cobbler/settings"))
+ status.append(_("dnsmasq isn't installed, but management is enabled in /etc/cobbler/settings"))
+
+ def check_bind_bin(self,status):
+ """
+ Check if bind is installed.
+ """
+ if not os.path.exists(self.settings.bind_bin):
+ status.append(_("bind isn't installed, but management is enabled in /etc/cobbler/settings"))
+
def check_bootloaders(self,status):
"""
@@ -140,8 +217,9 @@ class BootCheck:
"""
Check if cobbler.conf's tftpboot directory exists
"""
- if not os.path.exists(self.settings.tftpboot):
- status.append(_("please create directory: %(dirname)s") % { "dirname" : self.settings.tftpboot })
+ bootloc = utils.tftpboot_location()
+ if not os.path.exists(bootloc):
+ status.append(_("please create directory: %(dirname)s") % { "dirname" : bootloc })
def check_tftpd_conf(self,status):
@@ -152,17 +230,15 @@ class BootCheck:
if os.path.exists(self.settings.tftpd_conf):
f = open(self.settings.tftpd_conf)
re_disable = re.compile(r'disable.*=.*yes')
- found_bootdir = False
for line in f.readlines():
if re_disable.search(line):
status.append(_("change 'disable' to 'no' in %(file)s") % { "file" : self.settings.tftpd_conf })
- if line.find("-s %s" % self.settings.tftpboot) != -1:
- found_bootdir = True
- if not found_bootdir:
- status.append(_("change 'server_args' to '-s %(args)s' in %(file)s") % { "file" : "/etc/xinetd.d/tftp", "args" : self.settings.tftpboot })
-
else:
status.append(_("file %(file)s does not exist") % { "file" : self.settings.tftpd_conf })
+
+ bootloc = utils.tftpboot_location()
+ if not os.path.exists(bootloc):
+ status.append(_("directory needs to be created: %s" % bootloc))
def check_dhcpd_conf(self,status):
diff --git a/cobbler/action_import.py b/cobbler/action_import.py
index db09818..3e726a6 100644
--- a/cobbler/action_import.py
+++ b/cobbler/action_import.py
@@ -23,7 +23,7 @@ import glob
import api
import utils
import shutil
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
WGET_CMD = "wget --mirror --no-parent --no-host-directories --directory-prefix %s/%s %s"
RSYNC_CMD = "rsync -a %s '%s' %s/ks_mirror/%s --exclude-from=/etc/cobbler/rsync.exclude --progress"
@@ -36,7 +36,7 @@ TRY_LIST = [
class Importer:
- def __init__(self,api,config,mirror,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None):
+ def __init__(self,api,config,mirror,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None):
"""
Performs an import of a install tree (or trees) from the given
mirror address. The prefix of the distro is to be specified
@@ -59,6 +59,7 @@ class Importer:
self.distros_added = []
self.kickstart_file = kickstart_file
self.rsync_flags = rsync_flags
+ self.arch = arch
# ----------------------------------------------------------------------
@@ -67,6 +68,26 @@ class Importer:
raise CX(_("import failed. no --mirror specified"))
if self.mirror_name is None:
raise CX(_("import failed. no --name specified"))
+ if self.arch is not None:
+ self.arch = self.arch.lower()
+ if self.arch not in [ "x86", "ia64", "x86_64" ]:
+ raise CX(_("arch must be x86, x86_64, or ia64"))
+
+ mpath = os.path.join(self.settings.webdir, "ks_mirror", self.mirror_name)
+
+ if os.path.exists(mpath) and self.arch is None:
+ raise CX(_("Something already exists at this import location (%s). You must specify --arch to avoid potentially overwriting existing files.") % mpath)
+
+ if self.arch:
+ # append the arch path to the name if the arch is not already
+ # found in the name.
+ found = False
+ for x in [ "ia64", "i386", "x86_64", "x86" ]:
+ if self.mirror_name.lower().find(x) != -1:
+ found = True
+ break
+ if not found:
+ self.mirror_name = self.mirror_name + "-" + self.arch
if self.mirror_name is None:
raise CX(_("import failed. no --name specified"))
@@ -171,13 +192,6 @@ class Importer:
print _("- skipping distro %s since it wasn't imported this time") % profile.distro
continue
- # THIS IS OBSOLETE:
- #
- #if not distro.kernel.startswith("%s/ks_mirror/" % self.settings.webdir):
- # # this isn't a mirrored profile, so we won't touch it
- # print _("- skipping %s since profile isn't mirrored") % profile.name
- # continue
-
if (self.kickstart_file == None):
kdir = os.path.dirname(distro.kernel)
base_dir = "/".join(kdir.split("/")[0:-2])
@@ -266,6 +280,8 @@ class Importer:
def set_kickstart(self, profile, flavor, major, minor):
if flavor == "fedora":
+ if major >= 8:
+ return profile.set_kickstart("/etc/cobbler/sample_end.ks")
if major >= 6:
return profile.set_kickstart("/etc/cobbler/sample.ks")
if flavor == "redhat" or flavor == "centos":
@@ -442,8 +458,7 @@ class Importer:
config_file.write("baseurl=http://@@http_server@@/cobbler/ks_mirror/%s\n" % (urlseg))
config_file.write("enabled=1\n")
config_file.write("gpgcheck=0\n")
- # NOTE: yum priority defaults to 99 if that plugin is enabled
- # so don't need to add priority=99 here
+ config_file.write("priority=1\n")
config_file.close()
# don't run creatrepo twice -- this can happen easily for Xen and PXE, when
diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py
index 457f3af..ad669be 100644
--- a/cobbler/action_litesync.py
+++ b/cobbler/action_litesync.py
@@ -30,7 +30,7 @@ from cexceptions import *
import traceback
import errno
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class BootLiteSync:
@@ -49,45 +49,36 @@ class BootLiteSync:
self.systems = config.systems()
self.settings = config.settings()
self.repos = config.repos()
- self.sync = action_sync.BootSync(self.config)
+ self.sync = config.api.get_sync()
def add_single_distro(self, name):
# get the distro record
distro = self.distros.find(name=name)
if distro is None:
raise CX(_("error in distro lookup: %s") % name)
- # generate YAML file in distros/$name in webdir
- self.sync.write_distro_file(distro)
# copy image files to images/$name in webdir & tftpboot:
- self.sync.copy_single_distro_files(distro)
+ self.sync.pxegen.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
- self.sync.rmfile(os.path.join(self.settings.webdir, "distros", name))
+ bootloc = utils.tftpboot_location()
# delete contents of images/$name directory in webdir
- self.sync.rmtree(os.path.join(self.settings.webdir, "images", name))
+ utils.rmtree(os.path.join(self.settings.webdir, "images", name))
# delete contents of images/$name in tftpboot
- self.sync.rmtree(os.path.join(self.settings.tftpboot, "images", name))
+ utils.rmtree(os.path.join(bootloc, "images", name))
# delete potential symlink to tree in webdir/links
- self.sync.rmfile(os.path.join(self.settings.webdir, "links", name))
+ utils.rmfile(os.path.join(self.settings.webdir, "links", name))
def add_single_profile(self, name):
# get the profile object:
profile = self.profiles.find(name=name)
if profile is None:
raise CX(_("error in profile lookup"))
- # rebuild profile_list YAML file in webdir
- self.sync.write_listings()
- # add profiles/$name YAML file in webdir
- self.sync.write_profile_file(profile)
- # generate kickstart for kickstarts/$name/ks.cfg in webdir
- self.sync.validate_kickstart_for_specific_profile(profile)
# rebuild the yum configuration files for any attached repos
- self.sync.retemplate_yum_repos(profile,True)
+ self.sync.yumgen.retemplate_yum_repos(profile,True)
# cascade sync
kids = profile.get_children()
for k in kids:
@@ -97,18 +88,16 @@ class BootLiteSync:
self.add_single_system(k.name)
def remove_single_profile(self, name):
- # rebuild profile_list YAML file in webdir
- self.sync.write_listings()
# delete profiles/$name file in webdir
- self.sync.rmfile(os.path.join(self.settings.webdir, "profiles", name))
+ utils.rmfile(os.path.join(self.settings.webdir, "profiles", name))
# delete contents on kickstarts/$name directory in webdir
- self.sync.rmtree(os.path.join(self.settings.webdir, "kickstarts", name))
+ utils.rmtree(os.path.join(self.settings.webdir, "kickstarts", name))
def update_system_netboot_status(self,name):
system = self.systems.find(name=name)
if system is None:
raise CX(_("error in system lookup for %s") % name)
- self.sync.write_all_system_files(system,True)
+ self.sync.pxegen.write_all_system_files(system)
def add_single_system(self, name):
# get the system object:
@@ -116,28 +105,42 @@ class BootLiteSync:
if system is None:
raise CX(_("error in system lookup for %s") % name)
# rebuild system_list file in webdir
- self.sync.regen_ethers() # /etc/ethers, for dnsmasq & rarpd
- self.sync.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq
- self.sync.write_listings()
- # write the PXE and YAML files for the system
- self.sync.write_all_system_files(system)
+ if self.settings.manage_dhcp:
+ self.sync.dhcp.regen_ethers()
+ if self.settings.manage_dns:
+ self.sync.dns.regen_hosts()
+ # write the PXE files for the system
+ self.sync.pxegen.write_all_system_files(system)
# per system kickstarts
- self.sync.validate_kickstart_for_specific_system(system)
- # rebuild the yum configuration files for any attached repos
- self.sync.retemplate_yum_repos(system,False)
+ self.sync.yumgen.retemplate_yum_repos(system,False)
+ if self.settings.manage_dhcp:
+ if self.settings.omapi_enabled:
+ for (name,interface) in system.interfaces.iteritems():
+ self.sync.dhcp.write_dhcp_lease(
+ self.settings.omapi_port,
+ interface["hostname"],
+ interface["mac_address"],
+ interface["ip_address"]
+ )
+
def remove_single_system(self, name):
+ bootloc = utils.tftpboot_location()
system_record = self.systems.find(name=name)
- # rebuild system_list file in webdir
- self.sync.write_listings()
- # delete system YAML file in systems/$name in webdir
- self.sync.rmfile(os.path.join(self.settings.webdir, "systems", name))
# delete contents of kickstarts_sys/$name in webdir
system_record = self.systems.find(name=name)
# delete any kickstart files related to this system
for (name,interface) in system_record.interfaces.iteritems():
filename = utils.get_config_filename(system_record,interface=name)
- self.sync.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename))
+ utils.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename))
+
+ if self.settings.manage_dhcp:
+ if self.settings.omapi_enabled:
+ for (name,interface) in system_record.interfaces.iteritems():
+ self.sync.dhcp.remove_dhcp_lease(
+ self.settings.omapi_port,
+ interface["hostname"]
+ )
# unneeded
#if not system_record.is_pxe_supported():
@@ -152,7 +155,7 @@ class BootLiteSync:
if distro is not None and distro in [ "ia64", "IA64"]:
itanic = True
if not itanic:
- self.sync.rmfile(os.path.join(self.settings.tftpboot, "pxelinux.cfg", filename))
+ utils.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename))
else:
- self.sync.rmfile(os.path.join(self.settings.tftpboot, filename))
+ utils.rmfile(os.path.join(bootloc, filename))
diff --git a/cobbler/action_replicate.py b/cobbler/action_replicate.py
new file mode 100644
index 0000000..ae3adc8
--- /dev/null
+++ b/cobbler/action_replicate.py
@@ -0,0 +1,160 @@
+"""
+Replicate from a cobbler master.
+
+Copyright 2007-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+Scott Henson <shenson@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 os
+import os.path
+import xmlrpclib
+import api as cobbler_api
+from utils import _
+
+class Replicate:
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ self.config = config
+ self.settings = config.settings()
+ self.api = config.api
+ self.remote = None
+ self.uri = None
+
+ # -------------------------------------------------------
+
+ def link_distro(self, distro):
+ """
+ Create the distro links
+ """
+ # find the tree location
+ dirname = os.path.dirname(distro.kernel)
+ tokens = dirname.split("/")
+ tokens = tokens[:-2]
+ base = "/".join(tokens)
+ dest_link = os.path.join(self.settings.webdir, "links", distro.name)
+
+ # create the links directory only if we are mirroring because with
+ # SELinux Apache can't symlink to NFS (without some doing)
+
+ if not os.path.exists(dest_link):
+ try:
+ os.symlink(base, dest_link)
+ except:
+ # this shouldn't happen but I've seen it ... debug ...
+ print _("- symlink creation failed: %(base)s, %(dest)s") % { "base" : base, "dest" : dest_link }
+
+ # -------------------------------------------------------
+
+ def add_distro(self, distro):
+ """
+ Add a distro that has been found
+ """
+ #Register the distro
+ if os.path.exists(distro['kernel']):
+ new_distro = self.api.new_distro()
+ new_distro.from_datastruct(distro)
+ #create the symlinks
+ self.link_distro(new_distro)
+ #Add the distro permanently
+ self.api.distros().add(new_distro, save=True)
+ print 'Added distro %s. Creating Links.' % distro['name']
+ else:
+ print 'Distro %s not here yet.' % distro['name']
+
+ # -------------------------------------------------------
+
+ def add_profile(self, profile):
+ """
+ Add a profile that has been found
+ """
+ #Register the new profile
+ new_profile = self.api.new_profile()
+ new_profile.from_datastruct(profile)
+ self.api.profiles().add(new_profile, save=True)
+ print 'Added profile %s.' % profile['name']
+
+
+ # -------------------------------------------------------
+
+ def check_profile(self, profile):
+ """
+ Check that a profile belongs to a distro
+ """
+ profiles = self.api.profiles().to_datastruct()
+ if profile not in profiles:
+ for distro in self.api.distros().to_datastruct():
+ if distro['name'] == profile['name']:
+ return True
+ return False
+
+
+ # -------------------------------------------------------
+
+ def sync_distros(self):
+ """
+ Sync distros from master
+ """
+ local_distros = self.api.distros()
+ remote_distros = self.remote.get_distros()
+
+ needsync = False
+ for distro in remote_distros:
+ if distro not in local_distros.to_datastruct():
+ print 'Found distro %s.' % distro['name']
+ self.add_distro(distro)
+ needsync = True
+
+ self.call_sync(needsync)
+
+
+ # -------------------------------------------------------
+
+ def sync_profiles(self):
+ """
+ Sync profiles from master
+ """
+ local_profiles = self.api.profiles()
+ remote_profiles = self.remote.get_profiles()
+
+ needsync = False
+ for profile in remote_profiles:
+ if self.check_profile(profile):
+ print 'Found profile %s.' % profilew['name']
+ self.add_profile(profile)
+ needsync = True
+ self.call_sync(needsync)
+
+
+ # -------------------------------------------------------
+
+ def call_sync(self, needsync):
+ if needsync:
+ self.api.sync()
+
+ # -------------------------------------------------------
+
+ def run(self, cobbler_master=None):
+ """
+ Get remote profiles and distros and sync them locally
+ """
+ if cobbler_master is not None:
+ self.uri = 'http://%s/cobbler_api' % cobbler_master
+ elif len(self.settings.cobbler_master) > 0:
+ self.uri = 'http://%s/cobbler_api' % self.settings.cobbler_master
+ else:
+ print _('No cobbler master found to replicate from, try --master.')
+ if self.uri is not None:
+ self.remote = xmlrpclib.Server(self.uri)
+ self.sync_distros()
+ self.sync_profiles()
+
diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py
index 32d38bd..57c2d08 100644
--- a/cobbler/action_reposync.py
+++ b/cobbler/action_reposync.py
@@ -25,7 +25,7 @@ from cexceptions import *
import traceback
import errno
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class RepoSync:
"""
@@ -57,8 +57,10 @@ class RepoSync:
self.verbose = verbose
for repo in self.repos:
if name is not None and repo.name != name:
+ # invoked to sync only a specific repo, this is not the one
continue
elif name is None and not repo.keep_updated:
+ # invoked to run against all repos, but this one is off
print _("- %s is set to not be updated") % repo.name
continue
@@ -71,6 +73,8 @@ class RepoSync:
if repo.is_rsync_mirror():
self.do_rsync(repo)
else:
+ # which may actually NOT reposync if the repo is set to not mirror locally
+ # but that's a technicality
self.do_reposync(repo)
self.update_permissions(repo_path)
@@ -105,7 +109,7 @@ class RepoSync:
store_path = os.path.join(self.settings.webdir, "repo_mirror")
dest_path = os.path.join(store_path, repo.name)
temp_path = os.path.join(store_path, ".origin")
- if not os.path.isdir(temp_path):
+ if not os.path.isdir(temp_path) and repo.mirror_locally:
# FIXME: there's a chance this might break the RHN D/L case
os.makedirs(temp_path)
@@ -118,18 +122,21 @@ class RepoSync:
# this is the simple non-RHN case.
# create the config file that yum will use for the copying
- temp_file = self.create_local_file(repo, temp_path, output=False)
+ if repo.mirror_locally:
+ temp_file = self.create_local_file(repo, temp_path, output=False)
- if not has_rpm_list:
+ if not has_rpm_list and repo.mirror_locally:
# if we have not requested only certain RPMs, use reposync
cmd = "/usr/bin/reposync --config=%s --repoid=%s --download_path=%s" % (temp_file, repo.name, store_path)
if repo.arch != "":
+ if repo.arch == "x86":
+ repo.arch = "i386" # FIX potential arch errors
cmd = "%s -a %s" % (cmd, repo.arch)
print _("- %s") % cmd
cmds.append(cmd)
- else:
+ elif repo.mirror_locally:
# create the output directory if it doesn't exist
if not os.path.exists(dest_path):
@@ -149,6 +156,8 @@ class RepoSync:
# this is the somewhat more-complex RHN case.
# NOTE: this requires that you have entitlements for the server and you give the mirror as rhn://$channelname
+ if not repo.mirror_locally:
+ raise CX(_("rhn:// repos do not work with --mirror-locally=1"))
if has_rpm_list:
print _("- warning: --rpm-list is not supported for RHN content")
@@ -161,7 +170,6 @@ class RepoSync:
if repo.arch != "":
cmd = "%s -a %s" % (cmd, repo.arch)
- print _("- %s") % cmd
cmds.append(cmd)
# now regardless of whether we're doing yumdownloader or reposync
@@ -169,9 +177,10 @@ class RepoSync:
# commands here. Any failure at any point stops the operation.
for cmd in cmds:
- rc = sub_process.call(cmd, shell=True)
- if rc !=0:
- raise CX(_("cobbler reposync failed"))
+ if repo.mirror_locally:
+ rc = sub_process.call(cmd, shell=True)
+ if rc !=0:
+ raise CX(_("cobbler reposync failed"))
# some more special case handling for RHN.
# create the config file now, because the directory didn't exist earlier
@@ -181,7 +190,8 @@ class RepoSync:
# now run createrepo to rebuild the index
- os.path.walk(dest_path, self.createrepo_walker, repo)
+ if repo.mirror_locally:
+ os.path.walk(dest_path, self.createrepo_walker, repo)
# create the config file the hosts will use to access the repository.
@@ -196,6 +206,9 @@ class RepoSync:
Handle copying of rsync:// and rsync-over-ssh repos.
"""
+ if not repo.mirror_locally:
+ raise CX(_("rsync:// urls must be mirrored locally, yum cannot access them directly"))
+
if repo.rpm_list != "":
print _("- warning: --rpm-list is not supported for rsync'd repositories")
dest_path = os.path.join(self.settings.webdir, "repo_mirror", repo.name)
@@ -235,22 +248,34 @@ class RepoSync:
config_file = open(fname, "w+")
config_file.write("[%s]\n" % repo.name)
config_file.write("name=%s\n" % repo.name)
+ optenabled = False
+ optgpgcheck = False
if output:
- line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name)
+ if repo.mirror_locally:
+ line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name)
+ else:
+ line = "baseurl=%s\n" % (repo.mirror)
+
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]))
+ if x == "enabled":
+ optenabled = True
+ if x == "gpgcheck":
+ optgpgcheck = True
else:
line = "baseurl=%s\n" % repo.mirror
http_server = "%s:%s" % (self.settings.server, self.settings.http_port)
line = line.replace("@@server@@",http_server)
config_file.write(line)
- config_file.write("enabled=1\n")
+ if not optenabled:
+ config_file.write("enabled=1\n")
config_file.write("priority=%s\n" % repo.priority)
# FIXME: potentially might want a way to turn this on/off on a per-repo basis
- config_file.write("gpgcheck=0\n")
+ if not optgpgcheck:
+ config_file.write("gpgcheck=0\n")
config_file.close()
return fname
@@ -260,7 +285,7 @@ class RepoSync:
"""
Used to run createrepo on a copied mirror.
"""
- if os.path.exists(os.path.join(dirname,"RPMS")) or repo.is_rsync_mirror():
+ if os.path.exists(dirname) or repo.is_rsync_mirror():
utils.remove_yum_olddata(dirname)
try:
cmd = "createrepo %s %s" % (repo.createrepo_flags, dirname)
@@ -289,7 +314,7 @@ class RepoSync:
if os.path.exists(getenforce):
data = sub_process.Popen(getenforce, shell=True, stdout=sub_process.PIPE).communicate()[0]
if data.lower().find("disabled") == -1:
- cmd3 = "chcon --reference /var/www %s" % repo_path
+ cmd3 = "chcon --reference /var/www %s >/dev/null 2>/dev/null" % repo_path
sub_process.call(cmd3, shell=True)
diff --git a/cobbler/action_status.py b/cobbler/action_status.py
index cc6ebd4..79f9083 100644
--- a/cobbler/action_status.py
+++ b/cobbler/action_status.py
@@ -2,7 +2,7 @@
Reports on kickstart activity by examining the logs in
/var/log/cobbler.
-Copyright 2007, Red Hat, Inc
+Copyright 2007-2008, Red Hat, Inc
Michael DeHaan <mdehaan@redhat.com>
This software may be freely redistributed under the terms of the GNU
@@ -19,213 +19,138 @@ import glob
import time
import api as cobbler_api
-from rhpl.translate import _, N_, textdomain, utf8
+#from utils import _
+# ARRAY INDEXES
+MOST_RECENT_START = 0
+MOST_RECENT_STOP = 1
+MOST_RECENT_TARGET = 2
+SEEN_START = 3
+SEEN_STOP = 4
+STATE = 5
class BootStatusReport:
+
def __init__(self,config,mode):
"""
Constructor
"""
self.config = config
self.settings = config.settings()
+ self.ip_data = {}
self.mode = mode
# -------------------------------------------------------
- def scan_apache_logfiles(self):
- results = {}
- files = [ "/var/log/httpd/access_log" ]
- for x in range(1,4):
- consider = "/var/log/httpd/access_log.%s" % x
- if os.path.exists(consider):
- files.append(consider)
+ def scan_logfiles(self):
+
+ #profile foosball ? 127.0.0.1 start 1208294043.58
+ #system neo ? 127.0.0.1 start 1208295122.86
+
+
+ files = glob.glob("/var/log/cobbler/install.log*")
for fname in files:
- fh = open(fname)
- data = fh.readline()
- while (data is not None and data != ""):
- data = fh.readline()
- tokens = data.split(None)
- if len(tokens) < 6:
- continue
- ip = tokens[0]
- stime = tokens[3].replace("[","")
- req = tokens[6]
- if req.find("/cblr") == -1:
- continue
- ttime = time.strptime(stime,"%d/%b/%Y:%H:%M:%S")
- itime = time.mktime(ttime)
- if not results.has_key(ip):
- results[ip] = {}
- results[ip][itime] = req
-
- return results
+ fd = open(fname)
+ data = fd.read()
+ for line in data.split("\n"):
+ tokens = line.split()
+ if len(tokens) == 0:
+ continue
+ (profile_or_system, name, ip, start_or_stop, ts) = tokens
+ self.catalog(profile_or_system,name,ip,start_or_stop,ts)
+ fd.close()
+
+ # ------------------------------------------------------
+
+ def catalog(self,profile_or_system,name,ip,start_or_stop,ts):
+ ip_data = self.ip_data
+
+ if not ip_data.has_key(ip):
+ ip_data[ip] = [ -1, -1, "?", 0, 0, "?" ]
+ elem = ip_data[ip]
+
+ ts = float(ts)
+
+ mrstart = elem[MOST_RECENT_START]
+ mrstop = elem[MOST_RECENT_STOP]
+ mrtarg = elem[MOST_RECENT_TARGET]
+ snstart = elem[SEEN_START]
+ snstop = elem[SEEN_STOP]
+
+
+ if start_or_stop == "start":
+ if mrstart < ts:
+ mrstart = ts
+ mrtarg = "%s:%s" % (profile_or_system, name)
+ elem[SEEN_START] = elem[SEEN_START] + 1
+
+ if start_or_stop == "stop":
+ if mrstop < ts:
+ mrstop = ts
+ mrtarg = "%s:%s" % (profile_or_system, name)
+ elem[SEEN_STOP] = elem[SEEN_STOP] + 1
+
+ elem[MOST_RECENT_START] = mrstart
+ elem[MOST_RECENT_STOP] = mrstop
+ elem[MOST_RECENT_TARGET] = mrtarg
# -------------------------------------------------------
- def scan_syslog_logfiles(self):
-
- # find all of the logged IP addrs
- filelist = glob.glob("/var/log/cobbler/syslog/*")
- filelist.sort()
- results = {}
-
- for fullname in filelist:
- #fname = os.path.basename(fullname)
- logfile = open(fullname, "r")
- # for each line in the file...
- data = logfile.readline()
- while(data is not None and data != ""):
- data = logfile.readline()
-
- try:
- (epoch, strdate, ip, request) = data.split("\t", 3)
- epoch = float(epoch)
- except:
- continue
-
- if not results.has_key(ip):
- results[ip] = {}
- results[ip][epoch] = request
-
- return results
+ def process_results(self):
+ # FIXME: this should update the times here
+
+ tnow = int(time.time())
+ for ip in self.ip_data.keys():
+ elem = self.ip_data[ip]
+
+ start = int(elem[MOST_RECENT_START])
+ stop = int(elem[MOST_RECENT_STOP])
+ if (stop > start):
+ elem[STATE] = "finished"
+ else:
+ delta = tnow - start
+ min = delta / 60
+ sec = delta % 60
+ if min > 100:
+ elem[STATE] = "unknown/stalled"
+ else:
+ elem[STATE] = "installing (%sm %ss)" % (min,sec)
+
+ return self.ip_data
+
+ def get_printable_results(self):
+ format = "%-15s|%-20s|%-17s|%-17s"
+ ip_data = self.ip_data
+ ips = ip_data.keys()
+ ips.sort()
+ line = (
+ "ip",
+ "target",
+ "start",
+ "state",
+ )
+ buf = format % line
+ for ip in ips:
+ elem = ip_data[ip]
+ line = (
+ ip,
+ elem[MOST_RECENT_TARGET],
+ time.ctime(elem[MOST_RECENT_START]),
+ elem[STATE]
+ )
+ buf = buf + "\n" + format % line
+ return buf
# -------------------------------------------------------
def run(self):
"""
Calculate and print a kickstart-status report.
- For kickstart trees not in /var/lib/cobbler (or a symlink off of there)
- tracking will be incomplete. This should be noted in the docs.
"""
- api = cobbler_api.BootAPI()
-
- apache_results = self.scan_apache_logfiles()
- syslog_results = self.scan_syslog_logfiles()
- ips = apache_results.keys()
- ips.sort()
- ips2 = syslog_results.keys()
- ips2.sort()
-
- ips.extend(ips2)
- ip_printed = {}
-
- last_recorded_time = 0
- time_collisions = 0
-
- #header = ("Name", "State", "Started", "Last Request", "Seconds", "Log Entries")
- print "%-20s | %-15s | %-25s | %-25s | %-10s | %-6s" % (
- _("Name"),
- _("State"),
- _("Last Request"),
- _("Started"),
- _("Seconds"),
- _("Log Entries")
- )
-
-
- for ip in ips:
- if ip_printed.has_key(ip):
- continue
- ip_printed[ip] = 1
- entries = {} # hash of access times and messages
- if apache_results.has_key(ip):
- times = apache_results[ip].keys()
- for logtime in times:
- request = apache_results[ip][logtime]
- if request.find("?system_done") != -1:
- entries[logtime] = "DONE"
- elif request.find("?profile_done") != -1:
- entries[logtime] = "DONE"
- else:
- entries[logtime] = "1" # don't really care what the filename was
-
- if syslog_results.has_key(ip):
- times = syslog_results[ip].keys()
- for logtime in times:
- request = syslog_results[ip][logtime]
- if request.find("methodcomplete") != -1:
- entries[logtime] = "DONE"
- elif request.find("Method =") != -1:
- entries[logtime] = "START"
- else:
- entries[logtime] = "1"
-
- obj = api.systems().find(ip_address=ip)
-
- if obj is not None:
- self.generate_report(entries,obj.name)
- else:
- self.generate_report(entries,ip)
-
+ self.scan_logfiles()
+ self.process_results()
+ print self.get_printable_results()
return True
- #-----------------------------------------
-
- def generate_report(self,entries,name):
- """
- Given the information about transferred files and kickstart finish times, attempt
- to produce a report that most describes the state of the system.
- """
- # sort the access times
- rtimes = entries.keys()
- rtimes.sort()
-
- # variables for calculating kickstart state
- last_request_time = 0
- last_start_time = 0
- last_done_time = 0
- fcount = 0
-
- if len(rtimes) == 0:
- print _("%s: ?") % name
- return
-
- # for each request time the machine has made
- for rtime in rtimes:
-
- rtime = rtime
- fname = entries[rtime]
-
- if fname == "START":
- install_state = "installing"
- last_start_time = rtime
- last_request_time = rtime
- fcount = 0
- elif fname == "DONE":
- # kickstart finished
- last_done_time = rtime
- install_state = "done"
- else:
- install_state = "?"
- last_request_time = rtime
- fcount = fcount + 1
-
- # calculate elapsed time for kickstart
- elapsed_time = 0
- if install_state == "done":
- elapsed_time = int(last_done_time - last_start_time)
- else:
- elapsed_time = int(last_request_time - last_start_time)
-
- # FIXME: IP to MAC mapping where cobbler knows about it would be nice.
- display_start = time.asctime(time.localtime(last_start_time))
- display_last = time.asctime(time.localtime(last_request_time))
-
- if display_start.find(" 1969") != -1:
- display_start = "?"
- elapsed_time = "?"
-
- # print the status line for this IP address
- print "%-20s | %-15s | %-25s | %-25s | %-10s | %-6s" % (
- name,
- install_state,
- display_start,
- display_last,
- elapsed_time,
- fcount
- )
-
-
diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py
index 139066e..55cdad5 100644
--- a/cobbler/action_sync.py
+++ b/cobbler/action_sync.py
@@ -1,10 +1,9 @@
"""
-Builds out a TFTP/cobbler boot tree based on the object tree.
+Builds out filesystem trees/data based on the object tree.
This is the code behind 'cobbler sync'.
-Copyright 2006,2007, Red Hat, Inc
+Copyright 2006-2008, Red Hat, Inc
Michael DeHaan <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.
@@ -22,19 +21,23 @@ import yaml # Howell-Clark version
import sub_process
import sys
import glob
+import traceback
+import errno
import utils
from cexceptions import *
-import traceback
-import errno
+import templar
+import pxegen
+import yumgen
import item_distro
import item_profile
+import item_repo
import item_system
from Cheetah.Template import Template
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class BootSync:
@@ -42,7 +45,7 @@ class BootSync:
Handles conversion of internal state to the tftpboot tree layout
"""
- def __init__(self,config,verbose=False):
+ def __init__(self,config,verbose=False,dhcp=None,dns=None):
"""
Constructor
"""
@@ -54,202 +57,53 @@ class BootSync:
self.systems = config.systems()
self.settings = config.settings()
self.repos = config.repos()
- self.blend_cache = {}
- self.load_snippet_cache()
+ self.templar = templar.Templar(config)
+ self.pxegen = pxegen.PXEGen(config)
+ self.dns = dns
+ self.dhcp = dhcp
+ self.yumgen = yumgen.YumGen(config)
+ self.bootloc = utils.tftpboot_location()
def run(self):
"""
Syncs the current configuration file with the config tree.
Using the Check().run_ functions previously is recommended
"""
- if not os.path.exists(self.settings.tftpboot):
- raise CX(_("cannot find directory: %s") % self.settings.tftpboot)
+ if not os.path.exists(self.bootloc):
+ raise CX(_("cannot find directory: %s") % self.bootloc)
# run pre-triggers...
utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/pre/*")
- # in case the pre-trigger modified any objects...
+ # (paranoid) in case the pre-trigger modified any objects...
self.api.deserialize()
self.distros = self.config.distros()
self.profiles = self.config.profiles()
self.systems = self.config.systems()
self.settings = self.config.settings()
self.repos = self.config.repos()
+ self.pxegen = pxegen.PXEGen(self.config)
+ self.yumgen = yumgen.YumGen(self.config)
# execute the core of the sync operation
self.clean_trees()
- self.copy_bootloaders()
- self.copy_distros()
- self.retemplate_all_yum_repos()
- self.validate_kickstarts()
- self.build_trees()
+ self.pxegen.copy_bootloaders()
+ self.pxegen.copy_distros()
+ for x in self.systems:
+ self.pxegen.write_all_system_files(x)
+ self.yumgen.retemplate_all_yum_repos()
if self.settings.manage_dhcp:
- # these functions DRT for ISC or dnsmasq
- self.write_dhcp_file()
- self.regen_ethers()
- self.regen_hosts()
- self.make_pxe_menu()
+ self.dhcp.write_dhcp_file()
+ self.dhcp.regen_ethers()
+ if self.settings.manage_dns:
+ self.dns.regen_hosts()
+ self.dns.write_dns_files()
+ self.pxegen.make_pxe_menu()
# run post-triggers
utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/post/*")
return True
- def copy_bootloaders(self):
- """
- Copy bootloaders to the configured tftpboot directory
- NOTE: we support different arch's if defined in
- /var/lib/cobbler/settings.
- """
- for loader in self.settings.bootloaders.keys():
- path = self.settings.bootloaders[loader]
- newname = os.path.basename(path)
- destpath = os.path.join(self.settings.tftpboot, newname)
- self.copyfile(path, destpath)
- self.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.settings.tftpboot, "menu.c32"))
-
- def write_dhcp_file(self):
- """
- DHCP files are written when manage_dhcp is set in
- /var/lib/cobbler/settings.
- """
-
- settings_file = self.settings.dhcpd_conf
- template_file = "/etc/cobbler/dhcp.template"
- mode = self.settings.manage_dhcp_mode.lower()
- if mode == "dnsmasq":
- settings_file = self.settings.dnsmasq_conf
- template_file = "/etc/cobbler/dnsmasq.template"
-
- try:
- f2 = open(template_file,"r")
- except:
- raise CX(_("error writing template to file: %s") % template_file)
- template_data = ""
- template_data = f2.read()
- f2.close()
-
- # build each per-system definition
- # as configured, this only works for ISC, patches accepted
- # from those that care about Itanium. elilo seems to be unmaintained
- # so additional maintaince in other areas may be required to keep
- # this working.
-
- elilo = os.path.basename(self.settings.bootloaders["ia64"])
-
- system_definitions = {}
- counter = 0
-
- # we used to just loop through each system, but now we must loop
- # through each network interface of each system.
-
- for system in self.systems:
- profile = system.get_conceptual_parent()
- distro = profile.get_conceptual_parent()
- for (name, interface) in system.interfaces.iteritems():
-
- mac = interface["mac_address"]
- ip = interface["ip_address"]
- host = interface["hostname"]
-
- if mac is None or mac == "":
- # can't write a DHCP entry for this system
- continue
-
- counter = counter + 1
- systxt = ""
-
- if mode == "isc":
-
- # the label the entry after the hostname if possible
- if host is not None and host != "":
- systxt = "\nhost %s {\n" % host
- else:
- systxt = "\nhost generic%d {\n" % counter
-
- if distro.arch == "ia64":
- # can't use pxelinux.0 anymore
- systxt = systxt + " filename \"/%s\";\n" % elilo
- systxt = systxt + " hardware ethernet %s;\n" % mac
- if ip is not None and ip != "":
- systxt = systxt + " fixed-address %s;\n" % ip
- systxt = systxt + "}\n"
-
- else:
- # dnsmasq. don't have to write IP and other info here, but we do tag
- # each MAC based on the arch of it's distro, if it needs something other
- # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq
- # reload (full "cobbler sync") would be required after adding the system
- # to cobbler, just to tag this relationship.
-
- if ip is not None and ip != "":
- if distro.arch.lower() == "ia64":
- systxt = "dhcp-host=net:ia64," + ip + "\n"
- # support for other arches needs modifications here
- else:
- systxt = ""
-
- dhcp_tag = interface["dhcp_tag"]
- if dhcp_tag == "":
- dhcp_tag = "default"
-
- if not system_definitions.has_key(dhcp_tag):
- system_definitions[dhcp_tag] = ""
- system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt
-
- # we are now done with the looping through each interface of each system
-
- metadata = {
- "insert_cobbler_system_definitions" : system_definitions.get("default",""),
- "date" : time.asctime(time.gmtime()),
- "cobbler_server" : self.settings.server,
- "next_server" : self.settings.next_server,
- "elilo" : elilo
- }
-
- # now add in other DHCP expansions that are not tagged with "default"
- for x in system_definitions.keys():
- if x == "default":
- continue
- metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x]
-
- self.apply_template(template_data, metadata, settings_file)
-
- def regen_ethers(self):
- # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date
- # every time we add a system.
- # read 'man ethers' for format info
- fh = open("/etc/ethers","w+")
- for sys in self.systems:
- for (name, interface) in sys.interfaces.iteritems():
- mac = interface["mac_address"]
- ip = interface["ip_address"]
- if mac is None or mac == "":
- # can't write this w/o a MAC address
- continue
- if ip is not None and ip != "":
- fh.write(mac.upper() + "\t" + ip + "\n")
- fh.close()
-
- def regen_hosts(self):
- # dnsmasq knows how to read this database for host info
- # (other things may also make use of this later)
- fh = open("/var/lib/cobbler/cobbler_hosts","w+")
- for sys in self.systems:
- for (name, interface) in sys.interfaces.iteritems():
- mac = interface["mac_address"]
- host = interface["hostname"]
- ip = interface["ip_address"]
- if mac is None or mac == "":
- continue
- if host is not None and host != "" and ip is not None and ip != "":
- fh.write(ip + "\t" + host + "\n")
- fh.close()
-
-
- #def templatify(self, data, metadata, outfile):
- # for x in metadata.keys():
- # template_data = template_data.replace("$%s" % x, metadata[x])
-
def clean_trees(self):
"""
Delete any previously built pxelinux.cfg tree and virt tree info and then create
@@ -267,770 +121,15 @@ class BootSync:
path = os.path.join(self.settings.webdir,x)
if os.path.isfile(path):
if not x.endswith(".py"):
- self.rmfile(path)
+ utils.rmfile(path)
if os.path.isdir(path):
- if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems","profiles","links","repo_profile","repo_system"] :
+ if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc"] :
# delete directories that shouldn't exist
- self.rmtree(path)
+ utils.rmtree(path)
if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system"]:
# clean out directory contents
- self.rmtree_contents(path)
- self.rmtree_contents(os.path.join(self.settings.tftpboot, "pxelinux.cfg"))
- self.rmtree_contents(os.path.join(self.settings.tftpboot, "images"))
-
- def copy_distros(self):
- """
- A distro is a kernel and an initrd. Copy all of them and error
- out if any files are missing. The conf file was correct if built
- via the CLI or API, though it's possible files have been moved
- since or perhaps they reference NFS directories that are no longer
- mounted.
-
- NOTE: this has to be done for both tftp and http methods
- """
- # copy is a 4-letter word but tftpboot runs chroot, thus it's required.
- for d in self.distros:
- print _("sync distro: %s") % d.name
- self.copy_single_distro_files(d)
-
- def copy_single_distro_files(self, d):
- for dirtree in [self.settings.tftpboot, self.settings.webdir]:
- distros = os.path.join(dirtree, "images")
- distro_dir = os.path.join(distros,d.name)
- self.mkdir(distro_dir)
- kernel = utils.find_kernel(d.kernel) # full path
- initrd = utils.find_initrd(d.initrd) # full path
- if kernel is None or not os.path.isfile(kernel):
- raise CX(_("kernel not found: %(file)s, distro: %(distro)s") % { "file" : d.kernel, "distro" : d.name })
- if initrd is None or not os.path.isfile(initrd):
- raise CX(_("initrd not found: %(file)s, distro: %(distro)s") % { "file" : d.initrd, "distro" : d.name })
- b_kernel = os.path.basename(kernel)
- b_initrd = os.path.basename(initrd)
- if kernel.startswith(dirtree):
- self.linkfile(kernel, os.path.join(distro_dir, b_kernel))
- else:
- self.copyfile(kernel, os.path.join(distro_dir, b_kernel))
- if initrd.startswith(dirtree):
- self.linkfile(initrd, os.path.join(distro_dir, b_initrd))
- else:
- self.copyfile(initrd, os.path.join(distro_dir, b_initrd))
-
- def validate_kickstarts(self):
- """
- Similar to what we do for distros, ensure all the kickstarts
- in conf file are valid. kickstarts are referenced by URL
- (http or ftp), can stay as is. kickstarts referenced by absolute
- path (i.e. are files path) will be mirrored over http.
- """
-
- self.validate_kickstarts_per_profile()
- self.validate_kickstarts_per_system()
- return True
-
- def validate_kickstarts_per_profile(self):
- """
- Koan provisioning (Virt + auto-ks) needs kickstarts
- per profile. Validate them as needed. Local kickstarts
- get template substitution. Since http:// kickstarts might
- get generated via magic URLs, those are *not* substituted.
- NFS kickstarts are also not substituted when referenced
- by NFS URL's as we don't copy those files over to the cobbler
- directories. They are supposed to be live such that an
- admin can update those without needing to run 'sync' again.
-
- NOTE: kickstart only uses the web directory (if it uses them at all)
- """
-
- for g in self.profiles:
- print _("sync profile: %s") % g.name
- self.validate_kickstart_for_specific_profile(g)
-
- def validate_kickstart_for_specific_profile(self,g):
- distro = g.get_conceptual_parent()
- meta = utils.blender(self.api, False, g, self.blend_cache)
- if distro is None:
- raise CX(_("profile %(profile)s references missing distro %(distro)s") % { "profile" : g.name, "distro" : g.distro })
- kickstart_path = utils.find_kickstart(meta["kickstart"])
- if kickstart_path is not None and os.path.exists(kickstart_path):
- # the input is an *actual* file, hence we have to copy it
- copy_path = os.path.join(
- self.settings.webdir,
- "kickstarts", # profile kickstarts go here
- g.name
- )
- self.mkdir(copy_path)
- dest = os.path.join(copy_path, "ks.cfg")
- try:
- meta = utils.blender(self.api, False, g, self.blend_cache)
- ksmeta = meta["ks_meta"]
- del meta["ks_meta"]
- meta.update(ksmeta) # make available at top level
- meta["yum_repo_stanza"] = self.generate_repo_stanza(g,True)
- meta["yum_config_stanza"] = self.generate_config_stanza(g,True)
- meta["kickstart_done"] = self.generate_kickstart_signal(g, None)
- 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() # leave this in, for now...
- msg = "err_kickstart2"
- raise CX(_("Error while rendering kickstart file %(src)s to %(dest)s") % { "src" : kickstart_path, "dest" : dest })
-
- def generate_kickstart_signal(self, profile, system=None):
- """
- Do things that we do at the end of kickstarts...
- * signal the status watcher we're done
- * disable PXE if needed
- * save the original kickstart file for debug
- """
-
- # FIXME: watcher is more of a request than a packaged file
- # we should eventually package something and let it do something important"
- pattern1 = "wget \"http://%s/cgi-bin/cobbler/nopxe.cgi?system=%s\""
- pattern2 = "wget \"http://%s/cobbler/%s/%s/ks.cfg\" -O /root/cobbler.ks"
- pattern3 = "wget \"http://%s/cgi-bin/cobbler/post_install_trigger.cgi?system=%s\""
-
- blend_this = profile
- if system:
- blend_this = system
-
- blended = utils.blender(self.api, False, blend_this, self.blend_cache)
- kickstart = blended.get("kickstart",None)
-
- buf = ""
- if system is not None:
- if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]:
- buf = buf + "\n" + pattern1 % (blended["http_server"], system.name)
- if kickstart and os.path.exists(kickstart):
- buf = buf + "\n" + pattern2 % (blended["http_server"], "kickstarts_sys", system.name)
- if self.settings.run_post_install_trigger:
- buf = buf + "\n" + pattern3 % (blended["http_server"], system.name)
-
- else:
- if kickstart and os.path.exists(kickstart):
- buf = buf + "\n" + pattern2 % (blended["http_server"], "kickstarts", profile.name)
-
- return buf
-
- def get_repo_segname(self, is_profile):
- if is_profile:
- return "repos_profile"
- else:
- return "repos_system"
-
- def generate_repo_stanza(self, obj, is_profile=True):
-
- """
- Automatically attaches yum repos to profiles/systems in kickstart files
- that contain the magic $yum_repo_stanza variable.
- """
-
- buf = ""
- blended = utils.blender(self.api, False, obj, self.blend_cache)
-
- configs = self.get_repo_filenames(obj,is_profile)
- for c in configs:
- name = c.split("/")[-1].replace(".repo","")
- (is_core, baseurl) = self.analyze_repo_config(c)
- buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl)
-
- return buf
-
- def analyze_repo_config(self, filename):
- fd = open(filename)
- data = fd.read()
- lines = data.split("\n")
- ret = False
- baseurl = None
- for line in lines:
- if line.find("ks_mirror") != -1:
- ret = True
- if line.find("baseurl") != -1:
- first, baseurl = line.split("=")
- fd.close()
- return (ret, baseurl)
-
- def get_repo_baseurl(self, server, repo_name, is_repo_mirror=True):
- """
- Construct the URL to a repo definition.
- """
- if is_repo_mirror:
- return "http://%s/cobbler/repo_mirror/%s" % (server, repo_name)
- else:
- return "http://%s/cobbler/ks_mirror/config/%s" % (server, repo_name)
-
- def get_repo_filenames(self, obj, is_profile=True):
- """
- For a given object, return the paths to repo configuration templates
- that will be used to generate per-object repo configuration files and
- baseurls
- """
-
- blended = utils.blender(self.api, False, obj, self.blend_cache)
- urlseg = self.get_repo_segname(is_profile)
-
- topdir = "%s/%s/%s/*.repo" % (self.settings.webdir, urlseg, blended["name"])
- files = glob.glob(topdir)
- return files
-
-
- def generate_config_stanza(self, obj, is_profile=True):
-
- """
- Add in automatic to configure /etc/yum.repos.d on the remote system
- if the kickstart file contains the magic $yum_config_stanza.
- """
-
- if not self.settings.yum_post_install_mirror:
- return ""
-
- urlseg = self.get_repo_segname(is_profile)
-
- distro = obj.get_conceptual_parent()
- if not is_profile:
- distro = distro.get_conceptual_parent()
-
- blended = utils.blender(self.api, False, obj, self.blend_cache)
- configs = self.get_repo_filenames(obj, is_profile)
- buf = ""
-
- # for each kickstart template we have rendered ...
- for c in configs:
-
- name = c.split("/")[-1].replace(".repo","")
- # add the line to create the yum config file on the target box
- conf = self.get_repo_config_file(blended["http_server"],urlseg,blended["name"],name)
- buf = buf + "wget \"%s\" --output-document=/etc/yum.repos.d/%s.repo\n" % (conf, name)
-
- return buf
-
- def get_repo_config_file(self,server,urlseg,obj_name,repo_name):
- """
- Construct the URL to a repo config file that is usable in kickstart
- for use with yum. This is different than the templates cobbler reposync
- creates, as this file will allow the server to migrate and have different
- variables for different subnets/profiles/etc.
- """
- return "http://%s/cblr/%s/%s/%s.repo" % (server,urlseg,obj_name,repo_name)
-
- def validate_kickstarts_per_system(self):
- """
- PXE provisioning needs kickstarts evaluated per system.
- Profiles would normally be sufficient, but not in cases
- such as static IP, where we want to be able to do templating
- on a system basis.
-
- NOTE: kickstart only uses the web directory (if it uses them at all)
- """
-
- for s in self.systems:
- print _("sync system: %s") % s.name
- self.validate_kickstart_for_specific_system(s)
-
- def validate_kickstart_for_specific_system(self,s):
- profile = s.get_conceptual_parent()
- if profile is None:
- raise CX(_("system %(system)s references missing profile %(profile)s") % { "system" : s.name, "profile" : s.profile })
- distro = profile.get_conceptual_parent()
- meta = utils.blender(self.api, False, s, self.blend_cache)
- kickstart_path = utils.find_kickstart(meta["kickstart"])
- if kickstart_path and os.path.exists(kickstart_path):
- copy_path = os.path.join(self.settings.webdir,
- "kickstarts_sys", # system kickstarts go here
- s.name
- )
- self.mkdir(copy_path)
- dest = os.path.join(copy_path, "ks.cfg")
- try:
- ksmeta = meta["ks_meta"]
- del meta["ks_meta"]
- meta.update(ksmeta) # make available at top level
- meta["yum_repo_stanza"] = self.generate_repo_stanza(s, False)
- meta["yum_config_stanza"] = self.generate_config_stanza(s, False)
- meta["kickstart_done"] = self.generate_kickstart_signal(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):
-
- # first load all of the files in /var/lib/cobbler/snippets and load them, for use
- # in adding long bits to kickstart templates without having to have them hard coded
- # inside the sync code.
-
- snippet_cache = {}
- snippets = glob.glob("%s/*" % self.settings.snippetsdir)
- for snip in snippets:
- if os.path.isdir(snip):
- continue
- snip_file = open(snip)
- data = snip_file.read()
- snip_file.close()
- snippet_cache[os.path.basename(snip)] = data
- self.snippet_cache = snippet_cache
-
-
- def apply_template(self, data_input, metadata, out_path):
- """
- Take filesystem file kickstart_input, apply metadata using
- Cheetah and save as out_path.
- """
-
- if type(data_input) != str:
- data = data_input.read()
- else:
- data = data_input
-
- # backward support for Cobbler's legacy (and slightly more readable)
- # template syntax.
- data = data.replace("TEMPLATE::","$")
-
- # replace contents of the data stream with items from the snippet cache
- # do not use Cheetah yet, Cheetah can't really be run twice on the same
- # stream and be expected to do the right thing
- newdata = ""
- for line in data.split("\n"):
- for x in self.snippet_cache:
- if not line.startswith("#"):
- line = line.replace("SNIPPET::%s" % x, self.snippet_cache[x])
- newdata = "\n".join((newdata, line))
- data = newdata
-
- # HACK: the ksmeta field may contain nfs://server:/mount in which
- # case this is likely WRONG for kickstart, which needs the NFS
- # directive instead. Do this to make the templates work.
- newdata = ""
- if metadata.has_key("tree") and metadata["tree"].startswith("nfs://"):
- for line in data.split("\n"):
- if line.find("--url") != -1 and line.find("url ") != -1:
- rest = metadata["tree"][6:] # strip off "nfs://" part
- try:
- (server, dir) = rest.split(":",2)
- except:
- raise CX(_("Invalid syntax for NFS path given during import: %s" % metadata["tree"]))
- line = "nfs --server %s --dir %s" % (server,dir)
- # but put the URL part back in so koan can still see
- # what the original value was
- line = line + "\n" + "#url --url=%s" % metadata["tree"]
- newdata = newdata + line + "\n"
- data = newdata
-
- # tell Cheetah not to blow up if it can't find a symbol for something
- data = "#errorCatcher Echo\n" + data
-
- # now do full templating scan, where we will also templatify the snippet insertions
- t = Template(source=data, searchList=[metadata])
- try:
- data_out = str(t)
- except:
- print _("There appears to be an formatting error in the template file.")
- print _("For completeness, the traceback from Cheetah has been included below.")
- raise
-
- # now apply some magic post-filtering that is used by cobbler import and some
- # other places, but doesn't use Cheetah. Forcing folks to double escape
- # things would be very unwelcome.
-
- for x in metadata:
- if type(metadata[x]) == str:
- data_out = data_out.replace("@@%s@@" % x, metadata[x])
-
- # remove leading newlines which apparently breaks AutoYAST ?
- if data_out.startswith("\n"):
- data_out = data_out.strip()
-
- if out_path is not None:
- self.mkdir(os.path.dirname(out_path))
- fd = open(out_path, "w+")
- fd.write(data_out)
- fd.close()
-
- return data_out
-
- def build_trees(self):
- """
- Now that kernels and initrds are copied and kickstarts are all valid,
- build the pxelinux.cfg tree, which contains a directory for each
- configured IP or MAC address. Also build a tree for Virt info.
-
- NOTE: some info needs to go in TFTP and HTTP directories, but not all.
- Usually it's just one or the other.
-
- """
-
- self.write_listings()
-
- # create pxelinux.cfg under tftpboot
- # and file for each MAC or IP (hex encoded 01-XX-XX-XX-XX-XX-XX)
-
- for d in self.distros:
- self.write_distro_file(d)
-
- for p in self.profiles:
- self.write_profile_file(p)
-
- for system in self.systems:
- self.write_all_system_files(system)
-
- def retemplate_all_yum_repos(self):
- for p in self.profiles:
- self.retemplate_yum_repos(p,True)
- for system in self.systems:
- self.retemplate_yum_repos(system,False)
-
- def retemplate_yum_repos(self,obj,is_profile):
- # FIXME: blender could use caching for performance
- # FIXME: make stanza generation code load stuff from the right place
- """
- Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo
- and also potentially in listed in the source_repos structure of the distro object, however
- these files have server URLs in them that must be templated out. This function does this.
- """
- blended = utils.blender(self.api, False, obj, self.blend_cache)
-
- if is_profile:
- outseg = "repos_profile"
- else:
- outseg = "repos_system"
-
- input_files = []
-
- # chance old versions from upgrade do not have a source_repos
- # workaround for user bug
- if not blended.has_key("source_repos"):
- blended["source_repos"] = []
-
- # tack on all the install source repos IF there is more than one.
- # this is basically to support things like RHEL5 split trees
- # if there is only one, then there is no need to do this.
-
- for r in blended["source_repos"]:
- filename = self.settings.webdir + "/" + "/".join(r[0].split("/")[4:])
- input_files.append(filename)
-
- for repo in blended["repos"]:
- input_files.append(os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo"))
-
- for infile in input_files:
- if infile.find("ks_mirror") == -1:
- dispname = infile.split("/")[-2]
- else:
- dispname = infile.split("/")[-1].replace(".repo","")
- confdir = os.path.join(self.settings.webdir, outseg)
- outdir = os.path.join(confdir, blended["name"])
- self.mkdir(outdir)
- try:
- infile_h = open(infile)
- except:
- print _("WARNING: cobbler reposync needs to be run on repo (%s), then re-run cobbler sync") % dispname
- continue
- infile_data = infile_h.read()
- infile_h.close()
- outfile = os.path.join(outdir, "%s.repo" % (dispname))
- self.apply_template(infile_data, blended, outfile)
-
-
- def write_all_system_files(self,system,just_edit_pxe=False):
-
- profile = system.get_conceptual_parent()
- if profile is None:
- raise CX(_("system %(system)s references a missing profile %(profile)s") % { "system" : system.name, "profile" : system.profile})
- distro = profile.get_conceptual_parent()
- if distro is None:
- raise CX(_("profile %(profile)s references a missing distro %(distro)s") % { "profile" : system.profile, "distro" : profile.distro})
-
- # this used to just generate a single PXE config file, but now must
- # generate one record for each described NIC ...
-
- counter = 0
- for (name,interface) in system.interfaces.iteritems():
-
- ip = interface["ip_address"]
-
- f1 = utils.get_config_filename(system,interface=name)
-
- # for tftp only ...
- if distro.arch in [ "x86", "x86_64", "standard"]:
- # pxelinux wants a file named $name under pxelinux.cfg
- f2 = os.path.join(self.settings.tftpboot, "pxelinux.cfg", f1)
- if distro.arch == "ia64":
- # elilo expects files to be named "$name.conf" in the root
- # and can not do files based on the MAC address
- if ip is not None and ip != "":
- print _("Warning: Itanium system object (%s) needs an IP address to PXE") % system.name
-
- filename = "%s.conf" % utils.get_config_filename(system,interface=name)
- f2 = os.path.join(self.settings.tftpboot, filename)
-
- f3 = os.path.join(self.settings.webdir, "systems", f1)
-
- if system.netboot_enabled and system.is_pxe_supported():
- if distro.arch in [ "x86", "x86_64", "standard"]:
- self.write_pxe_file(f2,system,profile,distro,False)
- if distro.arch == "ia64":
- self.write_pxe_file(f2,system,profile,distro,True)
- else:
- # ensure the file doesn't exist
- self.rmfile(f2)
-
- if not just_edit_pxe:
- # allows netboot-disable to be highly performant
- # by not invoking the Cheetah engine
- self.write_system_file(f3,system)
-
- counter = counter + 1
-
-
- def make_pxe_menu(self):
- # only do this if there is NOT a system named default.
- default = self.systems.find(name="default")
- if default is not None:
- return
-
- fname = os.path.join(self.settings.tftpboot, "pxelinux.cfg", "default")
-
- # read the default template file
- template_src = open("/etc/cobbler/pxedefault.template")
- template_data = template_src.read()
-
- # sort the profiles
- profile_list = [profile for profile in self.profiles]
- def sort_name(a,b):
- return cmp(a.name,b.name)
- profile_list.sort(sort_name)
-
- # build out the menu entries
- pxe_menu_items = ""
- for profile in profile_list:
- distro = profile.get_conceptual_parent()
- contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False)
- if contents is not None:
- pxe_menu_items = pxe_menu_items + contents + "\n"
-
- # save the template.
- metadata = { "pxe_menu_items" : pxe_menu_items }
- outfile = os.path.join(self.settings.tftpboot, "pxelinux.cfg", "default")
- self.apply_template(template_data, metadata, outfile)
- template_src.close()
-
-
- def write_pxe_file(self,filename,system,profile,distro,is_ia64, include_header=True):
- """
- Write a configuration file for the boot loader(s).
- More system-specific configuration may come in later, if so
- that would appear inside the system object in api.py
-
- NOTE: relevant to tftp only
- """
-
- # ---
- # system might have netboot_enabled set to False (see item_system.py), if so,
- # don't do anything else and flag the error condition.
- if system is not None and not system.netboot_enabled:
- return None
-
- # ---
- # just some random variables
- template = None
- metadata = {}
- buffer = ""
-
- # ---
- # find kernel and initrd
- kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel))
- initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd))
-
- # Find the kickstart if we inherit from another profile
- kickstart_path = utils.blender(self.api, True, profile, self.blend_cache)["kickstart"]
-
- # ---
- # choose a template
- if system is None:
- template = "/etc/cobbler/pxeprofile.template"
- elif not is_ia64:
- template = "/etc/cobbler/pxesystem.template"
- else:
- template = "/etc/cobbler/pxesystem_ia64.template"
-
- # now build the kernel command line
- if system is not None:
- blended = utils.blender(self.api, True,system,self.blend_cache)
- else:
- blended = utils.blender(self.api, True,profile,self.blend_cache)
- kopts = blended["kernel_options"]
-
- # ---
- # generate the append line
- append_line = "append %s" % utils.hash_to_string(kopts)
- if not is_ia64:
- append_line = "%s initrd=%s" % (append_line, initrd_path)
- if len(append_line) >= 255 + len("append "):
- print _("warning: kernel option length exceeds 255")
-
- # ---
- # kickstart path rewriting (get URLs for local files)
- if kickstart_path is not None and kickstart_path != "":
-
- if system is not None and kickstart_path.startswith("/"):
- kickstart_path = "http://%s/cblr/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["http_server"], profile.name)
-
- if distro.breed is None or distro.breed == "redhat":
- append_line = "%s ks=%s" % (append_line, kickstart_path)
- elif distro.breed == "suse":
- append_line = "%s autoyast=%s" % (append_line, kickstart_path)
- elif distro.breed == "debian":
- append_line = "%s auto=true url=%s" % (append_line, kickstart_path)
- append_line = append_line.replace("ksdevice","interface")
-
- # ---
- # store variables for templating
- metadata["menu_label"] = ""
- if not is_ia64 and system is None:
- metadata["menu_label"] = "MENU LABEL %s" % profile.name
- metadata["profile_name"] = profile.name
- metadata["kernel_path"] = kernel_path
- metadata["initrd_path"] = initrd_path
- metadata["append_line"] = append_line
-
- # ---
- # get the template
- template_fh = open(template)
- template_data = template_fh.read()
- template_fh.close()
-
- # ---
- # save file and/or return results, depending on how called.
- buffer = self.apply_template(template_data, metadata, None)
- if filename is not None:
- fd = open(filename, "w")
- fd.write(buffer)
- fd.close()
- return buffer
-
-
- def write_listings(self):
- """
- Creates a very simple index of available systems and profiles
- that cobbler knows about. Just the names, no details.
- """
- names1 = [x.name for x in self.profiles]
- names2 = [x.name for x in self.systems]
- data1 = yaml.dump(names1)
- data2 = yaml.dump(names2)
- fd1 = open(os.path.join(self.settings.webdir, "profile_list"), "w+")
- fd2 = open(os.path.join(self.settings.webdir, "system_list"), "w+")
- fd1.write(data1)
- fd2.write(data2)
- fd1.close()
- fd2.close()
-
- def write_distro_file(self,distro):
- """
- Create distro information for koan install
- """
- blended = utils.blender(self.api, True, distro, self.blend_cache)
- filename = os.path.join(self.settings.webdir,"distros",distro.name)
- fd = open(filename, "w+")
- fd.write(yaml.dump(blended))
- fd.close()
-
- def write_profile_file(self,profile):
- """
- Create profile information for virt install
-
- NOTE: relevant to http only
- """
-
- blended = utils.blender(self.api, True, profile, self.blend_cache)
- filename = os.path.join(self.settings.webdir,"profiles",profile.name)
- fd = open(filename, "w+")
- if blended.has_key("kickstart") and blended["kickstart"].startswith("/"):
- # write the file location as needed by koan
- blended["kickstart"] = "http://%s/cblr/kickstarts/%s/ks.cfg" % (blended["http_server"], profile.name)
- fd.write(yaml.dump(blended))
- fd.close()
-
- def write_system_file(self,filename,system):
- """
- Create system information for virt install
-
- NOTE: relevant to http only
- """
-
- blended = utils.blender(self.api, True, system, self.blend_cache)
- filename = os.path.join(self.settings.webdir,"systems",system.name)
- fd = open(filename, "w+")
- fd.write(yaml.dump(blended))
- fd.close()
-
- def linkfile(self, src, dst):
- """
- Attempt to create a link dst that points to src. Because file
- systems suck we attempt several different methods or bail to
- self.copyfile()
- """
-
- try:
- return os.link(src, dst)
- except (IOError, OSError):
- pass
-
- try:
- return os.symlink(src, dst)
- except (IOError, OSError):
- pass
-
- return self.copyfile(src, dst)
-
- def copyfile(self,src,dst):
- try:
- return shutil.copyfile(src,dst)
- except:
- if not os.path.samefile(src,dst):
- # accomodate for the possibility that we already copied
- # the file as a symlink/hardlink
- raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst})
-
- def rmfile(self,path):
- try:
- os.unlink(path)
- return True
- except OSError, ioe:
- if not ioe.errno == errno.ENOENT: # doesn't exist
- traceback.print_exc()
- raise CX(_("Error deleting %s") % path)
- return True
-
- def rmtree_contents(self,path):
- what_to_delete = glob.glob("%s/*" % path)
- for x in what_to_delete:
- self.rmtree(x)
-
- def rmtree(self,path):
- try:
- if os.path.isfile(path):
- return self.rmfile(path)
- else:
- return shutil.rmtree(path,ignore_errors=True)
- except OSError, ioe:
- traceback.print_exc()
- if not ioe.errno == errno.ENOENT: # doesn't exist
- raise CX(_("Error deleting %s") % path)
- return True
+ utils.rmtree_contents(path)
+ utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg"))
+ utils.rmtree_contents(os.path.join(self.bootloc, "images"))
- def mkdir(self,path,mode=0777):
- try:
- return os.makedirs(path,mode)
- except OSError, oe:
- if not oe.errno == 17: # already exists (no constant for 17?)
- traceback.print_exc()
- print oe.errno
- raise CX(_("Error creating") % path)
diff --git a/cobbler/action_validate.py b/cobbler/action_validate.py
index f898478..44f3a9a 100644
--- a/cobbler/action_validate.py
+++ b/cobbler/action_validate.py
@@ -15,7 +15,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import os
import re
import sub_process
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
+import utils
class Validate:
@@ -39,16 +40,10 @@ class Validate:
failed = False
for x in self.config.profiles():
- distro = x.get_conceptual_parent()
- if distro.breed != "redhat":
- continue
- if not self.checkfile(x.name, "%s/kickstarts/%s/ks.cfg" % (self.settings.webdir, x.name)):
+ if not self.checkfile(x, True):
failed = True
for x in self.config.systems():
- distro = x.get_conceptual_parent().get_conceptual_parent()
- if distro.breed != "redhat":
- continue
- if not self.checkfile(x.name, "%s/kickstarts_sys/%s/ks.cfg" % (self.settings.webdir, x.name)):
+ if not self.checkfile(x, False):
failed = True
if failed:
@@ -58,15 +53,32 @@ class Validate:
return failed
- def checkfile(self,name,file):
- # print _("scanning rendered kickstart template: %s" % file)
- if not os.path.exists(file):
- print _("kickstart file does not exist for: %s") % name
- return False
- rc = os.system("/usr/bin/ksvalidator %s" % file)
- if not rc == 0:
- print _("ksvalidator detected a possible problem for: %s") % name
- print _(" rendered kickstart template at: %s" % file)
+ def checkfile(self,obj,is_profile):
+ blended = utils.blender(self.config.api, False, obj)
+ ks = blended["kickstart"]
+ breed = blended["breed"]
+ if breed != "redhat":
+ print "%s has a breed of %s, skipping" % (obj.name, breed)
+ return True
+ if ks is None or ks == "":
+ print "%s has no kickstart, skipping" % obj.name
+ return True
+
+ server = blended["server"]
+ if not ks.startswith("/"):
+ url = self.kickstart
+ elif is_profile:
+ url = "http://%s/cblr/svc/?op=ks;profile=%s" % (server,obj.name)
+ else:
+ url = "http://%s/cblr/svc/?op=ks;system=%s" % (server,obj.name)
+
+ print "----------------------------"
+ print "checking url: %s" % url
+
+
+ rc = os.system("/usr/bin/ksvalidator \"%s\"" % url)
+ if rc != 0:
return False
+
return True
diff --git a/cobbler/api.py b/cobbler/api.py
index 13fc5f3..9f4a636 100644
--- a/cobbler/api.py
+++ b/cobbler/api.py
@@ -22,14 +22,17 @@ import action_import
import action_reposync
import action_status
import action_validate
+import action_buildiso
+import action_replicate
from cexceptions import *
import sub_process
import module_loader
+import kickgen
import logging
import os
import fcntl
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
ERROR = 100
INFO = 10
@@ -38,7 +41,7 @@ DEBUG = 5
# notes on locking:
# BootAPI is a singleton object
# the XMLRPC variants allow 1 simultaneous request
-# therefore we flock on /var/lib/cobbler/settings for now
+# therefore we flock on /etc/cobbler/settings for now
# on a request by request basis.
class BootAPI:
@@ -79,20 +82,11 @@ class BootAPI:
"module",
"authz_allowall"
)
+ self.kickgen = kickgen.KickGen(self._config)
self.logger.debug("API handle initialized")
def __setup_logger(self,name):
- logger = logging.getLogger(name)
- logger.setLevel(logging.INFO)
- try:
- ch = logging.FileHandler("/var/log/cobbler/cobbler.log")
- except:
- raise CX(_("No write permissions on log file. Are you root?"))
- ch.setLevel(logging.INFO)
- formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s")
- ch.setFormatter(formatter)
- logger.addHandler(ch)
- return logger
+ return utils.setup_logger(name)
def log(self,msg,args=None,debug=False):
if debug:
@@ -221,21 +215,21 @@ class BootAPI:
self.log("new_repo",[is_subobject])
return self._config.new_repo(is_subobject=is_subobject)
- def add_distro(self, ref):
+ def add_distro(self, ref, check_for_duplicate_names=False):
self.log("add_distro",[ref.name])
- return self._config.distros().add(ref,save=True)
+ return self._config.distros().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
- def add_profile(self, ref):
+ def add_profile(self, ref, check_for_duplicate_names=False):
self.log("add_profile",[ref.name])
- return self._config.profiles().add(ref,save=True)
+ return self._config.profiles().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
- def add_system(self,ref):
+ def add_system(self, ref, check_for_duplicate_names=False, check_for_duplicate_netinfo=False):
self.log("add_system",[ref.name])
- return self._config.systems().add(ref,save=True)
+ return self._config.systems().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names,check_for_duplicate_netinfo=check_for_duplicate_netinfo)
- def add_repo(self,ref):
+ def add_repo(self, ref, check_for_duplicate_names=False):
self.log("add_repo",[ref.name])
- return self._config.repos().add(ref,save=True)
+ return self._config.repos().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
def find_distro(self, name=None, return_list=False, **kargs):
return self._config.distros().find(name=name, return_list=return_list, **kargs)
@@ -249,6 +243,9 @@ class BootAPI:
def find_repo(self, name=None, return_list=False, **kargs):
return self._config.repos().find(name=name, return_list=return_list, **kargs)
+ def dump_vars(self, obj, format=False):
+ return obj.dump_vars(format)
+
def auto_add_repos(self):
"""
Import any repos this server knows about and mirror them.
@@ -284,7 +281,14 @@ class BootAPI:
# run cobbler reposync to apply changes
return True
-
+
+ def generate_kickstart(self,profile,system):
+ self.log("generate_kickstart")
+ if system:
+ return self.kickgen.generate_kickstart_for_system(system)
+ else:
+ return self.kickgen.generate_kickstart_for_profile(profile)
+
def check(self):
"""
See if all preqs for network booting are valid. This returns
@@ -319,9 +323,22 @@ class BootAPI:
saved with serialize() will NOT be synchronized with this command.
"""
self.log("sync")
- sync = action_sync.BootSync(self._config)
+ sync = self.get_sync()
return sync.run()
+ def get_sync(self):
+ self.dhcp = self.get_module_from_file(
+ "dhcp",
+ "module",
+ "manage_isc"
+ ).get_manager(self._config)
+ self.dns = self.get_module_from_file(
+ "dns",
+ "module",
+ "manage_bind"
+ ).get_manager(self._config)
+ return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns)
+
def reposync(self, name=None):
"""
Take the contents of /var/lib/cobbler/repos and update them --
@@ -332,11 +349,11 @@ class BootAPI:
return reposync.run(name)
def status(self,mode):
- self.log("status",[mode])
- statusifier = action_status.BootStatusReport(self._config, mode)
+ self.log("status")
+ statusifier = action_status.BootStatusReport(self._config,mode)
return statusifier.run()
- def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None):
+ def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None):
"""
Automatically import a directory tree full of distribution files.
mirror_url can be a string that represents a path, a user@host
@@ -346,7 +363,7 @@ class BootAPI:
"""
self.log("import_tree",[mirror_url, mirror_name, network_root, kickstart_file, rsync_flags])
importer = action_import.Importer(
- self, self._config, mirror_url, mirror_name, network_root, kickstart_file, rsync_flags
+ self, self._config, mirror_url, mirror_name, network_root, kickstart_file, rsync_flags, arch
)
return importer.run()
@@ -405,3 +422,16 @@ class BootAPI:
self.log("authorize",[user,resource,arg1,arg2,rc],debug=True)
return rc
+ def build_iso(self,iso=None,profiles=None,tempdir=None):
+ builder = action_buildiso.BuildIso(self._config)
+ return builder.run(
+ iso=iso, profiles=profiles, tempdir=tempdir
+ )
+
+ def replicate(self, cobbler_master = None):
+ replicator = action_replicate.Replicate(self._config)
+ return replicator.run(cobbler_master = cobbler_master)
+
+ def get_kickstart_templates(self):
+ return utils.get_kickstar_templates(self)
+
diff --git a/cobbler/cexceptions.py b/cobbler/cexceptions.py
index a78f7f2..dba2857 100644
--- a/cobbler/cexceptions.py
+++ b/cobbler/cexceptions.py
@@ -20,6 +20,9 @@ class CobblerException(exceptions.Exception):
def __init__(self, value, *args):
self.value = value % args
+ # this is a hack to work around some odd exception handling
+ # in older pythons
+ self.from_cobbler = 1
def __str__(self):
return repr(self.value)
diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py
index c02302e..2aed672 100755
--- a/cobbler/cobbler.py
+++ b/cobbler/cobbler.py
@@ -20,10 +20,12 @@ import os
import os.path
import traceback
import optparse
+import string
import commands
+import cexceptions
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
I18N_DOMAIN = "cobbler"
####################################################
@@ -31,7 +33,6 @@ I18N_DOMAIN = "cobbler"
class BootCLI:
def __init__(self):
- textdomain(I18N_DOMAIN)
self.api = api.BootAPI()
self.loader = commands.FunctionLoader()
climods = self.api.get_modules_in_category("cli")
@@ -44,26 +45,36 @@ class BootCLI:
####################################################
+def run_upgrade_checks():
+ """
+ Cobbler tries to make manual upgrade steps unneeded, though
+ this function serves to inform users of manual steps when they /are/
+ needed.
+ """
+ # for users running pre-1.0 upgrading to 1.0
+ if os.path.exists("/var/lib/cobbler/settings"):
+ raise CX(_("/var/lib/cobbler/settings is no longer in use, remove this file to acknowledge you have migrated your configuration to /etc/cobbler/settings. Do not simply copy the file over or you will lose new configuration entries. Run 'cobbler check' and then 'cobbler sync' after making changes."))
+
def main():
"""
CLI entry point
"""
exitcode = 0
try:
- # FIXME: redo locking code?
+ run_upgrade_checks()
return BootCLI().run(sys.argv)
- except CX, exc:
- print str(exc)[1:-1] # remove framing air quotes
- except SystemExit:
- pass # probably exited from optparse, nothing extra to print
- except Exception, exc2:
- if str(type(exc2)).find("CX") == -1:
- traceback.print_exc()
- else:
- print str(exc2)[1:-1] # remove framing air quotes
+ except SystemExit, ex:
+ return 1
+ except Exception, exc:
+ (t, v, tb) = sys.exc_info()
+ try:
+ getattr(exc, "from_cobbler")
+ print str(exc)[1:-1]
+ except:
+ print t
+ print v
+ print string.join(traceback.format_list(traceback.extract_tb(tb)))
return 1
- return 1
-
if __name__ == "__main__":
sys.exit(main())
diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py
index 3c06723..065e99e 100644
--- a/cobbler/cobblerd.py
+++ b/cobbler/cobblerd.py
@@ -16,8 +16,9 @@ import time
import os
import SimpleXMLRPCServer
import glob
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import xmlrpclib
+import binascii
from server import xmlrpclib2
import api as cobbler_api
@@ -40,6 +41,8 @@ def core(logger=None):
pid = os.fork()
+ regen_ss_file()
+
if pid == 0:
# part one: XMLRPC -- which may be just read-only or both read-only and read-write
do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger)
@@ -47,6 +50,21 @@ def core(logger=None):
# part two: syslog, or syslog+avahi if avahi is installed
do_other_tasks(bootapi, settings, syslog_port, logger)
+def regen_ss_file():
+ # this is only used for Kerberos auth at the moment.
+ # it identifies XMLRPC requests from Apache that have already
+ # been cleared by Kerberos.
+
+ fd = open("/dev/urandom")
+ data = fd.read(512)
+ fd.close()
+ fd = open("/var/lib/cobbler/web.ss","w+")
+ fd.write(binascii.hexlify(data))
+ fd.close()
+ os.system("chmod 700 /var/lib/cobbler/web.ss")
+ os.system("chown apache /var/lib/cobbler/web.ss")
+ return 1
+
def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger):
if str(settings.xmlrpc_rw_enabled) != "0":
pid2 = os.fork()
@@ -108,7 +126,7 @@ def do_xmlrpc(bootapi, settings, port, logger):
# This is the simple XMLRPC API we provide to koan and other
# apps that do not need to manage Cobbler's config
- xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,True)
+ xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,False)
server = remote.CobblerXMLRPCServer(('', port))
server.logRequests = 0 # don't print stuff
@@ -124,7 +142,7 @@ def do_xmlrpc(bootapi, settings, port, logger):
def do_xmlrpc_rw(bootapi,settings,port,logger):
- xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,False)
+ xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,True)
server = remote.CobblerReadWriteXMLRPCServer(('127.0.0.1', port))
server.logRequests = 0 # don't print stuff
logger.debug("XMLRPC (read-write variant) running on %s" % port)
@@ -195,11 +213,14 @@ if __name__ == "__main__":
#main()
- bootapi = cobbler_api.BootAPI()
- settings = bootapi.settings()
- syslog_port = settings.syslog_port
- xmlrpc_port = settings.xmlrpc_port
- xmlrpc_port2 = settings.xmlrpc_rw_port
- logger = bootapi.logger_remote
- do_xmlrpc_unix(bootapi, settings, logger)
+ #bootapi = cobbler_api.BootAPI()
+ #settings = bootapi.settings()
+ #syslog_port = settings.syslog_port
+ #xmlrpc_port = settings.xmlrpc_port
+ #xmlrpc_port2 = settings.xmlrpc_rw_port
+ #logger = bootapi.logger_remote
+ #do_xmlrpc_unix(bootapi, settings, logger)
+
+ regen_ss_file()
+
diff --git a/cobbler/collection.py b/cobbler/collection.py
index 339a4b2..1b509f2 100644
--- a/cobbler/collection.py
+++ b/cobbler/collection.py
@@ -25,7 +25,7 @@ import item_profile
import item_distro
import item_repo
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Collection(serializable.Serializable):
@@ -35,7 +35,8 @@ class Collection(serializable.Serializable):
"""
self.config = config
self.clear()
- self.log_func = self.config.api.log
+ self.api = self.config.api
+ self.log_func = self.api.log
self.lite_sync = None
def factory_produce(self,config,seed_data):
@@ -125,10 +126,10 @@ class Collection(serializable.Serializable):
k.set_parent(newname)
else:
k.set_distro(newname)
- self.config.api.profiles().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers)
+ self.api.profiles().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers)
elif k.COLLECTION_TYPE == "system":
k.set_profile(newname)
- self.config.api.systems().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers)
+ self.api.systems().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers)
elif k.COLLECTION_TYPE == "repo":
raise CX(_("internal error, not expected to have repo child objects"))
else:
@@ -139,7 +140,7 @@ class Collection(serializable.Serializable):
return True
- def add(self,ref,save=False,with_copy=False,with_triggers=True,with_sync=True,quick_pxe_update=False):
+ def add(self,ref,save=False,with_copy=False,with_triggers=True,with_sync=True,quick_pxe_update=False,check_for_duplicate_names=False,check_for_duplicate_netinfo=False):
"""
Add an object to the collection, if it's valid. Returns True
if the object was added to the collection. Returns False if the
@@ -167,6 +168,11 @@ class Collection(serializable.Serializable):
# if not saving the object, you can't run these features
with_triggers = False
with_sync = False
+
+ # Avoid adding objects to the collection
+ # if an object of the same/ip/mac already exists.
+ self.__duplication_checks(ref,check_for_duplicate_names,check_for_duplicate_netinfo)
+
if ref is None or not ref.is_valid():
raise CX(_("insufficient or invalid arguments supplied"))
@@ -220,6 +226,51 @@ class Collection(serializable.Serializable):
def _run_triggers(self,ref,globber):
return utils.run_triggers(ref,globber)
+ def __duplication_checks(self,ref,check_for_duplicate_names,check_for_duplicate_netinfo):
+ """
+ Prevents adding objects with the same name.
+ Prevents adding or editing to provide the same IP, or MAC.
+ Enforcement is based on whether the API caller requests it.
+ """
+
+ # always protect against duplicate names
+ if check_for_duplicate_names:
+ match = None
+ if isinstance(ref, item_system.System):
+ match = self.api.find_system(ref.name)
+ elif isinstance(ref, item_profile.Profile):
+ match = self.api.find_profile(ref.name)
+ elif isinstance(ref, item_distro.Distro):
+ match = self.api.find_distro(ref.name)
+ elif isinstance(ref, item_repo.Repo):
+ match = self.api.find_repo(ref.name)
+
+ if match:
+ raise CX(_("An object already exists with that name. Try 'edit'?"))
+
+ # the duplicate mac/ip checks can be disabled.
+ if not check_for_duplicate_netinfo:
+ return
+
+ if isinstance(ref, item_system.System):
+ for (name, intf) in ref.interfaces.iteritems():
+ match_ip = []
+ match_mac = []
+ input_mac = intf["mac_address"]
+ input_ip = intf["ip_address"]
+ if not self.api.settings().allow_duplicate_macs and input_mac is not None and input_mac != "":
+ match_mac = self.api.find_system(mac_address=input_mac,return_list=True)
+ if not self.api.settings().allow_duplicate_ips and input_ip is not None and input_ip != "":
+ match_ip = self.api.find_system(ip_address=input_ip,return_list=True)
+ # it's ok to conflict with your own net info.
+
+ for x in match_mac:
+ if x.name != ref.name:
+ raise CX(_("Can't save system %s. The MAC address (%s) is already used by system %s (%s)") % (ref.name, intf["mac_address"], x.name, name))
+ for x in match_ip:
+ if x.name != ref.name:
+ raise CX(_("Can't save system %s. The IP address (%s) is already used by system %s (%s)") % (ref.name, intf["ip_address"], x.name, name))
+
def printable(self):
"""
Creates a printable representation of the collection suitable
diff --git a/cobbler/collection_distros.py b/cobbler/collection_distros.py
index f78dd64..5857b9a 100644
--- a/cobbler/collection_distros.py
+++ b/cobbler/collection_distros.py
@@ -18,7 +18,7 @@ import collection
import item_distro as distro
from cexceptions import *
import action_litesync
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Distros(collection.Collection):
diff --git a/cobbler/collection_profiles.py b/cobbler/collection_profiles.py
index f00a8dc..139c94e 100644
--- a/cobbler/collection_profiles.py
+++ b/cobbler/collection_profiles.py
@@ -20,7 +20,7 @@ import utils
import collection
from cexceptions import *
import action_litesync
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
#--------------------------------------------
diff --git a/cobbler/collection_repos.py b/cobbler/collection_repos.py
index 44bef2a..91e0667 100644
--- a/cobbler/collection_repos.py
+++ b/cobbler/collection_repos.py
@@ -18,7 +18,7 @@ import item_repo as repo
import utils
import collection
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
TESTMODE = False
diff --git a/cobbler/collection_systems.py b/cobbler/collection_systems.py
index 140a981..8a967be 100644
--- a/cobbler/collection_systems.py
+++ b/cobbler/collection_systems.py
@@ -18,7 +18,7 @@ import utils
import collection
from cexceptions import *
import action_litesync
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
#--------------------------------------------
diff --git a/cobbler/commands.py b/cobbler/commands.py
index d97a6e1..705249d 100644
--- a/cobbler/commands.py
+++ b/cobbler/commands.py
@@ -14,7 +14,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import optparse
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import sys
HELP_FORMAT = "%-20s%s"
@@ -195,6 +195,14 @@ class CobblerFunction:
Boilerplate for objects that offer add/edit/delete/remove/copy functionality.
"""
+ if "dumpvars" in self.args:
+ if not self.options.name:
+ raise CX(_("name is required"))
+ obj = collect_fn().find(self.options.name)
+ if obj is None:
+ raise CX(_("object not found"))
+ return obj
+
if "remove" in self.args:
recursive = False
# only applies to distros/profiles and is not supported elsewhere
@@ -219,11 +227,12 @@ class CobblerFunction:
self.reporting_list_names2(collect_fn(),self.options.name)
return None
+ if not self.options.name:
+ raise CX(_("name is required"))
+
if "add" in self.args:
obj = new_fn(is_subobject=subobject)
else:
- if not self.options.name:
- raise CX(_("name is required"))
if "delete" in self.args:
collect_fn().remove(self.options.name, with_delete=True)
return None
@@ -241,6 +250,14 @@ class CobblerFunction:
Boilerplate for objects that offer add/edit/delete/remove/copy functionality.
"""
+ if "dumpvars" in self.args:
+ print obj.dump_vars(True)
+ return True
+
+ clobber = False
+ if "add" in self.args:
+ clobber = options.clobber
+
if "copy" in self.args: # or "rename" in self.args:
if self.options.newname:
obj = obj.make_clone()
@@ -251,10 +268,45 @@ class CobblerFunction:
opt_sync = not options.nosync
opt_triggers = not options.notriggers
+ # ** WARNING: COMPLICATED **
+ # what operation we call depends on what type of object we are editing
+ # and what the operation is. The details behind this is that the
+ # add operation has special semantics around adding objects that might
+ # clobber other objects, and we don't want that to happen. Edit
+ # does not have to check for named clobbering but still needs
+ # to check for IP/MAC clobbering in some scenarios (FIXME).
+ # this is all enforced by collections.py though we need to make
+ # the apppropriate call to add to invoke the safety code in the right
+ # places -- and not in places where the safety code will generate
+ # errors under legit circumstances.
+
if not ("rename" in self.args):
- rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers)
+ if "add" in self.args:
+ if obj.COLLECTION_TYPE == "system":
+ # duplicate names and netinfo are both bad.
+ if not clobber:
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=True)
+ else:
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=False, check_for_duplicate_netinfo=True)
+ else:
+ # duplicate names are bad
+ if not clobber:
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=False)
+ else:
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=False, check_for_duplicate_netinfo=False)
+ else:
+ check_dup = False
+ if not "copy" in self.args:
+ check_dup = True
+ # FIXME: this ensures duplicate prevention on copy, but not
+ # rename?
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_netinfo=check_dup)
+
else:
+ # we are renaming here, so duplicate netinfo checks also
+ # need to be made.(FIXME)
rc = collect_fn().rename(obj, self.options.newname)
+
return rc
def reporting_sorter(self, a, b):
diff --git a/cobbler/config.py b/cobbler/config.py
index 258b241..4a1b9a4 100644
--- a/cobbler/config.py
+++ b/cobbler/config.py
@@ -29,7 +29,7 @@ import modules.serializer_yaml as serializer_yaml
import settings
import serializer
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Config:
diff --git a/cobbler/demo_connect.py b/cobbler/demo_connect.py
index 0fa058b..94fa390 100644
--- a/cobbler/demo_connect.py
+++ b/cobbler/demo_connect.py
@@ -11,12 +11,25 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
-from server.xmlrpcclient import ServerProxy
+from xmlrpclib import ServerProxy
+import optparse
if __name__ == "__main__":
- sp = ServerProxy("httpu:///var/lib/cobbler/sock")
- print sp.login("<system>","")
-
+ p = optparse.OptionParser()
+ p.add_option("-u","--user",dest="user",default="test")
+ p.add_option("-p","--pass",dest="password",default="test")
+
+ # NOTE: if you've changed your xmlrpc_rw port or
+ # disabled xmlrpc_rw this test probably won't work
+
+ sp = ServerProxy("http://127.0.0.1:25152")
+ (options, args) = p.parse_args()
+ print "- trying to login with user=%s" % options.user
+ token = sp.login(options.user,options.password)
+ print "- token: %s" % token
+ print "- authenticated ok, now seeing if user is authorized"
+ check = sp.check_access(token,"imaginary_method_name")
+ print "- access ok? %s" % check
diff --git a/cobbler/item.py b/cobbler/item.py
index fd79012..992a18d 100644
--- a/cobbler/item.py
+++ b/cobbler/item.py
@@ -16,7 +16,8 @@ import exceptions
import serializable
import utils
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
+import pprint
class Item(serializable.Serializable):
@@ -51,7 +52,6 @@ class Item(serializable.Serializable):
self.clear(is_subobject) # reset behavior differs for inheritance cases
self.parent = '' # all objects by default are not subobjects
self.children = {} # caching for performance reasons, not serialized
-
self.log_func = self.config.api.log
def clear(self):
@@ -122,6 +122,16 @@ class Item(serializable.Serializable):
self.name = name
return True
+ def set_owners(self,data):
+ """
+ The owners field is a comment unless using an authz module that pays attention to it,
+ like authz_ownership, which ships with Cobbler but is off by default. Consult the Wiki
+ docs for more info on CustomizableAuthorization.
+ """
+ owners = utils.input_string_or_list(data)
+ self.owners = owners
+ return True
+
def set_kernel_options(self,options):
"""
Kernel options are a space delimited list,
@@ -189,8 +199,9 @@ class Item(serializable.Serializable):
if key in [ "mac_address", "ip_address", "subnet", "gateway", "virt_bridge", "dhcp_tag", "hostname" ]:
key_found_already = True
for (name, interface) in data["interfaces"].iteritems():
- if interface[key].lower() == value.lower():
- return True
+ if value is not None:
+ if interface[key].lower() == value.lower():
+ return True
if not data.has_key(key):
if not key_found_already:
@@ -202,3 +213,10 @@ class Item(serializable.Serializable):
else:
return False
+ def dump_vars(self,data,format=True):
+ raw = utils.blender(self.config.api, False, self)
+ if format:
+ return pprint.pformat(raw)
+ else:
+ return raw
+
diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py
index f52ad0b..1a4c296 100644
--- a/cobbler/item_distro.py
+++ b/cobbler/item_distro.py
@@ -20,7 +20,7 @@ import weakref
import os
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Distro(item.Item):
@@ -32,6 +32,7 @@ class Distro(item.Item):
Reset this object.
"""
self.name = None
+ self.owners = self.settings.default_ownership
self.kernel = (None, '<<inherit>>')[is_subobject]
self.initrd = (None, '<<inherit>>')[is_subobject]
self.kernel_options = ({}, '<<inherit>>')[is_subobject]
@@ -60,6 +61,7 @@ class Distro(item.Item):
"""
self.parent = self.load_item(seed_data,'parent')
self.name = self.load_item(seed_data,'name')
+ self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership)
self.kernel = self.load_item(seed_data,'kernel')
self.initrd = self.load_item(seed_data,'initrd')
self.kernel_options = self.load_item(seed_data,'kernel_options')
@@ -75,6 +77,8 @@ class Distro(item.Item):
if self.ks_meta != "<<inherit>>" and type(self.ks_meta) != dict:
self.set_ksmeta(self.ks_meta)
+ self.set_owners(self.owners)
+
return self
def set_kernel(self,kernel):
@@ -165,7 +169,8 @@ class Distro(item.Item):
'breed' : self.breed,
'source_repos' : self.source_repos,
'parent' : self.parent,
- 'depth' : self.depth
+ 'depth' : self.depth,
+ 'owners' : self.owners
}
def printable(self):
@@ -175,12 +180,13 @@ class Distro(item.Item):
kstr = utils.find_kernel(self.kernel)
istr = utils.find_initrd(self.initrd)
buf = _("distro : %s\n") % self.name
- buf = buf + _("kernel : %s\n") % kstr
+ buf = buf + _("breed : %s\n") % self.breed
+ buf = buf + _("architecture : %s\n") % self.arch
buf = buf + _("initrd : %s\n") % istr
+ buf = buf + _("kernel : %s\n") % kstr
buf = buf + _("kernel options : %s\n") % self.kernel_options
- buf = buf + _("architecture : %s\n") % self.arch
buf = buf + _("ks metadata : %s\n") % self.ks_meta
- buf = buf + _("breed : %s\n") % self.breed
+ buf = buf + _("owners : %s\n") % self.owners
return buf
def remote_methods(self):
@@ -191,7 +197,8 @@ class Distro(item.Item):
'kopts' : self.set_kernel_options,
'arch' : self.set_arch,
'ksmeta' : self.set_ksmeta,
- 'breed' : self.set_breed
+ 'breed' : self.set_breed,
+ 'owners' : self.set_owners
}
diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py
index f229d4c..0e16f46 100644
--- a/cobbler/item_profile.py
+++ b/cobbler/item_profile.py
@@ -16,7 +16,7 @@ import utils
import item
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Profile(item.Item):
@@ -34,6 +34,7 @@ class Profile(item.Item):
Reset this object.
"""
self.name = None
+ self.owners = self.settings.default_ownership
self.distro = (None, '<<inherit>>')[is_subobject]
self.kickstart = (self.settings.default_kickstart , '<<inherit>>')[is_subobject]
self.kernel_options = ({}, '<<inherit>>')[is_subobject]
@@ -57,6 +58,7 @@ class Profile(item.Item):
self.parent = self.load_item(seed_data,'parent','')
self.name = self.load_item(seed_data,'name')
+ self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership)
self.distro = self.load_item(seed_data,'distro')
self.kickstart = self.load_item(seed_data,'kickstart')
self.kernel_options = self.load_item(seed_data,'kernel_options')
@@ -68,7 +70,10 @@ class Profile(item.Item):
# backwards compatibility
if type(self.repos) != list:
- self.set_repos(self.repos)
+ # ensure we are formatted correctly though if some repo
+ # defs don't exist on this side, don't fail as we need
+ # to convert everything -- cobbler check can report it
+ self.set_repos(self.repos,bypass_check=True)
self.set_parent(self.parent)
# virt specific
@@ -85,7 +90,9 @@ class Profile(item.Item):
if self.ks_meta != "<<inherit>>" and type(self.ks_meta) != dict:
self.set_ksmeta(self.ks_meta)
if self.repos != "<<inherit>>" and type(self.ks_meta) != list:
- self.set_repos(self.repos)
+ self.set_repos(self.repos,bypass_check=True)
+
+ self.set_owners(self.owners)
return self
@@ -134,46 +141,7 @@ class Profile(item.Item):
self.server = server
return True
- def set_repos(self,repos):
-
- # WARNING: hack
- repos = utils.fix_mod_python_select_submission(repos)
-
-
- # allow the magic inherit string to persist
- if repos == "<<inherit>>":
- # FIXME: this is not inheritable in the WebUI presently ?
- self.repos = "<<inherit>>"
- return
-
- # store as an array regardless of input type
- if repos is None:
- repolist = []
- elif type(repos) != list:
- # allow backwards compatibility support of string input
- repolist = repos.split(None)
- else:
- repolist = repos
-
-
- # make sure there are no empty strings
- try:
- repolist.remove('')
- except:
- pass
-
- self.repos = []
-
- # if any repos don't exist, fail the operation
- ok = True
- for r in repolist:
- if self.config.repos().find(name=r) is not None:
- self.repos.append(r)
- else:
- print _("warning: repository not found: %s" % r)
-
- return True
-
+
def set_kickstart(self,kickstart):
"""
Sets the kickstart. This must be a NFS, HTTP, or FTP URL.
@@ -188,108 +156,27 @@ class Profile(item.Item):
raise CX(_("kickstart not found"))
def set_virt_cpus(self,num):
- """
- For Virt only. Set the number of virtual CPUs to give to the
- virtual machine. This is fed to virtinst RAW, so cobbler
- will not yelp if you try to feed it 9999 CPUs. No formatting
- like 9,999 please :)
- """
- if num == "<<inherit>>":
- self.virt_cpus = "<<inherit>>"
- return True
-
- try:
- num = int(str(num))
- except:
- raise CX(_("invalid number of virtual CPUs"))
-
- self.virt_cpus = num
- return True
+ return utils.set_virt_cpus(self,num)
def set_virt_file_size(self,num):
- """
- For Virt only.
- Specifies the size of the virt image in gigabytes.
- Older versions of koan (x<0.6.3) interpret 0 as "don't care"
- Newer versions (x>=0.6.4) interpret 0 as "no disks"
- """
- # num is a non-negative integer (0 means default)
- # can also be a comma seperated list -- for usage with multiple disks
-
- if num == "<<inherit>>":
- self.virt_file_size = "<<inherit>>"
- return True
-
- if type(num) == str and num.find(",") != -1:
- tokens = num.split(",")
- for t in tokens:
- # hack to run validation on each
- self.set_virt_file_size(t)
- # if no exceptions raised, good enough
- self.virt_file_size = num
- return True
-
- try:
- inum = int(num)
- if inum != float(num):
- return CX(_("invalid virt file size"))
- if inum >= 0:
- self.virt_file_size = inum
- return True
- raise CX(_("invalid virt file size"))
- except:
- raise CX(_("invalid virt file size"))
-
+ return utils.set_virt_file_size(self,num)
+
def set_virt_ram(self,num):
- """
- For Virt only.
- Specifies the size of the Virt RAM in MB.
- 0 tells Koan to just choose a reasonable default.
- """
-
- if num == "<<inherit>>":
- self.virt_ram = "<<inherit>>"
- return True
-
- # num is a non-negative integer (0 means default)
- try:
- inum = int(num)
- if inum != float(num):
- return CX(_("invalid virt ram size"))
- if inum >= 0:
- self.virt_ram = inum
- return True
- return CX(_("invalid virt ram size"))
- except:
- return CX(_("invalid virt ram size"))
+ return utils.set_virt_ram(self,num)
def set_virt_type(self,vtype):
- """
- Virtualization preference, can be overridden by koan.
- """
-
- if vtype == "<<inherit>>":
- self.virt_type == "<<inherit>>"
- return True
-
- if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]:
- raise CX(_("invalid virt type"))
- self.virt_type = vtype
- return True
+ return utils.set_virt_type(self,vtype)
def set_virt_bridge(self,vbridge):
- """
- The default bridge for all virtual interfaces under this profile.
- """
self.virt_bridge = vbridge
return True
def set_virt_path(self,path):
- """
- Virtual storage location suggestion, can be overriden by koan.
- """
- self.virt_path = path
- return True
+ return utils.set_virt_path(self,path)
+
+ def set_repos(self,repos,bypass_check=False):
+ return utils.set_repos(self,repos,bypass_check)
+
def get_parent(self):
"""
@@ -328,6 +215,7 @@ class Profile(item.Item):
"""
return {
'name' : self.name,
+ 'owners' : self.owners,
'distro' : self.distro,
'kickstart' : self.kickstart,
'kernel_options' : self.kernel_options,
@@ -342,7 +230,8 @@ class Profile(item.Item):
'virt_type' : self.virt_type,
'virt_path' : self.virt_path,
'dhcp_tag' : self.dhcp_tag,
- 'server' : self.server
+ 'server' : self.server,
+
}
def printable(self):
@@ -354,20 +243,22 @@ class Profile(item.Item):
buf = buf + _("parent : %s\n") % self.parent
else:
buf = buf + _("distro : %s\n") % self.distro
- buf = buf + _("kickstart : %s\n") % self.kickstart
+ buf = buf + _("dhcp tag : %s\n") % self.dhcp_tag
buf = buf + _("kernel options : %s\n") % self.kernel_options
+ buf = buf + _("kickstart : %s\n") % self.kickstart
buf = buf + _("ks metadata : %s\n") % self.ks_meta
+ buf = buf + _("owners : %s\n") % self.owners
+ buf = buf + _("repos : %s\n") % self.repos
+ buf = buf + _("server : %s\n") % self.server
+ buf = buf + _("virt bridge : %s\n") % self.virt_bridge
+ buf = buf + _("virt cpus : %s\n") % self.virt_cpus
buf = buf + _("virt file size : %s\n") % self.virt_file_size
+ buf = buf + _("virt path : %s\n") % self.virt_path
buf = buf + _("virt ram : %s\n") % self.virt_ram
buf = buf + _("virt type : %s\n") % self.virt_type
- buf = buf + _("virt path : %s\n") % self.virt_path
- buf = buf + _("virt bridge : %s\n") % self.virt_bridge
- buf = buf + _("virt cpus : %s\n") % self.virt_cpus
- buf = buf + _("repos : %s\n") % self.repos
- buf = buf + _("dhcp tag : %s\n") % self.dhcp_tag
- buf = buf + _("server : %s\n") % self.server
return buf
+
def remote_methods(self):
return {
'name' : self.set_name,
@@ -385,6 +276,7 @@ class Profile(item.Item):
'virt-bridge' : self.set_virt_bridge,
'virt-cpus' : self.set_virt_cpus,
'dhcp-tag' : self.set_dhcp_tag,
- 'server' : self.set_server
+ 'server' : self.set_server,
+ 'owners' : self.set_owners
}
diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py
index ca9e94f..3b9b839 100644
--- a/cobbler/item_repo.py
+++ b/cobbler/item_repo.py
@@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import utils
import item
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Repo(item.Item):
@@ -31,6 +31,7 @@ class Repo(item.Item):
def clear(self,is_subobject=False):
self.parent = None
self.name = None
+ # FIXME: subobject code does not really make sense for repos
self.mirror = (None, '<<inherit>>')[is_subobject]
self.keep_updated = ('y', '<<inherit>>')[is_subobject]
self.priority = (99, '<<inherit>>')[is_subobject]
@@ -39,6 +40,8 @@ class Repo(item.Item):
self.depth = 2 # arbitrary, as not really apart of the graph
self.arch = "" # use default arch
self.yumopts = {}
+ self.owners = self.settings.default_ownership
+ self.mirror_locally = 1
def from_datastruct(self,seed_data):
self.parent = self.load_item(seed_data, 'parent')
@@ -51,9 +54,12 @@ class Repo(item.Item):
self.arch = self.load_item(seed_data, 'arch')
self.depth = self.load_item(seed_data, 'depth', 2)
self.yumopts = self.load_item(seed_data, 'yumopts', {})
+ self.owners = self.load_item(seed_data, 'owners', self.settings.default_ownership)
+ self.mirror_locally = self.load_item(seed_data, 'mirror_locally', '1')
- # force this to be saved as a boolean
+ # coerce types from input file
self.set_keep_updated(self.keep_updated)
+ self.set_owners(self.owners)
return self
@@ -63,6 +69,13 @@ class Repo(item.Item):
reposync/repotrack integration over HTTP might come later.
"""
self.mirror = mirror
+ if self.arch is None or self.arch == "":
+ if mirror.find("x86_64") != -1:
+ self.set_arch("x86_64")
+ elif mirror.find("x86") != -1 or mirror.find("i386") != -1:
+ self.set_arch("i386")
+ elif mirror.find("ia64") != -1:
+ self.set_arch("ia64")
return True
def set_keep_updated(self,keep_updated):
@@ -153,7 +166,9 @@ class Repo(item.Item):
def to_datastruct(self):
return {
'name' : self.name,
+ 'owners' : self.owners,
'mirror' : self.mirror,
+ 'mirror_locally' : self.mirror_locally,
'keep_updated' : self.keep_updated,
'priority' : self.priority,
'rpm_list' : self.rpm_list,
@@ -164,14 +179,24 @@ class Repo(item.Item):
'yumopts' : self.yumopts
}
+ def set_mirror_locally(self,value):
+ value = str(value).lower()
+ if value in [ "yes", "y", "1", "on", "true" ]:
+ self.mirror_locally = 1
+ else:
+ self.mirror_locally = 0
+ return True
+
def printable(self):
buf = _("repo : %s\n") % self.name
- buf = buf + _("mirror : %s\n") % self.mirror
+ buf = buf + _("arch : %s\n") % self.arch
+ buf = buf + _("createrepo_flags : %s\n") % self.createrepo_flags
buf = buf + _("keep updated : %s\n") % self.keep_updated
+ buf = buf + _("mirror : %s\n") % self.mirror
+ buf = buf + _("mirror locally : %s\n") % self.mirror_locally
+ buf = buf + _("owners : %s\n") % self.owners
buf = buf + _("priority : %s\n") % self.priority
buf = buf + _("rpm list : %s\n") % self.rpm_list
- buf = buf + _("createrepo_flags : %s\n") % self.createrepo_flags
- buf = buf + _("arch : %s\n") % self.arch
buf = buf + _("yum options : %s\n") % self.yumopts
return buf
@@ -202,6 +227,8 @@ class Repo(item.Item):
'priority' : self.set_priority,
'rpm-list' : self.set_rpm_list,
'createrepo-flags' : self.set_createrepo_flags,
- 'yumopts' : self.set_yumopts
+ 'yumopts' : self.set_yumopts,
+ 'owners' : self.set_owners,
+ 'mirror-locally' : self.set_mirror_locally
}
diff --git a/cobbler/item_system.py b/cobbler/item_system.py
index dadd8d1..09f169d 100644
--- a/cobbler/item_system.py
+++ b/cobbler/item_system.py
@@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import utils
import item
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class System(item.Item):
@@ -30,6 +30,7 @@ class System(item.Item):
def clear(self,is_subobject=False):
self.name = None
+ self.owners = self.settings.default_ownership
self.profile = None
self.kernel_options = {}
self.ks_meta = {}
@@ -37,9 +38,15 @@ class System(item.Item):
self.netboot_enabled = True
self.depth = 2
self.kickstart = "<<inherit>>" # use value in profile
+ self.server = "<<inherit>>" # "" (or settings)
self.virt_path = "<<inherit>>" # ""
self.virt_type = "<<inherit>>" # ""
- self.server = "<<inherit>>" # "" (or settings)
+ self.virt_cpus = "<<inherit>>" # ""
+ self.virt_file_size = "<<inherit>>" # ""
+ self.virt_ram = "<<inherit>>" # ""
+ self.virt_type = "<<inherit>>" # ""
+ self.virt_path = "<<inherit>>" # ""
+ self.virt_bridge = "<<inherit>>" # ""
def delete_interface(self,name):
"""
@@ -80,16 +87,25 @@ class System(item.Item):
self.parent = self.load_item(seed_data, 'parent')
self.name = self.load_item(seed_data, 'name')
+ self.owners = self.load_item(seed_data, 'owners', self.settings.default_ownership)
self.profile = self.load_item(seed_data, 'profile')
self.kernel_options = self.load_item(seed_data, 'kernel_options', {})
self.ks_meta = self.load_item(seed_data, 'ks_meta', {})
self.depth = self.load_item(seed_data, 'depth', 2)
self.kickstart = self.load_item(seed_data, 'kickstart', '<<inherit>>')
- self.virt_path = self.load_item(seed_data, 'virt_path', '<<inherit>>')
- self.virt_type = self.load_item(seed_data, 'virt_type', '<<inherit>>')
self.netboot_enabled = self.load_item(seed_data, 'netboot_enabled', True)
self.server = self.load_item(seed_data, 'server', '<<inherit>>')
+ # virt specific
+ self.virt_path = self.load_item(seed_data, 'virt_path', '<<inherit>>')
+ self.virt_type = self.load_item(seed_data, 'virt_type', '<<inherit>>')
+ self.virt_ram = self.load_item(seed_data,'virt_ram','<<inherit>>')
+ self.virt_file_size = self.load_item(seed_data,'virt_file_size','<<inherit>>')
+ self.virt_path = self.load_item(seed_data,'virt_path','<<inherit>>')
+ self.virt_type = self.load_item(seed_data,'virt_type','<<inherit>>')
+ self.virt_bridge = self.load_item(seed_data,'virt_bridge','<<inherit>>')
+ self.virt_cpus = self.load_item(seed_data,'virt_cpus','<<inherit>>')
+
# backwards compat, these settings are now part of the interfaces data structure
# and will contain data only in upgrade scenarios.
@@ -126,8 +142,9 @@ class System(item.Item):
# explicitly re-call the set_name function to possibily populate MAC/IP.
self.set_name(self.name)
- # coerce this into a boolean
+ # coerce types from input file
self.set_netboot_enabled(self.netboot_enabled)
+ self.set_owners(self.owners)
return self
@@ -154,7 +171,7 @@ class System(item.Item):
raise CX(_("name must be a string"))
for x in name:
if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] :
- raise CX(_("invalid characters in name"))
+ raise CX(_("invalid characters in name: %s") % x)
if utils.is_mac(name):
if intf["mac_address"] == "":
@@ -212,11 +229,12 @@ class System(item.Item):
"""
if self.name == "default":
return True
- mac = self.get_mac_address(interface)
- ip = self.get_ip_address(interface)
- if mac is None and ip is None:
- return False
- return True
+ for (name,x) in self.interfaces.iteritems():
+ mac = x.get("mac_address",None)
+ ip = x.get("ip_address",None)
+ if mac is not None or ip is not None:
+ return True
+ return False
def set_dhcp_tag(self,dhcp_tag,interface="intf0"):
intf = self.__get_interface(interface)
@@ -231,7 +249,7 @@ class System(item.Item):
def set_ip_address(self,address,interface="intf0"):
"""
Assign a IP or hostname in DHCP when this MAC boots.
- Only works if manage_dhcp is set in /var/lib/cobbler/settings
+ Only works if manage_dhcp is set in /etc/cobbler/settings
"""
intf = self.__get_interface(interface)
if address == "" or utils.is_ip(address):
@@ -273,21 +291,20 @@ class System(item.Item):
return True
raise CX(_("invalid profile name"))
- def set_virt_path(self,path):
- """
- Virtual storage location suggestion, can be overriden by koan.
- """
- self.virt_path = path
- return True
+ def set_virt_cpus(self,num):
+ return utils.set_virt_cpus(self,num)
+
+ def set_virt_file_size(self,num):
+ return utils.set_virt_file_size(self,num)
+
+ def set_virt_ram(self,num):
+ return utils.set_virt_ram(self,num)
def set_virt_type(self,vtype):
- """
- Virtualization preference, can be overridden by koan.
- """
- if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]:
- raise CX(_("invalid virt type"))
- self.virt_type = vtype
- return True
+ return utils.set_virt_type(self,vtype)
+
+ def set_virt_path(self,path):
+ return utils.set_virt_path(self,path)
def set_netboot_enabled(self,netboot_enabled):
"""
@@ -336,6 +353,9 @@ class System(item.Item):
abstraction layer -- assigning systems to defined and repeatable
roles.
"""
+ if kickstart is None or kickstart == "" or kickstart == "delete":
+ self.kickstart = "<<inherit>>"
+ return True
if utils.find_kickstart(kickstart):
self.kickstart = kickstart
return True
@@ -344,32 +364,44 @@ class System(item.Item):
def to_datastruct(self):
return {
- 'name' : self.name,
- 'profile' : self.profile,
- 'kernel_options' : self.kernel_options,
- 'ks_meta' : self.ks_meta,
- 'netboot_enabled' : self.netboot_enabled,
- 'parent' : self.parent,
- 'depth' : self.depth,
- 'kickstart' : self.kickstart,
- 'virt_type' : self.virt_type,
- 'virt_path' : self.virt_path,
- 'interfaces' : self.interfaces,
- 'server' : self.server
+ 'name' : self.name,
+ 'kernel_options' : self.kernel_options,
+ 'depth' : self.depth,
+ 'interfaces' : self.interfaces,
+ 'ks_meta' : self.ks_meta,
+ 'kickstart' : self.kickstart,
+ 'netboot_enabled' : self.netboot_enabled,
+ 'owners' : self.owners,
+ 'parent' : self.parent,
+ 'profile' : self.profile,
+ 'server' : self.server,
+ 'virt_cpus' : self.virt_cpus,
+ 'virt_bridge' : self.virt_bridge,
+ 'virt_file_size' : self.virt_file_size,
+ 'virt_path' : self.virt_path,
+ 'virt_ram' : self.virt_ram,
+ 'virt_type' : self.virt_type
+
}
def printable(self):
buf = _("system : %s\n") % self.name
buf = buf + _("profile : %s\n") % self.profile
buf = buf + _("kernel options : %s\n") % self.kernel_options
+ buf = buf + _("kickstart : %s\n") % self.kickstart
buf = buf + _("ks metadata : %s\n") % self.ks_meta
buf = buf + _("netboot enabled? : %s\n") % self.netboot_enabled
- buf = buf + _("kickstart : %s\n") % self.kickstart
- buf = buf + _("virt type : %s\n") % self.virt_type
- buf = buf + _("virt path : %s\n") % self.virt_path
+ buf = buf + _("owners : %s\n") % self.owners
buf = buf + _("server : %s\n") % self.server
+ buf = buf + _("virt cpus : %s\n") % self.virt_cpus
+ buf = buf + _("virt file size : %s\n") % self.virt_file_size
+ buf = buf + _("virt path : %s\n") % self.virt_path
+ buf = buf + _("virt ram : %s\n") % self.virt_ram
+ buf = buf + _("virt type : %s\n") % self.virt_type
+
+
counter = 0
for (name,x) in self.interfaces.iteritems():
buf = buf + _("interface : %s\n") % (name)
@@ -414,6 +446,12 @@ class System(item.Item):
'virt-type' : self.set_virt_type,
'modify-interface' : self.modify_interface,
'delete-interface' : self.delete_interface,
- 'server' : self.set_server
+ 'virt-path' : self.set_virt_path,
+ 'virt-ram' : self.set_virt_ram,
+ 'virt-type' : self.set_virt_type,
+ 'virt-cpus' : self.set_virt_cpus,
+ 'virt-file-size' : self.set_virt_file_size,
+ 'server' : self.set_server,
+ 'owners' : self.set_owners
}
diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py
new file mode 100644
index 0000000..f302d00
--- /dev/null
+++ b/cobbler/kickgen.py
@@ -0,0 +1,279 @@
+"""
+Builds out filesystem trees/data based on the object tree.
+This is the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <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 os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+class KickGen:
+ """
+ Handles conversion of internal state to the tftpboot tree layout
+ """
+
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.systems = config.systems()
+ self.settings = config.settings()
+ self.repos = config.repos()
+ self.templar = templar.Templar(config)
+
+ def generate_kickstart_for_profile(self,g):
+
+ g = self.api.find_profile(name=g)
+ if g is None:
+ return "# profile not found"
+
+ distro = g.get_conceptual_parent()
+ meta = utils.blender(self.api, False, g)
+ if distro is None:
+ raise CX(_("profile %(profile)s references missing distro %(distro)s") % { "profile" : g.name, "distro" : g.distro })
+ kickstart_path = utils.find_kickstart(meta["kickstart"])
+ if kickstart_path is not None and os.path.exists(kickstart_path):
+ # the input is an *actual* file, hence we have to copy it
+ try:
+ meta = utils.blender(self.api, False, g)
+ ksmeta = meta["ks_meta"]
+ del meta["ks_meta"]
+ meta.update(ksmeta) # make available at top level
+ meta["yum_repo_stanza"] = self.generate_repo_stanza(g,True)
+ meta["yum_config_stanza"] = self.generate_config_stanza(g,True)
+ meta["kickstart_done"] = self.generate_kickstart_signal(0, g, None)
+ meta["kickstart_start"] = self.generate_kickstart_signal(1, g, None)
+ meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"])
+ kfile = open(kickstart_path)
+ data = self.templar.render(kfile, meta, None, g)
+ kfile.close()
+ return data
+ except:
+ utils.log_exc(self.api.logger)
+ return _("# Error while rendering kickstart file, see /var/log/cobbler/cobbler.log for details")
+
+ def generate_kickstart_signal(self, is_pre=0, profile=None, system=None):
+ """
+ Do things that we do at the start/end of kickstarts...
+ * start: signal the status watcher we're starting
+ * end: signal the status watcher we're done
+ * end: disable PXE if needed
+ * end: save the original kickstart file for debug
+ """
+
+ nopxe = "\nwget \"http://%s/cblr/svc/op/nopxe/system/%s\" -O /dev/null"
+ saveks = "\nwget \"http://%s/cblr/svc/op/ks/%s/%s\" -O /root/cobbler.ks"
+ runpost = "\nwget \"http://%s/cblr/svc/op/trig/mode/post/%s/%s\" -O /dev/null"
+ runpre = "\nwget \"http://%s/cblr/svc/op/trig/mode/pre/%s/%s\" -O /dev/null"
+
+ what = "profile"
+ blend_this = profile
+ if system:
+ what = "system"
+ blend_this = system
+
+ blended = utils.blender(self.api, False, blend_this)
+ kickstart = blended.get("kickstart",None)
+
+ buf = ""
+ srv = blended["http_server"]
+ if system is not None:
+ if not is_pre:
+ if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]:
+ buf = buf + nopxe % (srv, system.name)
+ if kickstart and os.path.exists(kickstart):
+ buf = buf + saveks % (srv, "system", system.name)
+ if self.settings.run_install_triggers:
+ buf = buf + runpost % (srv, what, system.name)
+ else:
+ if self.settings.run_install_triggers:
+ buf = buf + runpre % (srv, what, system.name)
+
+ else:
+ if not is_pre:
+ if kickstart and os.path.exists(kickstart):
+ buf = buf + saveks % (srv, "profile", profile.name)
+ if self.settings.run_install_triggers:
+ buf = buf + runpost % (srv, what, profile.name)
+ else:
+ if self.settings.run_install_triggers:
+ buf = buf + runpre % (srv, what, profile.name)
+
+ return buf
+
+ def generate_repo_stanza(self, obj, is_profile=True):
+
+ """
+ Automatically attaches yum repos to profiles/systems in kickstart files
+ that contain the magic $yum_repo_stanza variable.
+ """
+
+ buf = ""
+ blended = utils.blender(self.api, False, obj)
+ configs = self.get_repo_filenames(obj,is_profile)
+ repos = self.repos
+
+ # FIXME: this really should be dynamically generated as with the kickstarts
+ for c in configs:
+ name = c.split("/")[-1].replace(".repo","")
+ (is_core, baseurl) = self.analyze_repo_config(c)
+ for repo in repos:
+ if repo.name == name:
+ if not repo.yumopts.has_key('enabled') or repo.yumopts['enabled'] == '1':
+ if repo.mirror_locally:
+ buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl)
+ else:
+ buf = buf + "repo --name=%s --baseurl=%s\n" % (name, repo.mirror)
+
+ return buf
+
+ def analyze_repo_config(self, filename):
+ fd = open(filename)
+ data = fd.read()
+ lines = data.split("\n")
+ ret = False
+ baseurl = None
+ for line in lines:
+ if line.find("ks_mirror") != -1:
+ ret = True
+ if line.find("baseurl") != -1:
+ try:
+ first, baseurl = line.split("=",1)
+ except:
+ raise CX(_("error scanning repo: %s" % filename))
+ fd.close()
+ return (ret, baseurl)
+
+ def get_repo_baseurl(self, server, repo_name, is_repo_mirror=True):
+ """
+ Construct the URL to a repo definition.
+ """
+ if is_repo_mirror:
+ return "http://%s/cobbler/repo_mirror/%s" % (server, repo_name)
+ else:
+ return "http://%s/cobbler/ks_mirror/config/%s" % (server, repo_name)
+
+ def get_repo_filenames(self, obj, is_profile=True):
+ """
+ For a given object, return the paths to repo configuration templates
+ that will be used to generate per-object repo configuration files and
+ baseurls
+ """
+
+ blended = utils.blender(self.api, False, obj)
+ urlseg = self.get_repo_segname(is_profile)
+
+ topdir = "%s/%s/%s/*.repo" % (self.settings.webdir, urlseg, blended["name"])
+ files = glob.glob(topdir)
+ return files
+
+
+ def get_repo_segname(self, is_profile):
+ if is_profile:
+ return "repos_profile"
+ else:
+ return "repos_system"
+
+
+ def generate_config_stanza(self, obj, is_profile=True):
+
+ """
+ Add in automatic to configure /etc/yum.repos.d on the remote system
+ if the kickstart file contains the magic $yum_config_stanza.
+ """
+
+ if not self.settings.yum_post_install_mirror:
+ return ""
+
+ urlseg = self.get_repo_segname(is_profile)
+
+ distro = obj.get_conceptual_parent()
+ if not is_profile:
+ distro = distro.get_conceptual_parent()
+
+ blended = utils.blender(self.api, False, obj)
+ configs = self.get_repo_filenames(obj, is_profile)
+ buf = ""
+
+ # for each kickstart template we have rendered ...
+ for c in configs:
+
+ name = c.split("/")[-1].replace(".repo","")
+ # add the line to create the yum config file on the target box
+ conf = self.get_repo_config_file(blended["http_server"],urlseg,blended["name"],name)
+ buf = buf + "wget \"%s\" --output-document=/etc/yum.repos.d/%s.repo\n" % (conf, name)
+
+ return buf
+
+ def get_repo_config_file(self,server,urlseg,obj_name,repo_name):
+ """
+ Construct the URL to a repo config file that is usable in kickstart
+ for use with yum. This is different than the templates cobbler reposync
+ creates, as this file will allow the server to migrate and have different
+ variables for different subnets/profiles/etc.
+ """
+ return "http://%s/cblr/%s/%s/%s.repo" % (server,urlseg,obj_name,repo_name)
+
+ def generate_kickstart_for_system(self,s):
+
+
+ s = self.api.find_system(name=s)
+ if s is None:
+ return "# system not found"
+
+ profile = s.get_conceptual_parent()
+ if profile is None:
+ raise CX(_("system %(system)s references missing profile %(profile)s") % { "system" : s.name, "profile" : s.profile })
+ distro = profile.get_conceptual_parent()
+ meta = utils.blender(self.api, False, s)
+ kickstart_path = utils.find_kickstart(meta["kickstart"])
+ if kickstart_path and os.path.exists(kickstart_path):
+ try:
+ ksmeta = meta["ks_meta"]
+ del meta["ks_meta"]
+ meta.update(ksmeta) # make available at top level
+ meta["yum_repo_stanza"] = self.generate_repo_stanza(s, False)
+ meta["yum_config_stanza"] = self.generate_config_stanza(s, False)
+ meta["kickstart_done"] = self.generate_kickstart_signal(0, profile, s)
+ meta["kickstart_start"] = self.generate_kickstart_signal(1, profile, s)
+ meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"])
+ kfile = open(kickstart_path)
+ data = self.templar.render(kfile, meta, None, s)
+ kfile.close()
+ return data
+ except:
+ traceback.print_exc()
+ raise CX(_("Error templating file"))
+
diff --git a/cobbler/manage_ctrl.py b/cobbler/manage_ctrl.py
new file mode 100644
index 0000000..a01910a
--- /dev/null
+++ b/cobbler/manage_ctrl.py
@@ -0,0 +1,76 @@
+"""
+Builds out DHCP info
+This is the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <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 os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+import popen2
+from shlex import shlex
+
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+class ManageCtrl:
+ """
+ Handles conversion of internal state to the tftpboot tree layout
+ """
+
+ def __init__(self,config,verbose=False,dns=None,dhcp=None):
+ """
+ Constructor
+ """
+ self.verbose = verbose
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.systems = config.systems()
+ self.settings = config.settings()
+ self.repos = config.repos()
+ self.templar = templar.Templar(config)
+ self.dns = dns
+
+ def write_dhcp_lease(self,port,host,ip,mac):
+ return dhcp.write_dhcp_lease(port,host,ip,mac)
+
+ def remove_dhcp_lease(self,port,host):
+ return dhcp.remove_dhcp_lease(port,host)
+
+ def write_dhcp_file(self):
+ return dhcp.write_dhcp_file()
+
+ def regen_ethers(self):
+ return dhcp.regen_ethers()
+
+ def regen_hosts(self):
+ return dns.regen_hosts()
+
+ def write_dns_files(self):
+ return dns.write_bind_files()
diff --git a/cobbler/module_loader.py b/cobbler/module_loader.py
index b695a04..d584ce3 100644
--- a/cobbler/module_loader.py
+++ b/cobbler/module_loader.py
@@ -19,7 +19,7 @@ import distutils.sysconfig
import os
import sys
import glob
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import ConfigParser
MODULE_CACHE = {}
diff --git a/cobbler/modules/authn_configfile.py b/cobbler/modules/authn_configfile.py
index ffe87a7..be453be 100644
--- a/cobbler/modules/authn_configfile.py
+++ b/cobbler/modules/authn_configfile.py
@@ -17,7 +17,7 @@ import distutils.sysconfig
import ConfigParser
import sys
import os
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import md5
import traceback
diff --git a/cobbler/modules/authn_denyall.py b/cobbler/modules/authn_denyall.py
new file mode 100644
index 0000000..91e27d4
--- /dev/null
+++ b/cobbler/modules/authn_denyall.py
@@ -0,0 +1,43 @@
+"""
+Authentication module that denies everything.
+Used to disable the WebUI by default.
+
+Copyright 2007-2008, Red Hat, Inc
+Michael DeHaan <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)
+
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "authn"
+
+def authenticate(api_handle,username,password):
+ """
+ Validate a username/password combo, returning True/False
+
+ Thanks to http://trac.edgewall.org/ticket/845 for supplying
+ the algorithm info.
+ """
+
+ # debugging only (not safe to enable)
+ # api_handle.logger.debug("backend authenticate (%s,%s)" % (username,password))
+
+ return False
+
+
diff --git a/cobbler/modules/authn_kerberos.py b/cobbler/modules/authn_kerberos.py
deleted file mode 100644
index 7f85db6..0000000
--- a/cobbler/modules/authn_kerberos.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
-Authentication module that uses kerberos.
-
-Copyright 2007, Red Hat, Inc
-Michael DeHaan <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/authn_ldap.py b/cobbler/modules/authn_ldap.py
new file mode 100644
index 0000000..ff31750
--- /dev/null
+++ b/cobbler/modules/authn_ldap.py
@@ -0,0 +1,118 @@
+"""
+Authentication module that uses ldap
+Settings in /etc/cobbler/authn_ldap.conf
+Choice of authentication module is in /etc/cobbler/modules.conf
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import distutils.sysconfig
+import sys
+import os
+from utils import _
+import md5
+import traceback
+
+# we'll import this just a bit later
+# to keep it from being a requirement
+# import ldap
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import cexceptions
+import utils
+import api as cobbler_api
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+
+ return "authn"
+
+def authenticate(api_handle,username,password):
+ """
+ Validate an ldap bind, returning True/False
+ """
+
+ import ldap
+
+ server = api_handle.settings().ldap_server
+ basedn = api_handle.settings().ldap_base_dn
+ port = api_handle.settings().ldap_port
+ tls = api_handle.settings().ldap_tls
+ anon_bind = api_handle.settings().ldap_anonymous_bind
+ prefix = api_handle.settings().ldap_search_prefix
+
+ # form our ldap uri based on connection port
+ if port == '389':
+ uri = 'ldap://' + server
+ elif port == '636':
+ uri = 'ldaps://' + server
+ else:
+ uri = 'ldap://' + "%s:%s" % (server,port)
+
+ # connect to LDAP host
+ dir = ldap.initialize(uri)
+
+ # start_tls if tls is 'on', 'true' or 'yes'
+ # and we're not already using old-SSL
+ tls = str(tls).lower()
+ if port != '636':
+ if tls in [ "on", "true", "yes", "1" ]:
+ try:
+ dir.start_tls_s()
+ except:
+ traceback.print_exc()
+ return False
+
+ # if we're not allowed to search anonymously,
+ # grok the search bind settings and attempt to bind
+ anon_bind = str(anon_bind).lower()
+ if anon_bind not in [ "on", "true", "yes", "1" ]:
+ searchdn = api_handle.settings().ldap_search_bind_dn
+ searchpw = api_handle.settings().ldap_search_passwd
+
+ if searchdn == '' or searchpw == '':
+ raise "Missing search bind settings"
+
+ try:
+ dir.simple_bind_s(searchdn, searchpw)
+ except:
+ traceback.print_exc()
+ return False
+
+ # perform a subtree search in basedn to find the full dn of the user
+ # TODO: what if username is a CN? maybe it goes into the config file as well?
+ filter = prefix + username
+ result = dir.search_s(basedn, ldap.SCOPE_SUBTREE, filter, [])
+ if result:
+ for dn,entry in result:
+ # username _should_ be unique so we should only have one result
+ # ignore entry; we don't need it
+ pass
+ else:
+ return False
+
+ try:
+ # attempt to bind as the user
+ dir.simple_bind_s(dn,password)
+ dir.unbind()
+ return True
+ except:
+ # traceback.print_exc()
+ return False
+ # catch-all
+ return False
+
+if __name__ == "__main__":
+ api_handle = cobbler_api.BootAPI()
+ print authenticate(api_handle, "guest", "guest")
+
diff --git a/cobbler/modules/authn_passthru.py b/cobbler/modules/authn_passthru.py
new file mode 100644
index 0000000..ebbe79a
--- /dev/null
+++ b/cobbler/modules/authn_passthru.py
@@ -0,0 +1,49 @@
+"""
+Authentication module that defers to Apache and trusts
+what Apache trusts.
+
+Copyright 2008, Red Hat, Inc
+Michael DeHaan <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
+import os
+from utils import _
+import traceback
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import cexceptions
+import utils
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "authn"
+
+def authenticate(api_handle,username,password):
+ """
+ Validate a username/password combo, returning True/False
+ Uses cobbler_auth_helper
+ """
+
+ fd = open("/var/lib/cobbler/web.ss")
+ data = fd.read()
+ if password == data:
+ rc = 1
+ else:
+ rc = 0
+ fd.close()
+ return data
+
diff --git a/cobbler/modules/authn_testing.py b/cobbler/modules/authn_testing.py
new file mode 100644
index 0000000..cd74cdf
--- /dev/null
+++ b/cobbler/modules/authn_testing.py
@@ -0,0 +1,42 @@
+"""
+Authentication module that denies everything.
+Unsafe demo. Allows anyone in with testing/testing.
+
+Copyright 2007-2008, Red Hat, Inc
+Michael DeHaan <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)
+
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "authn"
+
+def authenticate(api_handle,username,password):
+ """
+ Validate a username/password combo, returning True/False
+
+ Thanks to http://trac.edgewall.org/ticket/845 for supplying
+ the algorithm info.
+ """
+
+ if username == "testing" and password == "testing":
+ return True
+ return False
+
+
diff --git a/cobbler/modules/authz_allowall.py b/cobbler/modules/authz_allowall.py
index 1b05630..10d4b17 100644
--- a/cobbler/modules/authz_allowall.py
+++ b/cobbler/modules/authz_allowall.py
@@ -16,7 +16,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import distutils.sysconfig
import ConfigParser
import sys
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
diff --git a/cobbler/modules/authz_configfile.py b/cobbler/modules/authz_configfile.py
new file mode 100644
index 0000000..84343e2
--- /dev/null
+++ b/cobbler/modules/authz_configfile.py
@@ -0,0 +1,64 @@
+"""
+Authorization module that allow users listed in
+/etc/cobbler/users.conf to be permitted to access resources.
+For instance, when using authz_ldap, you want to use authn_configfile,
+not authz_allowall, which will most likely NOT do what you want.
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+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 utils import _
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import cexceptions
+import utils
+
+CONFIG_FILE='/etc/cobbler/users.conf'
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "authz"
+
+def __parse_config():
+ if not os.path.exists(CONFIG_FILE):
+ return []
+ config = ConfigParser.SafeConfigParser()
+ config.read(CONFIG_FILE)
+ alldata = {}
+ groups = config.sections()
+ for g in groups:
+ alldata[str(g)] = {}
+ opts = config.options(g)
+ for o in opts:
+ alldata[g][o] = 1
+ return alldata
+
+
+def authorize(api_handle,user,resource,arg1=None,arg2=None):
+ """
+ Validate a user against a resource.
+ All users in the file are permitted by this module.
+ """
+
+ data = __parse_config()
+ for g in data:
+ if user in data[g]:
+ return 1
+ return 0
+
+if __name__ == "__main__":
+ print __parse_config()
diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py
new file mode 100644
index 0000000..fbd00f9
--- /dev/null
+++ b/cobbler/modules/authz_ownership.py
@@ -0,0 +1,178 @@
+"""
+Authorization module that allow users listed in
+/etc/cobbler/users.conf to be permitted to access resources, with
+the further restriction that cobbler objects can be edited to only
+allow certain users/groups to access those specific objects.
+
+Copyright 2008, Red Hat, Inc
+Michael DeHaan <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 cobbler.utils import _
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import cexceptions
+import utils
+
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "authz"
+
+def __parse_config():
+ etcfile='/etc/cobbler/users.conf'
+ if not os.path.exists(etcfile):
+ raise CX(_("/etc/cobbler/users.conf does not exist"))
+ config = ConfigParser.ConfigParser()
+ config.read(etcfile)
+ alldata = {}
+ sections = config.sections()
+ for g in sections:
+ alldata[str(g)] = {}
+ opts = config.options(g)
+ for o in opts:
+ alldata[g][o] = 1
+ return alldata
+
+def __authorize_kickstart(api_handle, user, user_groups, kickstart):
+ # the authorization rules for kickstart editing are a bit
+ # of a special case. Non-admin users can edit a kickstart
+ # only if all objects that depend on that kickstart are
+ # editable by the user in question.
+ #
+ # Example:
+ # if Pinky owns ProfileA
+ # and the Brain owns ProfileB
+ # and both profiles use the same kickstart template
+ # and neither Pinky nor the Brain is an admin
+ # neither is allowed to edit the kickstart template
+ # because they would make unwanted changes to each other
+ #
+ # In the above scenario the UI will explain the problem
+ # and ask that the user asks the admin to resolve it if required.
+ # NOTE: this function is only called by authorize so admin users are
+ # cleared before this function is called.
+
+ lst = api_handle.find_profile(kickstart=kickstart, return_list=True)
+ lst.extend(api_handle.find_system(kickstart=kickstart, return_list=True))
+ for obj in lst:
+ if not __is_user_allowed(obj, user, user_groups):
+ return 0
+ return 1
+
+def __is_user_allowed(obj, user, user_groups):
+ if obj.owners == []:
+ # no ownership restrictions, cleared
+ return 1
+ for allowed in obj.owners:
+ if user == allowed:
+ # user match
+ return 1
+ # else look for a group match
+ for group in user_groups:
+ if group == allowed and user in user_groups[group]:
+ return 1
+ return 0
+
+
+
+def authorize(api_handle,user,resource,arg1=None,arg2=None):
+ """
+ Validate a user against a resource.
+ All users in the file are permitted by this module.
+ """
+
+ # everybody can get read-only access to everything
+ # if they pass authorization, they don't have to be in users.conf
+ if resource is not None:
+ for x in [ "get", "read", "/cobbler/web" ]:
+ if resource.startswith(x):
+ return 1
+
+ user_groups = __parse_config()
+
+ # classify the type of operation
+ modify_operation = False
+ for criteria in ["save","remove","modify","write","edit"]:
+ if resource.find(criteria) != -1:
+ modify_operation = True
+
+ # FIXME: is everyone allowed to copy? I think so.
+ # FIXME: deal with the problem of deleted parents and promotion
+
+ found_user = False
+ for g in user_groups:
+ for x in user_groups[g]:
+ if x == user:
+ found_user = True
+ # if user is in the admin group, always authorize
+ # regardless of the ownership of the object.
+ if g == "admins" or g == "admin":
+ return 1
+ break
+
+ if not found_user:
+ # if the user isn't anywhere in the file, reject regardless
+ # they can still use read-only XMLRPC
+ return 0
+ if not modify_operation:
+ # sufficient to allow access for non save/remove ops to all
+ # users for now, may want to refine later.
+ return 1
+
+ # now we have a modify_operation op, so we must check ownership
+ # of the object. remove ops pass in arg1 as a string name,
+ # saves pass in actual objects, so we must treat them differently.
+ # kickstarts are even more special so we call those out to another
+ # function, rather than going through the rest of the code here.
+
+ if resource.find("kickstart") != -1:
+ return __authorize_kickstart(api_handle,user,user_groups,arg1)
+
+ obj = None
+ if resource.find("remove") != -1:
+ if resource == "remove_distro":
+ obj = api_handle.find_distro(arg1)
+ elif resource == "remove_profile":
+ obj = api_handle.find_profile(arg1)
+ elif resource == "remove_system":
+ obj = api_handle.find_system(arg1)
+ elif resource == "remove_repo":
+ obj = api_handle.find_system(arg1)
+ elif resource.find("save") != -1 or resource.find("modify") != -1:
+ obj = arg1
+
+ # if the object has no ownership data, allow access regardless
+ if obj.owners is None or obj.owners == []:
+ return 1
+
+ return __is_user_allowed(obj,user,user_groups)
+
+
+if __name__ == "__main__":
+ # real tests are contained in tests/tests.py
+ import api as cobbler_api
+ api = cobbler_api.BootAPI()
+ print __parse_config()
+ print authorize(api, "admin1", "sync")
+ d = api.find_distro("F9B-i386")
+ d.set_owners(["allowed"])
+ api.add_distro(d)
+ print authorize(api, "admin1", "save_distro", d)
+ print authorize(api, "basement2", "save_distro", d)
diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py
index 35f5a4b..b7a8fa7 100644
--- a/cobbler/modules/cli_distro.py
+++ b/cobbler/modules/cli_distro.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import commands
import cexceptions
@@ -33,13 +33,16 @@ class DistroFunction(commands.CobblerFunction):
return "distro"
def subcommands(self):
- return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ]
def add_options(self, p, args):
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--arch", dest="arch", help="ex: x86, x86_64, ia64")
p.add_option("--breed", dest="breed", help="ex: redhat, debian, suse")
+ if self.matches_args(args,["add"]):
+ p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true")
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--initrd", dest="initrd", help="absolute path to initrd.img (REQUIRED)")
p.add_option("--kernel", dest="kernel", help="absolute path to vmlinuz (REQUIRED)")
p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'")
@@ -47,12 +50,16 @@ class DistroFunction(commands.CobblerFunction):
p.add_option("--name", dest="name", help="ex: 'RHEL-5-i386' (REQUIRED)")
+
+
if self.matches_args(args,["copy","rename"]):
p.add_option("--newname", dest="newname", help="for copy/rename commands")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed")
- if not self.matches_args(args,["report","list"]):
+ if not self.matches_args(args,["dumpvars","report","list"]):
p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution")
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
+ p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module")
if self.matches_args(args,["remove"]):
p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects")
@@ -63,16 +70,19 @@ class DistroFunction(commands.CobblerFunction):
if obj is None:
return True
- if self.options.kernel:
- obj.set_kernel(self.options.kernel)
- if self.options.initrd:
- obj.set_initrd(self.options.initrd)
- if self.options.kopts:
- obj.set_kernel_options(self.options.kopts)
- if self.options.ksmeta:
- obj.set_ksmeta(self.options.ksmeta)
- if self.options.breed:
- obj.set_breed(self.options.breed)
+ if not "dumpvars" in self.args:
+ if self.options.kernel:
+ obj.set_kernel(self.options.kernel)
+ if self.options.initrd:
+ obj.set_initrd(self.options.initrd)
+ if self.options.kopts:
+ obj.set_kernel_options(self.options.kopts)
+ if self.options.ksmeta:
+ obj.set_ksmeta(self.options.ksmeta)
+ if self.options.breed:
+ obj.set_breed(self.options.breed)
+ if self.options.owners:
+ obj.set_owners(self.options.owners)
return self.object_manipulator_finish(obj, self.api.distros, self.options)
diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py
index ebbba47..c72b11d 100644
--- a/cobbler/modules/cli_misc.py
+++ b/cobbler/modules/cli_misc.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import commands
from cexceptions import *
HELP_FORMAT = commands.HELP_FORMAT
@@ -73,11 +73,12 @@ class ImportFunction(commands.CobblerFunction):
return "import"
def add_options(self, p, args):
+ p.add_option("--arch", dest="arch", help="explicitly specify the architecture being imported (RECOMENDED)")
p.add_option("--path", dest="mirror", help="local path or rsync location (REQUIRED)")
p.add_option("--mirror", dest="mirror_alt", help="alias for --path")
p.add_option("--name", dest="name", help="name, ex 'RHEL-5', (REQUIRED)")
- p.add_option("--available-as", dest="available_as", help="do not mirror, use this as install tree")
- p.add_option("--kickstart", dest="kickstart_file", help="use the kickstart file specified as the profile's kickstart file")
+ p.add_option("--available-as", dest="available_as", help="do not mirror, use this as install tree base")
+ p.add_option("--kickstart", dest="kickstart_file", help="use the kickstart file specified as the profile's kickstart file, do not auto-assign")
p.add_option("--rsync-flags", dest="rsync_flags", help="pass additional flags to rsync")
def run(self):
@@ -92,7 +93,8 @@ class ImportFunction(commands.CobblerFunction):
self.options.name,
network_root=self.options.available_as,
kickstart_file=self.options.kickstart_file,
- rsync_flags=self.options.rsync_flags
+ rsync_flags=self.options.rsync_flags,
+ arch=self.options.arch
)
@@ -182,11 +184,6 @@ class ReportFunction(commands.CobblerFunction):
self.reporting_print_sorted(self.api.repos())
return True
-## FIXME: add legacy command translator to keep things simple
-## cobbler system report foo --> cobbler report --what=systems --name=foo
-## cobbler system report --> cobbler report --what=systems
-## ditto for "cobbler list"
-
########################################################
class StatusFunction(commands.CobblerFunction):
@@ -198,7 +195,7 @@ class StatusFunction(commands.CobblerFunction):
return "status"
def run(self):
- return self.api.status("text") # no other output modes supported yet
+ return self.api.status("text") # no other output modes supported yet
########################################################
@@ -243,6 +240,46 @@ class ValidateKsFunction(commands.CobblerFunction):
return self.api.validateks()
########################################################
+
+class BuildIsoFunction(commands.CobblerFunction):
+
+ def add_options(self,p,args):
+ p.add_option("--iso", dest="isoname", help="(OPTIONAL) output ISO to this path")
+ p.add_option("--profiles", dest="profiles", help="(OPTIONAL) use these profiles only")
+ p.add_option("--tempdir", dest="tempdir", help="(OPTIONAL) working directory")
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler buildiso","")
+
+ def command_name(self):
+ return "buildiso"
+
+ def run(self):
+ return self.api.build_iso(
+ iso=self.options.isoname,
+ profiles=self.options.profiles,
+ tempdir=self.options.tempdir
+ )
+
+########################################################
+
+class ReplicateFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler replicate","[ARGS|--help]")
+
+ def command_name(self):
+ return "replicate"
+
+ def add_options(self, p, args):
+ p.add_option("--master", dest="master", help="Cobbler server to replicate from.")
+
+ def run(self):
+ return self.api.replicate(cobbler_master = self.options.master)
+
+
+
+########################################################
# MODULE HOOKS
def register():
@@ -253,9 +290,11 @@ def register():
def cli_functions(api):
return [
+ BuildIsoFunction(api),
CheckFunction(api), ImportFunction(api), ReserializeFunction(api),
ListFunction(api), ReportFunction(api), StatusFunction(api),
- SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api)
+ SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api),
+ ReplicateFunction(api)
]
return []
diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py
index e9d1d23..45b7b2e 100644
--- a/cobbler/modules/cli_profile.py
+++ b/cobbler/modules/cli_profile.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import commands
import cexceptions
@@ -33,10 +33,15 @@ class ProfileFunction(commands.CobblerFunction):
return "profile"
def subcommands(self):
- return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ]
def add_options(self, p, args):
- if not self.matches_args(args,["remove","report","list"]):
+
+ if self.matches_args(args,["add"]):
+ p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true")
+
+
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--distro", dest="distro", help="ex: 'RHEL-5-i386' (REQUIRED)")
p.add_option("--dhcp-tag", dest="dhcp_tag", help="for use in advanced DHCP configuration")
@@ -46,18 +51,20 @@ class ProfileFunction(commands.CobblerFunction):
p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'")
p.add_option("--name", dest="name", help="a name for the profile (REQUIRED)")
+
if "copy" in args or "rename" in args:
p.add_option("--newname", dest="newname")
- if not self.matches_args(args,["remove","report", "list"]):
+ if not self.matches_args(args,["dumpvars","remove","report", "list"]):
p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed")
- if not self.matches_args(args,["report", "list"]):
+ if not self.matches_args(args,["dumpvars","report", "list"]):
p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution")
+ p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module")
if self.matches_args(args,["remove"]):
p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--repos", dest="repos", help="names of cobbler repos")
p.add_option("--server-override", dest="server_override", help="overrides value in settings file")
p.add_option("--virt-bridge", dest="virt_bridge", help="ex: 'virbr0'")
@@ -69,8 +76,7 @@ class ProfileFunction(commands.CobblerFunction):
def run(self):
-
- if self.matches_args(self.args,["report","list","remove"]) or not self.options.inherit:
+ if self.matches_args(self.args,["report","list","remove","dumpvars"]) or not self.options.inherit:
obj = self.object_manipulator_start(self.api.new_profile,self.api.profiles,subobject=False)
else:
obj = self.object_manipulator_start(self.api.new_profile,self.api.profiles,subobject=True)
@@ -78,20 +84,23 @@ class ProfileFunction(commands.CobblerFunction):
if obj is None:
return True
- if self.options.inherit: obj.set_parent(self.options.inherit)
- if self.options.distro: obj.set_distro(self.options.distro)
- if self.options.kickstart: obj.set_kickstart(self.options.kickstart)
- if self.options.kopts: obj.set_kernel_options(self.options.kopts)
- if self.options.ksmeta: obj.set_ksmeta(self.options.ksmeta)
- if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size)
- if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram)
- if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge)
- if self.options.virt_type: obj.set_virt_type(self.options.virt_type)
- if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus)
- if self.options.repos: obj.set_repos(self.options.repos)
- if self.options.virt_path: obj.set_virt_path(self.options.virt_path)
- if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag)
- if self.options.server_override: obj.set_server(self.options.server)
+ if not self.matches_args(self.args,["dumpvars"]):
+ if self.options.inherit: obj.set_parent(self.options.inherit)
+ if self.options.distro: obj.set_distro(self.options.distro)
+ if self.options.kickstart: obj.set_kickstart(self.options.kickstart)
+ if self.options.kopts: obj.set_kernel_options(self.options.kopts)
+ if self.options.ksmeta: obj.set_ksmeta(self.options.ksmeta)
+ if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size)
+ if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram)
+ if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge)
+ if self.options.virt_type: obj.set_virt_type(self.options.virt_type)
+ if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus)
+ if self.options.repos: obj.set_repos(self.options.repos)
+ if self.options.virt_path: obj.set_virt_path(self.options.virt_path)
+ if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag)
+ if self.options.server_override: obj.set_server(self.options.server)
+
+ if self.options.owners: obj.set_owners(self.options.owners)
return self.object_manipulator_finish(obj, self.api.profiles, self.options)
diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py
index 96afa6f..af6a9d1 100644
--- a/cobbler/modules/cli_repo.py
+++ b/cobbler/modules/cli_repo.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import commands
import cexceptions
@@ -33,20 +33,25 @@ class RepoFunction(commands.CobblerFunction):
return "repo"
def subcommands(self):
- return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ]
def add_options(self, p, args):
- if not self.matches_args(args,["remove","report","list"]):
+
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--arch", dest="arch", help="overrides repo arch if required")
+ if self.matches_args(args,["add"]):
+ p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true")
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--createrepo-flags", dest="createrepo_flags", help="additional flags for createrepo")
p.add_option("--keep-updated", dest="keep_updated", help="update on each reposync, yes/no")
p.add_option("--name", dest="name", help="ex: 'Fedora-8-updates-i386' (REQUIRED)")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--mirror", dest="mirror", help="source to mirror (REQUIRED)")
+ p.add_option("--mirror-locally", dest="mirror_locally", help="mirror or use external directly? (default 1)")
p.add_option("--priority", dest="priority", help="set priority")
p.add_option("--rpm-list", dest="rpm_list", help="just mirror these rpms")
p.add_option("--yumopts", dest="yumopts", help="ex: pluginvar=abcd")
@@ -55,10 +60,12 @@ class RepoFunction(commands.CobblerFunction):
p.add_option("--newname", dest="newname", help="used for copy/edit")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed")
- if not self.matches_args(args,["report","list"]):
+ if not self.matches_args(args,["dumpvars","report","list"]):
p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution")
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
+ p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module")
def run(self):
@@ -66,6 +73,8 @@ class RepoFunction(commands.CobblerFunction):
obj = self.object_manipulator_start(self.api.new_repo,self.api.repos)
if obj is None:
return True
+ if self.matches_args(self.args,["dumpvars"]):
+ return self.object_manipulator_finish(obj, self.api.profiles, self.options)
if self.options.arch: obj.set_arch(self.options.arch)
if self.options.createrepo_flags: obj.set_createrepo_flags(self.options.createrepo_flags)
@@ -73,8 +82,12 @@ class RepoFunction(commands.CobblerFunction):
if self.options.keep_updated: obj.set_keep_updated(self.options.keep_updated)
if self.options.priority: obj.set_priority(self.options.priority)
if self.options.mirror: obj.set_mirror(self.options.mirror)
+ if self.options.mirror_locally: obj.set_mirror_locally(self.options.mirror_locally)
if self.options.yumopts: obj.set_yumopts(self.options.yumopts)
+ if self.options.owners:
+ obj.set_owners(self.options.owners)
+
return self.object_manipulator_finish(obj, self.api.repos, self.options)
diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py
index c463b8c..d8f8edf 100644
--- a/cobbler/modules/cli_system.py
+++ b/cobbler/modules/cli_system.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _, get_random_mac
import commands
import cexceptions
@@ -33,11 +33,14 @@ class SystemFunction(commands.CobblerFunction):
return "system"
def subcommands(self):
- return [ "add", "edit", "copy", "rename", "remove", "report", "list" ]
+ return [ "add", "edit", "copy", "rename", "remove", "report", "list", "dumpvars" ]
def add_options(self, p, args):
- if not self.matches_args(args,["remove","report","list"]):
+ if self.matches_args(args,["add"]):
+ p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true")
+
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--dhcp-tag", dest="dhcp_tag", help="for use in advanced DHCP configurations")
p.add_option("--gateway", dest="gateway", help="for static IP / templating usage")
p.add_option("--hostname", dest="hostname", help="ex: server.example.org")
@@ -50,25 +53,30 @@ class SystemFunction(commands.CobblerFunction):
p.add_option("--name", dest="name", help="a name for the system (REQUIRED)")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--netboot-enabled", dest="netboot_enabled", help="PXE on (1) or off (0)")
if self.matches_args(args,["copy","rename"]):
p.add_option("--newname", dest="newname", help="for use with copy/edit")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed")
- if not self.matches_args(args,["report","list"]):
+ if not self.matches_args(args,["dumpvars","report","list"]):
p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
+ p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module")
p.add_option("--profile", dest="profile", help="name of cobbler profile (REQUIRED)")
p.add_option("--server-override", dest="server_override", help="overrides server value in settings file")
p.add_option("--subnet", dest="subnet", help="for static IP / templating usage")
- p.add_option("--virt-bridge", dest="virt_bridge", help="ex: virbr0")
- p.add_option("--virt-path", dest="virt_path", help="path, partition, or volume")
- p.add_option("--virt-type", dest="virt_type", help="ex: xenpv, qemu, xenfv")
+
+ p.add_option("--virt-bridge", dest="virt_bridge", help="ex: 'virbr0'")
+ p.add_option("--virt-cpus", dest="virt_cpus", help="integer (default: 1)")
+ p.add_option("--virt-file-size", dest="virt_file_size", help="size in GB")
+ p.add_option("--virt-path", dest="virt_path", help="path, partition, or volume")
+ p.add_option("--virt-ram", dest="virt_ram", help="size in MB")
+ p.add_option("--virt-type", dest="virt_type", help="ex: 'xenpv', 'qemu'")
def run(self):
@@ -76,6 +84,8 @@ class SystemFunction(commands.CobblerFunction):
obj = self.object_manipulator_start(self.api.new_system,self.api.systems)
if obj is None:
return True
+ if self.matches_args(self.args,["dumpvars"]):
+ return self.object_manipulator_finish(obj, self.api.profiles, self.options)
if self.options.profile: obj.set_profile(self.options.profile)
if self.options.kopts: obj.set_kernel_options(self.options.kopts)
@@ -83,8 +93,13 @@ class SystemFunction(commands.CobblerFunction):
if self.options.kickstart: obj.set_kickstart(self.options.kickstart)
if self.options.netboot_enabled: obj.set_netboot_enabled(self.options.netboot_enabled)
if self.options.server_override: obj.set_server(self.options.server_override)
- if self.options.virt_path: obj.set_virt_path(self.options.virt_path)
+
+ if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size)
+ if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram)
if self.options.virt_type: obj.set_virt_type(self.options.virt_type)
+ if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus)
+ if self.options.virt_path: obj.set_virt_path(self.options.virt_path)
+
if self.options.interface:
my_interface = "intf%s" % self.options.interface
@@ -92,15 +107,23 @@ class SystemFunction(commands.CobblerFunction):
my_interface = "intf0"
if self.options.hostname: obj.set_hostname(self.options.hostname, my_interface)
- if self.options.mac: obj.set_mac_address(self.options.mac, my_interface)
+ if self.options.mac:
+ if self.options.mac.lower() == 'random':
+ obj.set_mac_address(get_random_mac(self.api), my_interface)
+ else:
+ obj.set_mac_address(self.options.mac, my_interface)
if self.options.ip: obj.set_ip_address(self.options.ip, my_interface)
if self.options.subnet: obj.set_subnet(self.options.subnet, my_interface)
if self.options.gateway: obj.set_gateway(self.options.gateway, my_interface)
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)
+ if self.options.owners:
+ obj.set_owners(self.options.owners)
+
+ rc = self.object_manipulator_finish(obj, self.api.systems, self.options)
+ return rc
########################################################
diff --git a/cobbler/modules/manage_bind.py b/cobbler/modules/manage_bind.py
new file mode 100644
index 0000000..339fbf0
--- /dev/null
+++ b/cobbler/modules/manage_bind.py
@@ -0,0 +1,283 @@
+"""
+This is some of the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+John Eckersberg <jeckersb@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 os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+import popen2
+import re
+from shlex import shlex
+
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "manage"
+
+
+class BindManager:
+
+ def what(self):
+ return "isc_and_bind"
+
+ def __init__(self,config,verbose=False):
+ """
+ Constructor
+ """
+ self.verbose = verbose
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.systems = config.systems()
+ self.settings = config.settings()
+ self.repos = config.repos()
+ self.templar = templar.Templar(config)
+
+ def regen_hosts(self):
+ pass # not used
+
+ def __forward_zones(self):
+ """
+ Returns a map of zones and the records that belong
+ in them
+ """
+ zones = {}
+ for zone in self.settings.manage_forward_zones:
+ zones[zone] = []
+
+ for sys in self.systems:
+ for (name, interface) in sys.interfaces.iteritems():
+ host = interface["hostname"]
+ ip = interface["ip_address"]
+ if not host or not ip:
+ # gotsta have some hostname and ip or else!
+ continue
+ if host.find(".") == -1:
+ continue
+
+ # match the longest zone!
+ # e.g. if you have a host a.b.c.d.e
+ # if manage_forward_zones has:
+ # - c.d.e
+ # - b.c.d.e
+ # then a.b.c.d.e should go in b.c.d.e
+ best_match = ''
+ for zone in zones.keys():
+ if re.search('\.%s$' % zone, host) and len(zone) > len(best_match):
+ best_match = zone
+
+ if best_match == '': # no match
+ continue
+
+ # strip the zone off the hostname and append the
+ # remainder + ip to the zone list
+ host = host.replace(best_match, '')
+ if host[-1] == '.': # strip trailing '.' if it's there
+ host = host[:-1]
+ zones[best_match].append((host, ip))
+
+ # axe zones that are defined in manage_forward_zones
+ # but don't actually match any hosts
+ for (k,v) in zones.items():
+ if v == []:
+ zones.pop(k)
+
+ return zones
+
+ def __reverse_zones(self):
+ """
+ Returns a map of zones and the records that belong
+ in them
+ """
+ zones = {}
+ for zone in self.settings.manage_reverse_zones:
+ zones[zone] = []
+
+ for sys in self.systems:
+ for (name, interface) in sys.interfaces.iteritems():
+ host = interface["hostname"]
+ ip = interface["ip_address"]
+ if not host or not ip:
+ # gotsta have some hostname and ip or else!
+ continue
+
+ # match the longest zone!
+ # e.g. if you have an ip 1.2.3.4
+ # if manage_reverse_zones has:
+ # - 1.2
+ # - 1.2.3
+ # then 1.2.3.4 should go in 1.2.3
+ best_match = ''
+ for zone in zones.keys():
+ if re.search('^%s\.' % zone, ip) and len(zone) > len(best_match):
+ best_match = zone
+
+ if best_match == '': # no match
+ continue
+
+ # strip the zone off the front of the ip
+ # reverse the rest of the octets
+ # append the remainder + hostname
+ ip = ip.replace(best_match, '', 1)
+ if ip[0] == '.': # strip leading '.' if it's there
+ ip = ip[1:]
+ tokens = ip.split('.')
+ tokens.reverse()
+ ip = '.'.join(tokens)
+ zones[best_match].append((ip, host + '.'))
+
+ # axe zones that are defined in manage_forward_zones
+ # but don't actually match any hosts
+ for (k,v) in zones.items():
+ if v == []:
+ zones.pop(k)
+
+ return zones
+
+
+ def __write_named_conf(self):
+ """
+ Write out the named.conf main config file from the template.
+ """
+ settings_file = self.settings.named_conf
+ template_file = "/etc/cobbler/named.template"
+ forward_zones = self.settings.manage_forward_zones
+ reverse_zones = self.settings.manage_reverse_zones
+
+ metadata = {'zone_include': ''}
+ for zone in self.__forward_zones().keys():
+ txt = """
+zone "%(zone)s." {
+ type master;
+ file "%(zone)s";
+};
+""" % {'zone': zone}
+ metadata['zone_include'] = metadata['zone_include'] + txt
+
+ for zone in self.__reverse_zones().keys():
+ tokens = zone.split('.')
+ tokens.reverse()
+ arpa = '.'.join(tokens) + '.in-addr.arpa'
+ txt = """
+zone "%(arpa)s." {
+ type master;
+ file "%(zone)s";
+};
+""" % {'arpa': arpa, 'zone': zone}
+ metadata['zone_include'] = metadata['zone_include'] + txt
+
+ try:
+ f2 = open(template_file,"r")
+ except:
+ raise CX(_("error reading template from file: %s") % template_file)
+ template_data = ""
+ template_data = f2.read()
+ f2.close()
+
+ self.templar.render(template_data, metadata, settings_file, None)
+
+ def __write_zone_files(self):
+ """
+ Write out the forward and reverse zone files for all configured zones
+ """
+ default_template_file = "/etc/cobbler/zone.template"
+ cobbler_server = self.settings.server
+ serial = int(time.time())
+ forward = self.__forward_zones()
+ reverse = self.__reverse_zones()
+
+ try:
+ f2 = open(default_template_file,"r")
+ except:
+ raise CX(_("error reading template from file: %s") % default_template_file)
+ default_template_data = ""
+ default_template_data = f2.read()
+ f2.close()
+
+ for (zone, hosts) in forward.iteritems():
+ metadata = {
+ 'cobbler_server': cobbler_server,
+ 'serial': serial,
+ 'host_record': ''
+ }
+
+ # grab zone-specific template if it exists
+ try:
+ fd = open('/etc/cobbler/zone_templates/%s' % zone)
+ template_data = fd.read()
+ fd.close()
+ except:
+ template_data = default_template_data
+
+ for host in hosts:
+ txt = '%s\tIN\tA\t%s\n' % host
+ metadata['host_record'] = metadata['host_record'] + txt
+
+ self.templar.render(template_data, metadata, '/var/named/' + zone, None)
+
+ for (zone, hosts) in reverse.iteritems():
+ metadata = {
+ 'cobbler_server': cobbler_server,
+ 'serial': serial,
+ 'host_record': ''
+ }
+
+ # grab zone-specific template if it exists
+ try:
+ fd = open('/etc/cobbler/zone_templates/%s' % zone)
+ template_data = fd.read()
+ fd.close()
+ except:
+ template_data = default_template_data
+
+ for host in hosts:
+ txt = '%s\tIN\tPTR\t%s\n' % host
+ metadata['host_record'] = metadata['host_record'] + txt
+
+ self.templar.render(template_data, metadata, '/var/named/' + zone, None)
+
+
+ def write_dns_files(self):
+ """
+ BIND files are written when manage_dns is set in
+ /var/lib/cobbler/settings.
+ """
+
+ self.__write_named_conf()
+ self.__write_zone_files()
+
+def get_manager(config):
+ return BindManager(config)
+
diff --git a/cobbler/modules/manage_dnsmasq.py b/cobbler/modules/manage_dnsmasq.py
new file mode 100644
index 0000000..465995f
--- /dev/null
+++ b/cobbler/modules/manage_dnsmasq.py
@@ -0,0 +1,194 @@
+
+"""
+This is some of the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+John Eckersberg <jeckersb@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 os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+import popen2
+from shlex import shlex
+import utils
+from cexceptions import *
+import templar
+import item_distro
+import item_profile
+import item_repo
+import item_system
+from utils import _
+
+def register():
+ return "manage"
+
+class DnsmasqManager:
+ """
+ Handles conversion of internal state to the tftpboot tree layout
+ """
+
+ def __init__(self,config,verbose=False,dhcp=None):
+ """
+ Constructor
+ """
+ self.verbose = verbose
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.systems = config.systems()
+ self.settings = config.settings()
+ self.repos = config.repos()
+ self.templar = templar.Templar(config)
+
+ def what(self):
+ return "dnsmasq"
+
+ def write_dhcp_lease(self,port,host,ip,mac):
+ pass
+
+ def remove_dhcp_lease(self,port,host):
+ pass
+
+
+ def write_dhcp_file(self):
+ """
+ DHCP files are written when manage_dhcp is set in
+ /var/lib/cobbler/settings.
+ """
+
+ settings_file = self.settings.dnsmasq_conf
+ template_file = "/etc/cobbler/dnsmasq.template"
+
+ try:
+ f2 = open(template_file,"r")
+ except:
+ raise CX(_("error writing template to file: %s") % template_file)
+ template_data = ""
+ template_data = f2.read()
+ f2.close()
+
+ # build each per-system definition
+ # as configured, this only works for ISC, patches accepted
+ # from those that care about Itanium. elilo seems to be unmaintained
+ # so additional maintaince in other areas may be required to keep
+ # this working.
+
+ elilo = os.path.basename(self.settings.bootloaders["ia64"])
+
+ system_definitions = {}
+ counter = 0
+
+ # we used to just loop through each system, but now we must loop
+ # through each network interface of each system.
+
+ for system in self.systems:
+ profile = system.get_conceptual_parent()
+ distro = profile.get_conceptual_parent()
+ for (name, interface) in system.interfaces.iteritems():
+
+ mac = interface["mac_address"]
+ ip = interface["ip_address"]
+ host = interface["hostname"]
+
+ if mac is None or mac == "":
+ # can't write a DHCP entry for this system
+ continue
+
+ counter = counter + 1
+ systxt = ""
+
+ # dnsmasq. don't have to write IP and other info here, but we do tag
+ # each MAC based on the arch of it's distro, if it needs something other
+ # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq
+ # reload (full "cobbler sync") would be required after adding the system
+ # to cobbler, just to tag this relationship.
+
+ if ip is not None and ip != "":
+ if distro.arch.lower() == "ia64":
+ systxt = "dhcp-host=net:ia64," + ip + "\n"
+ # support for other arches needs modifications here
+ else:
+ systxt = ""
+
+ dhcp_tag = interface["dhcp_tag"]
+ if dhcp_tag == "":
+ dhcp_tag = "default"
+
+ if not system_definitions.has_key(dhcp_tag):
+ system_definitions[dhcp_tag] = ""
+ system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt
+
+ # we are now done with the looping through each interface of each system
+
+ metadata = {
+ "omapi_enabled" : self.settings.omapi_enabled,
+ "omapi_port" : self.settings.omapi_port,
+ "insert_cobbler_system_definitions" : system_definitions.get("default",""),
+ "date" : time.asctime(time.gmtime()),
+ "cobbler_server" : self.settings.server,
+ "next_server" : self.settings.next_server,
+ "elilo" : elilo
+ }
+
+ # now add in other DHCP expansions that are not tagged with "default"
+ for x in system_definitions.keys():
+ if x == "default":
+ continue
+ metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x]
+
+ self.templar.render(template_data, metadata, settings_file, None)
+
+ def regen_ethers(self):
+ # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date
+ # every time we add a system.
+ # read 'man ethers' for format info
+ fh = open("/etc/ethers","w+")
+ for sys in self.systems:
+ for (name, interface) in sys.interfaces.iteritems():
+ mac = interface["mac_address"]
+ ip = interface["ip_address"]
+ if mac is None or mac == "":
+ # can't write this w/o a MAC address
+ continue
+ if ip is not None and ip != "":
+ fh.write(mac.upper() + "\t" + ip + "\n")
+ fh.close()
+
+ def regen_hosts(self):
+ # dnsmasq knows how to read this database for host info
+ # (other things may also make use of this later)
+ fh = open("/var/lib/cobbler/cobbler_hosts","w+")
+ for sys in self.systems:
+ for (name, interface) in sys.interfaces.iteritems():
+ mac = interface["mac_address"]
+ host = interface["hostname"]
+ ip = interface["ip_address"]
+ if mac is None or mac == "":
+ continue
+ if host is not None and host != "" and ip is not None and ip != "":
+ fh.write(ip + "\t" + host + "\n")
+ fh.close()
+
+ def write_dns_files(self):
+ # already taken care of by the regen_hosts()
+ pass
+
+def get_manager(config):
+ return DnsmasqManager(config)
+
diff --git a/cobbler/modules/manage_isc.py b/cobbler/modules/manage_isc.py
new file mode 100644
index 0000000..c398270
--- /dev/null
+++ b/cobbler/modules/manage_isc.py
@@ -0,0 +1,252 @@
+"""
+This is some of the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+John Eckersberg <jeckersb@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 os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+import popen2
+from shlex import shlex
+
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "manage"
+
+
+class IscManager:
+
+ def what(self):
+ return "isc_and_bind"
+
+ def __init__(self,config,verbose=False):
+ """
+ Constructor
+ """
+ self.verbose = verbose
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.systems = config.systems()
+ self.settings = config.settings()
+ self.repos = config.repos()
+ self.templar = templar.Templar(config)
+
+ def write_dhcp_lease(self,port,host,ip,mac):
+ """
+ Use DHCP's API to create a DHCP entry in the
+ /var/lib/dhcpd/dhcpd.leases file
+ #Code from http://svn.osgdc.org/browse/kusu/kusu
+ # /trunk/src/kits/base/packages/kusu-base-installer/lib/kusu/nodefun.py?r=3025
+ # FIXME: should use subprocess
+ """
+ try:
+ fromchild, tochild = popen2.popen2(self.settings.omshell_bin)
+ tochild.write("port %s\n" % port)
+ tochild.flush()
+ tochild.write("connect\n")
+ tochild.flush()
+ tochild.write("new host\n")
+ tochild.flush()
+ tochild.write('set name = \"%s\"\n' % host)
+ tochild.flush()
+ tochild.write("set ip-address = %s\n" % ip)
+ tochild.flush()
+ tochild.write("set hardware-address = %s\n" % mac.lower())
+ tochild.flush()
+ tochild.write("set hardware-type = 1\n")
+ tochild.flush()
+ tochild.write("create\n")
+ tochild.flush()
+ tochild.close()
+ fromchild.close()
+ except IOError:
+ # FIXME: just catch 32 (broken pipe) and show a warning
+ pass
+
+ def remove_dhcp_lease(self,port,host):
+ """
+ Use DHCP's API to delete a DHCP entry in
+ the /var/lib/dhcpd/dhcpd.leases file
+ """
+ fromchild, tochild = popen2.popen2(self.settings.omshell_bin)
+ try:
+ tochild.write("port %s\n" % port)
+ tochild.flush()
+ tochild.write("connect\n")
+ tochild.flush()
+ tochild.write("new host\n")
+ tochild.flush()
+ tochild.write('set name = \"%s\"\n' % host)
+ tochild.flush()
+ tochild.write("open\n") # opens register with host information
+ tochild.flush()
+ tochild.write("remove\n")
+ tochild.flush()
+ tochild.close()
+ fromchild.close()
+ except IOError:
+ # FIXME: convert this to subprocess.
+ # FIXME: catch specific errors only (32/broken pipe)
+ pass
+
+ def write_dhcp_file(self):
+ """
+ DHCP files are written when manage_dhcp is set in
+ /var/lib/cobbler/settings.
+ """
+
+ settings_file = self.settings.dhcpd_conf
+ template_file = "/etc/cobbler/dhcp.template"
+ mode = self.settings.manage_dhcp_mode.lower()
+
+ try:
+ f2 = open(template_file,"r")
+ except:
+ raise CX(_("error writing template to file: %s") % template_file)
+ template_data = ""
+ template_data = f2.read()
+ f2.close()
+
+ # build each per-system definition
+ # as configured, this only works for ISC, patches accepted
+ # from those that care about Itanium. elilo seems to be unmaintained
+ # so additional maintaince in other areas may be required to keep
+ # this working.
+
+ elilo = os.path.basename(self.settings.bootloaders["ia64"])
+
+ system_definitions = {}
+ counter = 0
+
+
+ # Clean system definitions in /var/lib/dhcpd/dhcpd.leases just in
+ # case to avoid conflicts with the hosts we're defining and to clean
+ # possible removed hosts (only if using OMAPI)
+ if self.settings.omapi_enabled and self.settings.omapi_port:
+ if os.path.exists("/var/lib/dhcpd/dhcpd.leases"):
+ file = open('/var/lib/dhcpd/dhcpd.leases')
+ item = shlex(file)
+ while 1:
+ elem = item.get_token()
+ if not elem:
+ break
+ if elem == 'host':
+ hostremove = item.get_token()
+ self.remove_dhcp_lease(self.settings.omapi_port,hostremove)
+
+ # we used to just loop through each system, but now we must loop
+ # through each network interface of each system.
+
+ for system in self.systems:
+ profile = system.get_conceptual_parent()
+ distro = profile.get_conceptual_parent()
+ for (name, interface) in system.interfaces.iteritems():
+
+ mac = interface["mac_address"]
+ ip = interface["ip_address"]
+ host = interface["hostname"]
+
+ if mac is None or mac == "":
+ # can't write a DHCP entry for this system
+ continue
+
+ counter = counter + 1
+ systxt = ""
+
+
+ # the label the entry after the hostname if possible
+ if host is not None and host != "":
+ systxt = "\nhost %s {\n" % host
+ if self.settings.isc_set_host_name:
+ systxt = systxt + " option host-name = \"%s\";\n" % host
+ else:
+ systxt = "\nhost generic%d {\n" % counter
+
+ if distro.arch == "ia64":
+ # can't use pxelinux.0 anymore
+ systxt = systxt + " filename \"/%s\";\n" % elilo
+ systxt = systxt + " hardware ethernet %s;\n" % mac
+ if ip is not None and ip != "":
+ systxt = systxt + " fixed-address %s;\n" % ip
+ systxt = systxt + "}\n"
+
+ # If we have all values defined and we're using omapi,
+ # we will just create entries dinamically into DHCPD
+ # without requiring a restart (but file will be written
+ # as usual for having it working after restart)
+
+ if ip is not None and ip != "":
+ if mac is not None and mac != "":
+ if host is not None and host != "":
+ if self.settings.omapi_enabled and self.settings.omapi_port:
+ self.remove_dhcp_lease(self.settings.omapi_port,host)
+ self.write_dhcp_lease(self.settings.omapi_port,host,ip,mac)
+
+ dhcp_tag = interface["dhcp_tag"]
+ if dhcp_tag == "":
+ dhcp_tag = "default"
+
+ if not system_definitions.has_key(dhcp_tag):
+ system_definitions[dhcp_tag] = ""
+ system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt
+
+ # we are now done with the looping through each interface of each system
+
+ metadata = {
+ "omapi_enabled" : self.settings.omapi_enabled,
+ "omapi_port" : self.settings.omapi_port,
+ "insert_cobbler_system_definitions" : system_definitions.get("default",""),
+ "date" : time.asctime(time.gmtime()),
+ "cobbler_server" : self.settings.server,
+ "next_server" : self.settings.next_server,
+ "elilo" : elilo
+ }
+
+ # now add in other DHCP expansions that are not tagged with "default"
+ for x in system_definitions.keys():
+ if x == "default":
+ continue
+ metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x]
+
+ self.templar.render(template_data, metadata, settings_file, None)
+
+ def regen_ethers(self):
+ pass # ISC/BIND do not use this
+
+
+def get_manager(config):
+ return IscManager(config)
+
diff --git a/cobbler/modules/serializer_shelve.py b/cobbler/modules/serializer_shelve.py
index 246a92a..09495dd 100644
--- a/cobbler/modules/serializer_shelve.py
+++ b/cobbler/modules/serializer_shelve.py
@@ -24,7 +24,7 @@ import os
import sys
import glob
import traceback
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import os
import shelve
import time
diff --git a/cobbler/modules/serializer_yaml.py b/cobbler/modules/serializer_yaml.py
index 6425bd1..c4bd359 100644
--- a/cobbler/modules/serializer_yaml.py
+++ b/cobbler/modules/serializer_yaml.py
@@ -21,7 +21,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import utils
import yaml # Howell-Clark version
import cexceptions
@@ -66,7 +66,10 @@ def get_filename(collection_type):
ending = collection_type
if not ending.endswith("s"):
ending = ending + "s"
- return "/var/lib/cobbler/%s" % ending
+ if ending != "settings":
+ return "/var/lib/cobbler/%s" % ending
+ else:
+ return "/etc/cobbler/settings"
def deserialize_raw(collection_type):
filename = get_filename(collection_type)
diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py
new file mode 100644
index 0000000..a438583
--- /dev/null
+++ b/cobbler/pxegen.py
@@ -0,0 +1,324 @@
+"""
+Builds out filesystem trees/data based on the object tree.
+This is the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <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 os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+class PXEGen:
+ """
+ Handles building out PXE stuff
+ """
+
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.systems = config.systems()
+ self.settings = config.settings()
+ self.repos = config.repos()
+ self.templar = templar.Templar(config)
+ self.bootloc = utils.tftpboot_location()
+
+ def copy_bootloaders(self):
+ """
+ Copy bootloaders to the configured tftpboot directory
+ NOTE: we support different arch's if defined in
+ /etc/cobbler/settings.
+ """
+ for loader in self.settings.bootloaders.keys():
+ path = self.settings.bootloaders[loader]
+ newname = os.path.basename(path)
+ destpath = os.path.join(self.bootloc, newname)
+ utils.copyfile(path, destpath)
+ utils.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32"))
+
+ # Copy memtest to tftpboot if package is installed on system
+ for memtest in glob.glob('/boot/memtest*'):
+ base = os.path.basename(memtest)
+ utils.copyfile(memtest,os.path.join(self.bootloc,"images",base))
+
+ def copy_distros(self):
+ """
+ A distro is a kernel and an initrd. Copy all of them and error
+ out if any files are missing. The conf file was correct if built
+ via the CLI or API, though it's possible files have been moved
+ since or perhaps they reference NFS directories that are no longer
+ mounted.
+
+ NOTE: this has to be done for both tftp and http methods
+ """
+ # copy is a 4-letter word but tftpboot runs chroot, thus it's required.
+ for d in self.distros:
+ self.copy_single_distro_files(d)
+
+ def copy_single_distro_files(self, d):
+ for dirtree in [self.bootloc, self.settings.webdir]:
+ distros = os.path.join(dirtree, "images")
+ distro_dir = os.path.join(distros,d.name)
+ utils.mkdir(distro_dir)
+ kernel = utils.find_kernel(d.kernel) # full path
+ initrd = utils.find_initrd(d.initrd) # full path
+ if kernel is None or not os.path.isfile(kernel):
+ raise CX(_("kernel not found: %(file)s, distro: %(distro)s") % { "file" : d.kernel, "distro" : d.name })
+ if initrd is None or not os.path.isfile(initrd):
+ raise CX(_("initrd not found: %(file)s, distro: %(distro)s") % { "file" : d.initrd, "distro" : d.name })
+ b_kernel = os.path.basename(kernel)
+ b_initrd = os.path.basename(initrd)
+ if kernel.startswith(dirtree):
+ utils.linkfile(kernel, os.path.join(distro_dir, b_kernel))
+ else:
+ utils.copyfile(kernel, os.path.join(distro_dir, b_kernel))
+ if initrd.startswith(dirtree):
+ utils.linkfile(initrd, os.path.join(distro_dir, b_initrd))
+ else:
+ utils.copyfile(initrd, os.path.join(distro_dir, b_initrd))
+
+ def write_all_system_files(self,system):
+
+ profile = system.get_conceptual_parent()
+ if profile is None:
+ raise CX(_("system %(system)s references a missing profile %(profile)s") % { "system" : system.name, "profile" : system.profile})
+ distro = profile.get_conceptual_parent()
+ if distro is None:
+ raise CX(_("profile %(profile)s references a missing distro %(distro)s") % { "profile" : system.profile, "distro" : profile.distro})
+
+ # this used to just generate a single PXE config file, but now must
+ # generate one record for each described NIC ...
+
+ counter = 0
+ for (name,interface) in system.interfaces.iteritems():
+
+ ip = interface["ip_address"]
+
+ f1 = utils.get_config_filename(system,interface=name)
+
+ # for tftp only ...
+ if distro.arch in [ "x86", "x86_64", "standard"]:
+ # pxelinux wants a file named $name under pxelinux.cfg
+ f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1)
+ if distro.arch == "ia64":
+ # elilo expects files to be named "$name.conf" in the root
+ # and can not do files based on the MAC address
+ if ip is not None and ip != "":
+ print _("Warning: Itanium system object (%s) needs an IP address to PXE") % system.name
+
+ filename = "%s.conf" % utils.get_config_filename(system,interface=name)
+ f2 = os.path.join(self.bootloc, filename)
+
+ f3 = os.path.join(self.settings.webdir, "systems", f1)
+
+ if system.netboot_enabled and system.is_pxe_supported():
+ if distro.arch in [ "x86", "x86_64", "standard"]:
+ self.write_pxe_file(f2,system,profile,distro,False)
+ if distro.arch == "ia64":
+ self.write_pxe_file(f2,system,profile,distro,True)
+ else:
+ # ensure the file doesn't exist
+ utils.rmfile(f2)
+
+ counter = counter + 1
+
+
+ def make_pxe_menu(self):
+ # only do this if there is NOT a system named default.
+ default = self.systems.find(name="default")
+ if default is not None:
+ return
+
+ fname = os.path.join(self.bootloc, "pxelinux.cfg", "default")
+
+ # read the default template file
+ template_src = open("/etc/cobbler/pxedefault.template")
+ template_data = template_src.read()
+
+ # sort the profiles
+ profile_list = [profile for profile in self.profiles]
+ def sort_name(a,b):
+ return cmp(a.name,b.name)
+ profile_list.sort(sort_name)
+
+ # build out the menu entries
+ pxe_menu_items = ""
+ for profile in profile_list:
+ distro = profile.get_conceptual_parent()
+ # xen distros can be ruled out as they won't boot
+ if distro.name.find("-xen") != -1:
+ continue
+ contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False)
+ if contents is not None:
+ pxe_menu_items = pxe_menu_items + contents + "\n"
+
+ # if we have any memtest files in images, make entries for them
+ # after we list the profiles
+ memtests = glob.glob(self.bootloc + "/images/memtest*")
+ if len(memtests) > 0:
+ pxe_menu_items = pxe_menu_items + "\n\n"
+ for memtest in glob.glob(self.bootloc + '/images/memtest*'):
+ base = os.path.basename(memtest)
+ contents = self.write_memtest_pxe("/images/%s" % base)
+ pxe_menu_items = pxe_menu_items + contents + "\n"
+
+ # save the template.
+ metadata = { "pxe_menu_items" : pxe_menu_items }
+ outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default")
+ self.templar.render(template_data, metadata, outfile, None)
+ template_src.close()
+
+ def write_memtest_pxe(self,filename):
+ """
+ Write a configuration file for memtest
+ """
+
+ # just some random variables
+ template = None
+ metadata = {}
+ buffer = ""
+
+ template = "/etc/cobbler/pxeprofile.template"
+
+ # store variables for templating
+ metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename)
+ metadata["profile_name"] = os.path.basename(filename)
+ metadata["kernel_path"] = "/images/%s" % os.path.basename(filename)
+ metadata["initrd_path"] = ""
+ metadata["append_line"] = ""
+
+ # get the template
+ template_fh = open(template)
+ template_data = template_fh.read()
+ template_fh.close()
+
+ # return results
+ buffer = self.templar.render(template_data, metadata, None)
+ return buffer
+
+
+
+ def write_pxe_file(self,filename,system,profile,distro,is_ia64, include_header=True):
+ """
+ Write a configuration file for the boot loader(s).
+ More system-specific configuration may come in later, if so
+ that would appear inside the system object in api.py
+
+ NOTE: relevant to tftp only
+ """
+
+ # ---
+ # system might have netboot_enabled set to False (see item_system.py), if so,
+ # don't do anything else and flag the error condition.
+ if system is not None and not system.netboot_enabled:
+ return None
+
+ # ---
+ # just some random variables
+ template = None
+ metadata = {}
+ buffer = ""
+
+ # ---
+ # find kernel and initrd
+ kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel))
+ initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd))
+
+ # Find the kickstart if we inherit from another profile
+ kickstart_path = utils.blender(self.api, True, profile)["kickstart"]
+
+ # ---
+ # choose a template
+ if system is None:
+ template = "/etc/cobbler/pxeprofile.template"
+ elif not is_ia64:
+ template = "/etc/cobbler/pxesystem.template"
+ else:
+ template = "/etc/cobbler/pxesystem_ia64.template"
+
+ # now build the kernel command line
+ if system is not None:
+ blended = utils.blender(self.api, True, system)
+ else:
+ blended = utils.blender(self.api, True,profile)
+ kopts = blended["kernel_options"]
+
+ # generate the append line
+ append_line = "append %s" % utils.hash_to_string(kopts)
+ if not is_ia64:
+ append_line = "%s initrd=%s" % (append_line, initrd_path)
+ if len(append_line) >= 255 + len("append "):
+ print _("warning: kernel option length exceeds 255")
+
+ # kickstart path rewriting (get URLs for local files)
+ if kickstart_path is not None and kickstart_path != "":
+
+ if system is not None and kickstart_path.startswith("/"):
+ kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (blended["http_server"], system.name)
+ elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1:
+ kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (blended["http_server"], profile.name)
+
+ if distro.breed is None or distro.breed == "redhat":
+ append_line = "%s ks=%s" % (append_line, kickstart_path)
+ elif distro.breed == "suse":
+ append_line = "%s autoyast=%s" % (append_line, kickstart_path)
+ elif distro.breed == "debian":
+ append_line = "%s auto=true url=%s" % (append_line, kickstart_path)
+ append_line = append_line.replace("ksdevice","interface")
+
+ # store variables for templating
+ metadata["menu_label"] = ""
+ if not is_ia64 and system is None:
+ metadata["menu_label"] = "MENU LABEL %s" % profile.name
+ metadata["profile_name"] = profile.name
+ metadata["kernel_path"] = kernel_path
+ metadata["initrd_path"] = initrd_path
+ metadata["append_line"] = append_line
+
+ # get the template
+ template_fh = open(template)
+ template_data = template_fh.read()
+ template_fh.close()
+
+ # save file and/or return results, depending on how called.
+ buffer = self.templar.render(template_data, metadata, None)
+ if filename is not None:
+ fd = open(filename, "w")
+ fd.write(buffer)
+ fd.close()
+ return buffer
+
+
+
+
diff --git a/cobbler/remote.py b/cobbler/remote.py
index 5131323..1a414f3 100644
--- a/cobbler/remote.py
+++ b/cobbler/remote.py
@@ -24,6 +24,7 @@ import random
import base64
import string
import traceback
+import glob
import api as cobbler_api
import utils
@@ -64,6 +65,11 @@ class CobblerXMLRPCInterface:
def ping(self):
return True
+ def update(self,token=None):
+ # ensure the config is up to date as of /now/
+ self.api.deserialize()
+ return True
+
def get_user_from_token(self,token):
if not TOKEN_CACHE.has_key(token):
raise CX(_("invalid token: %s") % token)
@@ -158,9 +164,36 @@ class CobblerXMLRPCInterface:
return self._fix_none(data)
+ def get_kickstart_templates(self,token):
+ """
+ Returns all of the kickstarts that are in use by the system.
+ """
+ self.log("get_kickstart_templates",token=token)
+ self.check_access(token, "get_kickstart_templates")
+ return utils.get_kickstart_templates(self.api)
+
+ def is_kickstart_in_use(self,ks,token):
+ self.log("is_kickstart_in_use",token=token)
+ self.check_access(token, "is_kickstart_in_use")
+ for x in self.api.profiles():
+ if x.kickstart is not None and x.kickstart == ks:
+ return True
+ for x in self.api.systems():
+ if x.kickstart is not None and x.kickstart == ks:
+ return True
+ return False
+
+ def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None):
+ self.log("generate_kickstart")
+
+ if profile and not system:
+ regrc = self.register_mac(REMOTE_MAC,profile)
+
+ return self.api.generate_kickstart(profile,system)
+
def get_settings(self,token=None):
"""
- Return the contents of /var/lib/cobbler/settings, which is a hash.
+ Return the contents of /etc/cobbler/settings, which is a hash.
"""
self.log("get_settings",token=token)
return self.__get_all("settings")
@@ -184,7 +217,7 @@ class CobblerXMLRPCInterface:
self.api.add_system(system)
- def register_mac(self,mac,token=None):
+ def register_mac(self,mac,profile,token=None):
"""
If allow_cgi_register_mac is enabled in settings, this allows
kickstarts to add new system records for per-profile-provisioned
@@ -193,18 +226,35 @@ class CobblerXMLRPCInterface:
READ: https://fedorahosted.org/cobbler/wiki/AutoRegistration
"""
- if not self.api.settings().allow_cgi_mac_registration:
+ if mac is None:
+ # don't go further if not being called by anaconda
return 1
- system = self.api.find_system(mac_address=mac)
- if system is not None:
+ if not self.api.settings().register_new_installs:
+ # must be enabled in settings
return 2
- obj = server.new_system(token)
+ system = self.api.find_system(mac_address=mac)
+ if system is not None:
+ # do not allow overwrites
+ return 3
+
+ # the MAC probably looks like "eth0 AA:BB:CC:DD:EE:FF" now, fix it
+ if mac.find(" ") != -1:
+ mac = mac.split()[-1]
+
+ dup = self.api.find_system(mac_address=mac)
+ if dup is not None:
+ return 4
+
+ self.log("register mac for profile %s" % profile,token=token,name=mac)
+ obj = self.api.new_system()
obj.set_profile(profile)
- obj.set_name(mac.replace(":","_"))
+ name = mac.replace(":","_")
+ obj.set_name(name)
obj.set_mac_address(mac, "intf0")
- systems.add(obj,save=True)
+ obj.set_netboot_enabled(False)
+ self.api.add_system(obj)
return 0
def disable_netboot(self,name,token=None):
@@ -230,26 +280,28 @@ class CobblerXMLRPCInterface:
systems.add(obj,save=True,with_triggers=False,with_sync=False,quick_pxe_update=True)
return True
- def run_post_install_triggers(self,name,token=None):
+ def run_install_triggers(self,mode,objtype,name,ip,token=None):
+
"""
- This is a feature used to run the post install trigger.
- It passes the system named "name" to the trigger. Disabled by default as
- this requires public API access and is technically a read-write operation.
+ This is a feature used to run the pre/post install triggers.
+ See CobblerTriggers on Wiki for details
"""
- self.log("run_post_install_triggers",token=token)
- # used by postinstalltrigger.cgi
- self.api.clear()
- self.api.deserialize()
- if not self.api.settings().run_post_install_trigger:
- # feature disabled!
+ self.log("run_install_triggers",token=token)
+
+ if mode != "pre" and mode != "post":
return False
- systems = self.api.systems()
- obj = systems.find(name=name)
- if obj == None:
- # system not found!
+ if objtype != "system" and objtype !="profile":
return False
- utils.run_triggers(obj, "/var/lib/cobbler/triggers/install/post/*")
+
+ # the trigger script is called with name,mac, and ip as arguments 1,2, and 3
+ # we do not do API lookups here because they are rather expensive at install
+ # time if reinstalling all of a cluster all at once.
+ # we can do that at "cobbler check" time.
+
+ utils.run_triggers(None, "/var/lib/cobbler/triggers/install/%s/*" % mode, additional=[objtype,name,ip])
+
+
return True
def _refresh(self):
@@ -418,25 +470,13 @@ class CobblerXMLRPCInterface:
def get_random_mac(self,token=None):
"""
- Generate a random MAC address.
- from xend/server/netif.py
- Generate a random MAC address.
- Uses OUI 00-16-3E, allocated to
- Xensource, Inc. Last 3 fields are random.
- return: MAC address string
+ Wrapper for utils.get_random_mac
+
+ Used in the webui
"""
self.log("get_random_mac",token=None)
self._refresh()
- mac = [ 0x00, 0x16, 0x3e,
- random.randint(0x00, 0x7f),
- random.randint(0x00, 0xff),
- random.randint(0x00, 0xff) ]
- mac = ':'.join(map(lambda x: "%02x" % x, mac))
- systems = self.api.systems()
- while ( systems.find(mac_address=mac) ):
- mac = self.get_random_mac()
-
- return mac
+ return utils.get_random_mac(self.api)
def _fix_none(self,data):
"""
@@ -561,10 +601,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
FIXME: currently looks for users in /etc/cobbler/auth.conf
Would be very nice to allow for PAM and/or just Kerberos.
"""
- if not self.auth_enabled and input_user == "<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):
@@ -579,11 +615,12 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
self.__invalidate_expired_tokens()
self.__invalidate_expired_objects()
- if not self.auth_enabled:
- user = self.get_user_from_token(token)
- if user == "<system>":
- self.token_cache[token] = (time.time(), user) # update to prevent timeout
- return True
+ #if not self.auth_enabled:
+ # user = self.get_user_from_token(token)
+ # # old stuff, preserving for future usage
+ # # if user == "<system>":
+ # # self.token_cache[token] = (time.time(), user) # update to prevent timeout
+ # # return True
if self.token_cache.has_key(token):
user = self.get_user_from_token(token)
@@ -596,12 +633,54 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
self.log("invalid token",token=token)
raise CX(_("invalid token: %s" % token))
+ def __name_to_object(self,resource,name):
+ if resource.find("distro") != -1:
+ return self.api.find_distro(name)
+ if resource.find("profile") != -1:
+ return self.api.find_profile(name)
+ if resource.find("system") != -1:
+ return self.api.find_system(name)
+ if resource.find("repo") != -1:
+ return self.api.find_repo(name)
+ return None
+
+ def check_access_no_fail(self,token,resource,arg1=None,arg2=None):
+ """
+ This is called by the WUI to decide whether an element
+ is editable or not. It differs form check_access in that
+ it is supposed to /not/ log the access checks (TBA) and does
+ not raise exceptions.
+ """
+
+ need_remap = False
+ for x in [ "distro", "profile", "system", "repo" ]:
+ if arg1 is not None and resource.find(x) != -1:
+ need_remap = True
+ break
+
+ if need_remap:
+ # we're called with an object name, but need an object
+ arg1 = self.__name_to_object(resource,arg1)
+
+ try:
+ self.check_access(token,resource,arg1,arg2)
+ return True
+ except:
+ utils.log_exc(self.logger)
+ return False
+
def check_access(self,token,resource,arg1=None,arg2=None):
validated = self.__validate_token(token)
+ user = self.get_user_from_token(token)
if not self.auth_enabled:
+ # for public read-only XMLRPC, permit access
+ self.log("permitting read-only access")
return True
- return self.__authorize(token,resource,arg1,arg2)
-
+ rc = self.__authorize(token,resource,arg1,arg2)
+ self.log("authorization result: %s" % rc)
+ if not rc:
+ raise CX(_("authorization failure for user %s" % user))
+ return rc
def login(self,login_user,login_password):
"""
@@ -621,7 +700,11 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
def __authorize(self,token,resource,arg1=None,arg2=None):
user = self.get_user_from_token(token)
- if self.api.authorize(user,resource,arg1,arg2):
+ args = [ resource, arg1, arg2 ]
+ self.log("calling authorize for resource %s" % args, user=user)
+
+ rc = self.api.authorize(user,resource,arg1,arg2)
+ if rc:
return True
else:
raise CX(_("user does not have access to resource: %s") % resource)
@@ -664,6 +747,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
return self.object_cache[object_id][0]
raise CX(_("No such object for ID: %s") % object_id)
+ def sync(self,token):
+ """
+ Run sync code, which should complete before XMLRPC timeout. We can't
+ do reposync this way. Would be nice to send output over AJAX/other
+ later.
+ """
+ self.log("sync",token=token)
+ self.check_access(token,"sync")
+ return self.api.sync()
+
def new_distro(self,token):
"""
Creates a new (unconfigured) distro object. It works something like
@@ -767,41 +860,56 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
found = self.api.repos().find(name)
return self.__store_object(found)
- def save_distro(self,object_id,token):
+ def save_distro(self,object_id,token,editmode="bypass"):
"""
Saves a newly created or modified distro object to disk.
"""
self.log("save_distro",object_id=object_id,token=token)
- self.check_access(token,"save_distro")
obj = self.__get_object(object_id)
- return self.api.distros().add(obj,save=True)
+ self.check_access(token,"save_distro",obj)
+ if editmode == "new":
+ return self.api.distros().add(obj,save=True,check_for_duplicate_names=True)
+ else:
+ return self.api.distros().add(obj,save=True)
- def save_profile(self,object_id,token):
+ def save_profile(self,object_id,token,editmode="bypass"):
"""
Saves a newly created or modified profile object to disk.
"""
self.log("save_profile",token=token,object_id=object_id)
- self.check_access(token,"save_profile")
obj = self.__get_object(object_id)
- return self.api.profiles().add(obj,save=True)
+ self.check_access(token,"save_profile",obj)
+ if editmode == "new":
+ return self.api.profiles().add(obj,save=True,check_for_duplicate_names=True)
+ else:
+ return self.api.profiles().add(obj,save=True)
- def save_system(self,object_id,token):
+ def save_system(self,object_id,token,editmode="bypass"):
"""
Saves a newly created or modified system object to disk.
"""
self.log("save_system",token=token,object_id=object_id)
- self.check_access(token,"save_system")
obj = self.__get_object(object_id)
- return self.api.systems().add(obj,save=True)
+ self.check_access(token,"save_system",obj)
+ if editmode == "new":
+ return self.api.systems().add(obj,save=True,check_for_duplicate_names=True,check_for_duplicate_netinfo=True)
+ elif editmode == "edit":
+ return self.api.systems().add(obj,save=True,check_for_duplicate_netinfo=True)
+ else:
+ return self.api.systems().add(obj,save=True)
+
- def save_repo(self,object_id,token=None):
+ def save_repo(self,object_id,token=None,editmode="bypass"):
"""
Saves a newly created or modified repo object to disk.
"""
self.log("save_repo",object_id=object_id,token=token)
- self.check_access(token,"save_repo")
obj = self.__get_object(object_id)
- return self.api.repos().add(obj,save=True)
+ self.check_access(token,"save_repo",obj)
+ if editmode == "new":
+ return self.api.repos().add(obj,save=True,check_for_duplicate_names=True)
+ else:
+ return self.api.repos().add(obj,save=True)
def copy_distro(self,object_id,newname,token=None):
"""
@@ -874,8 +982,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing distro object handle.
"""
- self.check_access(token, "modify_distro", attribute, arg)
obj = self.__get_object(object_id)
+ self.check_access(token, "modify_distro", obj, attribute)
return self.__call_method(obj, attribute, arg)
def modify_profile(self,object_id,attribute,arg,token):
@@ -883,8 +991,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing profile object handle.
"""
- self.check_access(token, "modify_profile", attribute, arg)
obj = self.__get_object(object_id)
+ self.check_access(token, "modify_profile", obj, attribute)
return self.__call_method(obj, attribute, arg)
def modify_system(self,object_id,attribute,arg,token):
@@ -892,8 +1000,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing system object handle.
"""
- self.check_access(token, "modify_system", attribute, arg)
obj = self.__get_object(object_id)
+ self.check_access(token, "modify_system", obj, attribute)
return self.__call_method(obj, attribute, arg)
def modify_repo(self,object_id,attribute,arg,token):
@@ -901,8 +1009,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing repo object handle.
"""
- self.check_access(token, "modify_repo", attribute, arg)
obj = self.__get_object(object_id)
+ self.check_access(token, "modify_repo", obj, attribute)
return self.__call_method(obj, attribute, arg)
def remove_distro(self,name,token,recursive=1):
@@ -910,7 +1018,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Deletes a distro from a collection. Note that this just requires the name
of the distro, not a handle.
"""
- self.log("remove_distro",name=name,token=token)
+ self.log("remove_distro (%s)" % recursive,name=name,token=token)
self.check_access(token, "remove_distro", name)
rc = self.api._config.distros().remove(name,recursive=True)
return rc
@@ -918,74 +1026,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
def remove_profile(self,name,token,recursive=1):
"""
Deletes a profile from a collection. Note that this just requires the name
- of the profile, not a handle.
"""
- self.log("remove_profile",name=name,token=token)
- self.check_access(token, "remove_profile", name)
- rc = self.api._config.profiles().remove(name,recursive=True)
- return rc
-
- def remove_system(self,name,token):
- """
- Deletes a system from a collection. Note that this just requires the name
- of the system, not a handle.
- """
- self.log("remove_system",name=name,token=token)
- self.check_access(token, "remove_system", name)
- rc = self.api._config.systems().remove(name)
- return rc
-
- def remove_repo(self,name,token):
- """
- Deletes a repo from a collection. Note that this just requires the name
- of the repo, not a handle.
- """
- self.log("remove_repo",name=name,token=token)
- self.check_access(token, "remove_repo", name)
- rc = self.api._config.repos().remove(name)
- return rc
-
- def sync(self,token):
- """
- Applies changes in Cobbler to the filesystem.
- Editing a leaf-node object (like a system) does not require
- this, but if updating a upper-level object or a kickstart file,
- running sync at the end of operations is a good idea. A typical
- cobbler sync may take anywhere between a few seconds and several
- minutes, so user interfaces should be programmed accordingly.
- Future versions of cobbler may understand how to do a cascade sync
- on object edits making explicit calls to sync redundant.
- """
- self.log("sync",token=token)
- self.check_access(token, "sync")
- return self.api.sync()
-
- def reposync(self,repos=[],token=None):
- """
- Updates one or more mirrored yum repositories.
- reposync is very slow and probably should not be used
- through the XMLRPC API, setting up reposync on nightly cron is better.
- """
- self.log("reposync",token=token,name=repos)
- self.check_access(token, "reposync", repos)
- return self.api.reposync(repos)
-
- def import_tree(self,mirror_url,mirror_name,network_root=None,token=None):
- """
- I'm exposing this in the XMLRPC API for consistancy but as this
- can be a very long running operation usage is /not/ recommended.
- It would be better to use the CLI. See documentation in api.py.
- This command may be removed from the API in a future release.
- """
- self.log("import_tree",name=mirror_name,token=token)
- self.check_access(token, "import_tree")
- return self.api.import_tree(mirror_url,mirror_name,network_root)
-
- def get_kickstart_templates(self,token):
- """
- Returns all of the kickstarts that are in use by the system.
- """
- self.log("get_kickstart_templates",token=token)
self.check_access(token, "get_kickstart_templates")
files = {}
for x in self.api.profiles():
@@ -998,7 +1039,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
def read_or_write_kickstart_template(self,kickstart_file,is_read,new_data,token):
- """
+ """
Allows the WebUI to be used as a kickstart file editor. For security
reasons we will only allow kickstart files to be edited if they reside in
/var/lib/cobbler/kickstarts/ or /etc/cobbler. This limits the damage
@@ -1028,9 +1069,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
fileh.close()
return data
else:
- fileh = open(kickstart_file,"w+")
- fileh.write(new_data)
- fileh.close()
+ if new_data == -1:
+ # delete requested
+ if not self.is_kickstart_in_use(kickstart_file,token):
+ os.remove(kickstart_file)
+ else:
+ raise CX(_("attempt to delete in-use file"))
+ else:
+ fileh = open(kickstart_file,"w+")
+ fileh.write(new_data)
+ fileh.close()
return True
diff --git a/cobbler/serializable.py b/cobbler/serializable.py
index ba8f015..ad9e64e 100644
--- a/cobbler/serializable.py
+++ b/cobbler/serializable.py
@@ -13,7 +13,7 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import exceptions
class Serializable:
diff --git a/cobbler/serializer.py b/cobbler/serializer.py
index ae9f18c..ac98412 100644
--- a/cobbler/serializer.py
+++ b/cobbler/serializer.py
@@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import errno
import os
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import fcntl
from cexceptions import *
@@ -59,10 +59,8 @@ def serialize_item(collection, item):
storage_module = __get_storage_module(collection.collection_type())
save_fn = getattr(storage_module, "serialize_item", None)
if save_fn is None:
- # print "DEBUG: WARNING: full serializer"
rc = storage_module.serialize(collection)
else:
- # print "DEBUG: partial serializer"
rc = save_fn(collection,item)
__release_lock()
return rc
@@ -75,10 +73,8 @@ def serialize_delete(collection, item):
storage_module = __get_storage_module(collection.collection_type())
delete_fn = getattr(storage_module, "serialize_delete", None)
if delete_fn is None:
- # print "DEBUG: full delete"
rc = storage_module.serialize(collection)
else:
- # print "DEBUG: partial delete"
rc = delete_fn(collection,item)
__release_lock()
return rc
diff --git a/cobbler/services.py b/cobbler/services.py
new file mode 100644
index 0000000..1cb3864
--- /dev/null
+++ b/cobbler/services.py
@@ -0,0 +1,96 @@
+# Mod Python service functions for Cobbler's public interface
+# (aka cool stuff that works with wget)
+#
+# Copyright 2007 Albert P. Tobey <tobert@gmail.com>
+# additions: 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 exceptions
+import xmlrpclib
+import os
+import traceback
+import string
+import sys
+import time
+
+def log_exc(apache):
+ """
+ Log active traceback to logfile.
+ """
+ (t, v, tb) = sys.exc_info()
+ apache.log_error("Exception occured: %s" % t )
+ apache.log_error("Exception value: %s" % v)
+ apache.log_error("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb))))
+
+class CobblerSvc(object):
+ """
+ Interesting mod python functions are all keyed off the parameter
+ mode, which defaults to index. All options are passed
+ as parameters into the function.
+ """
+ def __init__(self, server=None, apache=None):
+ self.server = server
+ self.apache = apache
+ self.remote = None
+
+ def __xmlrpc_setup(self):
+ """
+ Sets up the connection to the Cobbler XMLRPC server.
+ This is the version that does not require logins.
+ """
+ self.remote = xmlrpclib.Server(self.server, allow_none=True)
+ self.remote.update()
+
+ def modes(self):
+ """
+ Returns a list of methods in this object that can be run as web
+ modes.
+ """
+ retval = list()
+ for m in dir(self):
+ func = getattr( self, m )
+ if hasattr(func, 'exposed') and getattr(func,'exposed'):
+ retval.append(m)
+ return retval
+
+ def index(self,**args):
+ return "no mode specified"
+
+ def ks(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,**rest):
+ """
+ Generate kickstart files...
+ """
+ self.__xmlrpc_setup()
+ data = self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC)
+ return u"%s" % data
+
+ def trig(self,mode="?",profile=None,system=None,REMOTE_ADDR=None,**rest):
+ """
+ Hook to call install triggers.
+ """
+ self.__xmlrpc_setup()
+ ip = REMOTE_ADDR
+ if profile:
+ rc = self.remote.run_install_triggers(mode,"profile",profile,ip)
+ else:
+ rc = self.remote.run_install_triggers(mode,"system",system,ip)
+ return str(rc)
+
+ def nopxe(self,system=None,**rest):
+ self.__xmlrpc_setup()
+ return str(self.remote.disable_netboot(system))
+
+ # =======================================================
+ # list of functions that are callable via mod_python:
+ modes.exposed = False
+ index.exposed = True
+ ks.exposed = True
+ trig.exposed = True
+
+
diff --git a/cobbler/settings.py b/cobbler/settings.py
index 8c8be03..4670ab0 100644
--- a/cobbler/settings.py
+++ b/cobbler/settings.py
@@ -14,7 +14,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import serializable
import utils
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
TESTMODE = False
@@ -22,24 +22,36 @@ TESTMODE = False
# we need.
DEFAULTS = {
- "allow_cgi_mac_registration" : 0,
- "allow_cgi_profile_change" : 0,
+ "allow_duplicate_macs" : 0,
+ "allow_duplicate_ips" : 0,
+ "bind_bin" : "/usr/sbin/named",
"bootloaders" : {
"standard" : "/usr/lib/syslinux/pxelinux.0",
"ia64" : "/var/lib/cobbler/elilo-3.6-ia64.efi"
},
+ "cobbler_master" : '',
"default_kickstart" : "/etc/cobbler/default.ks",
"default_virt_bridge" : "xenbr0",
"default_virt_type" : "auto",
"default_virt_file_size" : "5",
"default_virt_ram" : "512",
+ "default_ownership" : "admin",
"dhcpd_conf" : "/etc/dhcpd.conf",
"dhcpd_bin" : "/usr/sbin/dhcpd",
"dnsmasq_bin" : "/usr/sbin/dnsmasq",
"dnsmasq_conf" : "/etc/dnsmasq.conf",
"httpd_bin" : "/usr/sbin/httpd",
"http_port" : "80",
- "kerberos_realm" : "example.org",
+ "isc_set_host_name" : 0,
+ "ldap_server" : "grimlock.devel.redhat.com",
+ "ldap_base_dn" : "DC=devel,DC=redhat,DC=com",
+ "ldap_port" : 389,
+ "ldap_tls" : "on",
+ "ldap_anonymous_bind" : 1,
+ "ldap_search_bind_dn" : '',
+ "ldap_search_passwd" : '',
+ "ldap_search_prefix" : 'uid=',
+ "kerberos_realm" : "EXAMPLE.COM",
"kernel_options" : {
"lang" : " ",
"text" : None,
@@ -47,18 +59,25 @@ DEFAULTS = {
},
"manage_dhcp" : 0,
"manage_dhcp_mode" : "isc",
+ "manage_dns" : 0,
+ "manage_forward_zones" : [],
+ "manage_reverse_zones" : [],
+ "named_conf" : "/etc/named.conf",
"next_server" : "127.0.0.1",
+ "omapi_enabled" : 0,
+ "omapi_port" : 647,
+ "omshell_bin" : "/usr/bin/omshell",
"pxe_just_once" : 0,
- "run_post_install_trigger" : 0,
+ "register_new_installs" : 0,
+ "run_install_triggers" : 1,
"server" : "127.0.0.1",
"snippetsdir" : "/var/lib/cobbler/snippets",
"syslog_port" : 25150,
- "tftpboot" : "/tftpboot",
"tftpd_bin" : "/usr/sbin/in.tftpd",
"tftpd_conf" : "/etc/xinetd.d/tftp",
"webdir" : "/var/www/cobbler",
"xmlrpc_port" : 25151,
- "xmlrpc_rw_enabled" : 0,
+ "xmlrpc_rw_enabled" : 1,
"xmlrpc_rw_port" : 25152,
"yum_post_install_mirror" : 1,
"yumdownloader_flags" : "--resolve"
@@ -101,7 +120,9 @@ class Settings(serializable.Serializable):
if datastruct is None:
print _("warning: not loading empty structure for %s") % self.filename()
return
+
self._attributes = datastruct
+
return self
def __getattr__(self,name):
@@ -119,11 +140,3 @@ class Settings(serializable.Serializable):
else:
raise AttributeError, name
-if __name__ == "__main__":
- # used to save a settings file to /var/lib/cobbler/settings, for purposes of
- # including a new updated settings file in the RPM without remembering how
- # to format lots of YAML.
- import yaml
- print yaml.dump(DEFAULTS)
-
-
diff --git a/cobbler/templar.py b/cobbler/templar.py
new file mode 100644
index 0000000..fa401b8
--- /dev/null
+++ b/cobbler/templar.py
@@ -0,0 +1,189 @@
+"""
+Cobbler uses Cheetah templates for lots of stuff, but there's
+some additional magic around that to deal with snippets/etc.
+(And it's not spelled wrong!)
+
+Copyright 2008, Red Hat, Inc
+Michael DeHaan <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 os
+import os.path
+import glob
+import utils
+from cexceptions import *
+from Cheetah.Template import Template
+
+class Templar:
+
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ self.config = config
+ self.api = config.api
+ self.settings = config.settings()
+ self.cache = {}
+
+ def render(self, data_input, search_table, out_path, subject=None):
+ """
+ Render data_input back into a file.
+ data_input is either a string or a filename
+ search_table is a hash of metadata keys and values
+ out_path if not-none writes the results to a file
+ (though results are always returned)
+ subject is a profile or system object, if available (for snippet eval)
+ """
+
+ if type(data_input) != str:
+ raw_data = data_input.read()
+ else:
+ raw_data = data_input
+
+ # backward support for Cobbler's legacy (and slightly more readable)
+ # template syntax.
+ raw_data = raw_data.replace("TEMPLATE::","$")
+
+ # replace snippets with the proper Cheetah include directives prior to processing.
+ # see Wiki for full details on how snippets operate.
+ snippet_results = ""
+ for line in raw_data.split("\n"):
+ line = self.replace_snippets(line,subject)
+ snippet_results = "\n".join((snippet_results, line))
+ raw_data = snippet_results
+
+ # HACK: the ksmeta field may contain nfs://server:/mount in which
+ # case this is likely WRONG for kickstart, which needs the NFS
+ # directive instead. Do this to make the templates work.
+ newdata = ""
+ if search_table.has_key("tree") and search_table["tree"].startswith("nfs://"):
+ for line in data.split("\n"):
+ if line.find("--url") != -1 and line.find("url ") != -1:
+ rest = search_table["tree"][6:] # strip off "nfs://" part
+ try:
+ (server, dir) = rest.split(":",2)
+ except:
+ raise CX(_("Invalid syntax for NFS path given during import: %s" % search_table["tree"]))
+ line = "nfs --server %s --dir %s" % (server,dir)
+ # but put the URL part back in so koan can still see
+ # what the original value was
+ line = line + "\n" + "#url --url=%s" % search_table["tree"]
+ newdata = newdata + line + "\n"
+ raw_data = newdata
+
+ # tell Cheetah not to blow up if it can't find a symbol for something
+ raw_data = "#errorCatcher Echo\n" + raw_data
+
+ # now do full templating scan, where we will also templatify the snippet insertions
+ t = Template(source=raw_data, searchList=[search_table])
+ try:
+ data_out = str(t)
+ except:
+ print _("There appears to be an formatting error in the template file.")
+ print _("For completeness, the traceback from Cheetah has been included below.")
+ raise
+
+ # now apply some magic post-filtering that is used by cobbler import and some
+ # other places, but doesn't use Cheetah. Forcing folks to double escape
+ # things would be very unwelcome.
+
+ for x in search_table:
+ if type(search_table[x]) == str:
+ data_out = data_out.replace("@@%s@@" % x, search_table[x])
+
+ # remove leading newlines which apparently breaks AutoYAST ?
+ if data_out.startswith("\n"):
+ data_out = data_out.strip()
+
+ if out_path is not None:
+ utils.mkdir(os.path.dirname(out_path))
+ fd = open(out_path, "w+")
+ fd.write(data_out)
+ fd.close()
+
+ return data_out
+
+ def replace_snippets(self,line,subject):
+ """
+ Replace all SNIPPET:: syntaxes on a line with the
+ results of evaluating the snippet, taking care not
+ to replace tabs with spaces or anything like that
+ """
+ tokens = line.split(None)
+ for t in tokens:
+ if t.startswith("SNIPPET::"):
+ snippet_name = t.replace("SNIPPET::","")
+ line = line.replace(t,self.eval_snippet(snippet_name,subject))
+ return line
+
+ def eval_snippet(self,name,subject):
+ """
+ Replace SNIPPET::foo with contents of files:
+ Use /var/lib/cobbler/snippets/per_system/$name/$sysname
+ /var/lib/cobbler/snippets/per_profile/$name/$proname
+ /var/lib/cobbler/snippets/$name
+ in order... (first one wins)
+ """
+
+ sd = self.settings.snippetsdir
+ default_path = "%s/%s" % (sd,name)
+
+ if subject is None:
+ if os.path.exists(default_path):
+ return self.slurp(default_path)
+ else:
+ return self.slurp(None)
+
+
+ if subject.COLLECTION_TYPE == "system":
+ profile = self.api.find_profile(name=subject.profile)
+ sys_path = "%s/per_system/%s/%s" % (sd,name,subject.name)
+ pro_path = "%s/per_profile/%s/%s" % (sd,name,profile.name)
+ if os.path.exists(sys_path):
+ return self.slurp(sys_path)
+ elif os.path.exists(pro_path):
+ return self.slurp(pro_path)
+ elif os.path.exists(default_path):
+ return self.slurp(default_path)
+ else:
+ return self.slurp(None)
+
+ if subject.COLLECTION_TYPE == "profile":
+ pro_path = "%s/per_profile/%s/%s" % (sd,name,subject.name)
+ if os.path.exists(pro_path):
+ return self.slurp(pro_path)
+ elif os.path.exists(default_path):
+ return self.slurp(default_path)
+ else:
+ return self.slurp(None)
+
+ return self.slurp(None)
+
+ def slurp(self,filename):
+ """
+ Get the contents of a filename but if none is specified
+ just include some generic error text for the rendered
+ template.
+ """
+
+ if filename is None:
+ return "# error: no snippet data found"
+
+ # potentially eliminate a ton of system calls if syncing
+ # thousands of systems that use the same template
+ if self.cache.has_key(filename):
+ return self.cache[filename]
+
+ fd = open(filename,"r")
+ data = fd.read()
+ self.cache[filename] = data
+ fd.close()
+
+ return data
diff --git a/cobbler/utils.py b/cobbler/utils.py
index 069d440..de00150 100644
--- a/cobbler/utils.py
+++ b/cobbler/utils.py
@@ -17,12 +17,19 @@ import os
import re
import socket
import glob
+import random
import sub_process
import shutil
import string
import traceback
+import errno
+import logging
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+
+#placeholder for translation
+def _(foo):
+ return foo
+
MODULE_CACHE = {}
@@ -31,6 +38,18 @@ MODULE_CACHE = {}
_re_kernel = re.compile(r'vmlinuz(.*)')
_re_initrd = re.compile(r'initrd(.*).img')
+def setup_logger(name):
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.INFO)
+ try:
+ ch = logging.FileHandler("/var/log/cobbler/cobbler.log")
+ except:
+ raise CX(_("No write permissions on log file. Are you root?"))
+ ch.setLevel(logging.INFO)
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s")
+ ch.setFormatter(formatter)
+ logger.addHandler(ch)
+ return logger
def log_exc(logger):
"""
@@ -106,6 +125,26 @@ def is_mac(strdata):
return True
return False
+def get_random_mac(api_handle):
+ """
+ Generate a random MAC address.
+ from xend/server/netif.py
+ Generate a random MAC address.
+ Uses OUI 00-16-3E, allocated to
+ Xensource, Inc. Last 3 fields are random.
+ return: MAC address string
+ """
+ mac = [ 0x00, 0x16, 0x3e,
+ random.randint(0x00, 0x7f),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff) ]
+ mac = ':'.join(map(lambda x: "%02x" % x, mac))
+ systems = api_handle.systems()
+ while ( systems.find(mac_address=mac) ):
+ mac = get_random_mac(api_handle)
+
+ return mac
+
def resolve_ip(strdata):
"""
@@ -230,6 +269,22 @@ def find_kickstart(url):
return url
return None
+def input_string_or_list(options,delim=","):
+ """
+ Accepts a delimited list of stuff or a list, but always returns a list.
+ """
+ if options is None or options == "delete":
+ return []
+ elif type(options) == list:
+ return options
+ elif type(options) == str:
+ tokens = options.split(delim)
+ if delim == ",":
+ tokens = [t.lstrip().rstrip() for t in tokens]
+ return tokens
+ else:
+ raise CX(_("invalid input type"))
+
def input_string_or_hash(options,delim=","):
"""
Older cobbler files stored configurations in a flat way, such that all values for strings.
@@ -240,7 +295,7 @@ def input_string_or_hash(options,delim=","):
if options == "<<inherit>>":
options = {}
- if options is None:
+ if options is None or options == "delete":
return (True, {})
elif type(options) == list:
raise CX(_("No idea what to do with list: %s") % options)
@@ -261,7 +316,7 @@ def input_string_or_hash(options,delim=","):
options.pop('',None)
return (True, options)
else:
- raise CX(_("Foreign options type"))
+ raise CX(_("invalid input type"))
def grab_tree(api_handle, obj):
"""
@@ -276,19 +331,14 @@ def grab_tree(api_handle, obj):
results.append(settings)
return results
-def blender(api_handle,remove_hashes, root_obj, blend_cache=None):
+def blender(api_handle,remove_hashes, root_obj):
"""
Combine all of the data in an object tree from the perspective
of that point on the tree, and produce a merged hash containing
consolidated data.
"""
- cache_enabled = False # FIXME: disabled for now as there a few bugs in this impl.
-
blend_key = "%s/%s/%s" % (root_obj.TYPE_NAME, root_obj.name, remove_hashes)
- if cache_enabled and blend_cache is not None:
- if blend_cache.has_key(blend_key):
- return blend_cache[blend_key]
settings = api_handle.settings()
tree = grab_tree(api_handle, root_obj)
@@ -334,8 +384,18 @@ def blender(api_handle,remove_hashes, root_obj, blend_cache=None):
if remove_hashes:
results = flatten(results)
- if cache_enabled and blend_cache is not None:
- blend_cache[blend_key] = results
+ # add in some variables for easier templating
+ # as these variables change based on object type
+ if results.has_key("interfaces"):
+ results["system_name"] = results["name"]
+ results["profile_name"] = results["profile"]
+ results["distro_name"] = results["distro"]
+ elif results.has_key("distro"):
+ results["profile_name"] = results["name"]
+ results["distro_name"] = results["distro"]
+ elif results.has_key("kernel"):
+ results["distro_name"] = results["name"]
+
return results
def flatten(data):
@@ -399,6 +459,24 @@ def __consolidate(node,results):
else:
results[field] = data_item
+ # now if we have any "!foo" results in the list, delete corresponding
+ # key entry "foo", and also the entry "!foo", allowing for removal
+ # of kernel options set in a distro later in a profile, etc.
+
+ hash_removals(results,"kernel_options")
+ hash_removals(results,"ks_meta")
+
+def hash_removals(results,subkey):
+ if not results.has_key(subkey):
+ return
+ scan = results[subkey].keys()
+ for k in scan:
+ if k.startswith("!") and k != "!":
+ remove_me = k[1:]
+ if results[subkey].has_key(remove_me):
+ del results[subkey][remove_me]
+ del results[subkey][k]
+
def hash_to_string(hash):
"""
Convert a hash to a printable string.
@@ -417,7 +495,7 @@ def hash_to_string(hash):
buffer = buffer + str(key) + "=" + str(value) + " "
return buffer
-def run_triggers(ref,globber):
+def run_triggers(ref,globber,additional=[]):
"""
Runs all the trigger scripts in a given directory.
ref can be a cobbler object, if not None, the name will be passed
@@ -434,10 +512,12 @@ def run_triggers(ref,globber):
# skip .rpmnew files that may have been installed
# in the triggers directory
continue
+ arglist = [ file ]
if ref:
- rc = sub_process.call([file,ref.name], shell=False)
- else:
- rc = sub_process.call([file], shell=False)
+ arglist.append(ref.name)
+ for x in additional:
+ arglist.append(x)
+ rc = sub_process.call(arglist, shell=False)
except:
print _("Warning: failed to execute trigger: %s" % file)
continue
@@ -452,9 +532,6 @@ def fix_mod_python_select_submission(repos):
which doesn't seem to happen on all versions of python/mp.
"""
- if str(repos).find("Field(") == -1:
- return repos # no hack needed
-
# should be nice regex, but this is readable :)
repos = str(repos)
repos = repos.replace("'repos'","")
@@ -468,3 +545,306 @@ def fix_mod_python_select_submission(repos):
repos = repos.lstrip().rstrip()
return repos
+def check_dist():
+ """
+ Determines what distro we're running under.
+ """
+ if os.path.exists("/etc/debian_version"):
+ return "debian"
+ else:
+ # valid for Fedora and all Red Hat / Fedora derivatives
+ return "redhat"
+
+def os_release():
+
+ if check_dist() == "redhat":
+
+ if not os.path.exists("/bin/rpm"):
+ return ("unknown", 0)
+ args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release"]
+ cmd = sub_process.Popen(args,shell=False,stdout=sub_process.PIPE)
+ data = cmd.communicate()[0]
+ data = data.rstrip().lower()
+ make = "other"
+ if data.find("redhat") != -1:
+ make = "redhat"
+ elif data.find("centos") != -1:
+ make = "centos"
+ elif data.find("fedora") != -1:
+ make = "fedora"
+ version = data.split("release-")[-1]
+ rest = 0
+ if version.find("-"):
+ parts = version.split("-")
+ version = parts[0]
+ rest = parts[1]
+ return (make, float(version), rest)
+ elif check_dist() == "debian":
+ fd = open("/etc/debian_version")
+ parts = fd.read().split(".")
+ version = parts[0]
+ rest = parts[1]
+ make = "debian"
+ return (make, float(version), rest)
+ else:
+ return ("unknown",0)
+
+def tftpboot_location():
+
+ # if possible, read from TFTP config file to get the location
+ if os.path.exists("/etc/xinetd.d/tftp"):
+ fd = open("/etc/xinetd.d/tftp")
+ lines = fd.read().split("\n")
+ for line in lines:
+ if line.find("server_args") != -1:
+ tokens = line.split(None)
+ mark = False
+ for t in tokens:
+ if t == "-s":
+ mark = True
+ elif mark:
+ return t
+
+ # otherwise, guess based on the distro
+ (make,version,rest) = os_release()
+ if make == "fedora" and version >= 9:
+ return "/var/lib/tftpboot"
+ return "/tftpboot"
+
+def linkfile(src, dst):
+ """
+ Attempt to create a link dst that points to src. Because file
+ systems suck we attempt several different methods or bail to
+ copyfile()
+ """
+
+ try:
+ return os.link(src, dst)
+ except (IOError, OSError):
+ pass
+
+ try:
+ return os.symlink(src, dst)
+ except (IOError, OSError):
+ pass
+
+ return copyfile(src, dst)
+
+def copyfile(src,dst):
+ try:
+ return shutil.copyfile(src,dst)
+ except:
+ if not os.access(src,os.R_OK):
+ raise CX(_("Cannot read: %s") % src)
+ if not os.path.samefile(src,dst):
+ # accomodate for the possibility that we already copied
+ # the file as a symlink/hardlink
+ raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst})
+
+def rmfile(path):
+ try:
+ os.unlink(path)
+ return True
+ except OSError, ioe:
+ if not ioe.errno == errno.ENOENT: # doesn't exist
+ traceback.print_exc()
+ raise CX(_("Error deleting %s") % path)
+ return True
+
+def rmtree_contents(path):
+ what_to_delete = glob.glob("%s/*" % path)
+ for x in what_to_delete:
+ rmtree(x)
+
+def rmtree(path):
+ try:
+ if os.path.isfile(path):
+ return rmfile(path)
+ else:
+ return shutil.rmtree(path,ignore_errors=True)
+ except OSError, ioe:
+ traceback.print_exc()
+ if not ioe.errno == errno.ENOENT: # doesn't exist
+ raise CX(_("Error deleting %s") % path)
+ return True
+
+def mkdir(path,mode=0777):
+ try:
+ return os.makedirs(path,mode)
+ except OSError, oe:
+ if not oe.errno == 17: # already exists (no constant for 17?)
+ traceback.print_exc()
+ print oe.errno
+ raise CX(_("Error creating") % path)
+
+def set_repos(self,repos,bypass_check=False):
+ # WARNING: hack
+ repos = fix_mod_python_select_submission(repos)
+
+ # allow the magic inherit string to persist
+ if repos == "<<inherit>>":
+ # FIXME: this is not inheritable in the WebUI presently ?
+ self.repos = "<<inherit>>"
+ return
+
+ # store as an array regardless of input type
+ if repos is None:
+ repolist = []
+ elif type(repos) != list:
+ # allow backwards compatibility support of string input
+ repolist = repos.split(None)
+ else:
+ repolist = repos
+
+ # make sure there are no empty strings
+ try:
+ repolist.remove('')
+ except:
+ pass
+
+ self.repos = []
+
+ # if any repos don't exist, fail the set operation
+ # unless called from the deserializer stage in which
+ # case we have a soft error that check can report
+ ok = True
+ for r in repolist:
+ if bypass_check:
+ self.repos.append(r)
+ else:
+ if self.config.repos().find(name=r) is not None:
+ self.repos.append(r)
+ else:
+ raise CX(_("repo %s is not defined") % r)
+
+ return True
+
+def set_virt_file_size(self,num):
+ """
+ For Virt only.
+ Specifies the size of the virt image in gigabytes.
+ Older versions of koan (x<0.6.3) interpret 0 as "don't care"
+ Newer versions (x>=0.6.4) interpret 0 as "no disks"
+ """
+ # num is a non-negative integer (0 means default)
+ # can also be a comma seperated list -- for usage with multiple disks
+
+ if num == "<<inherit>>":
+ self.virt_file_size = "<<inherit>>"
+ return True
+
+ if type(num) == str and num.find(",") != -1:
+ tokens = num.split(",")
+ for t in tokens:
+ # hack to run validation on each
+ self.set_virt_file_size(t)
+ # if no exceptions raised, good enough
+ self.virt_file_size = num
+ return True
+
+ try:
+ inum = int(num)
+ if inum != float(num):
+ return CX(_("invalid virt file size"))
+ if inum >= 0:
+ self.virt_file_size = inum
+ return True
+ raise CX(_("invalid virt file size"))
+ except:
+ raise CX(_("invalid virt file size"))
+ return True
+
+def set_virt_ram(self,num):
+ """
+ For Virt only.
+ Specifies the size of the Virt RAM in MB.
+ 0 tells Koan to just choose a reasonable default.
+ """
+
+ if num == "<<inherit>>":
+ self.virt_ram = "<<inherit>>"
+ return True
+
+ # num is a non-negative integer (0 means default)
+ try:
+ inum = int(num)
+ if inum != float(num):
+ return CX(_("invalid virt ram size"))
+ if inum >= 0:
+ self.virt_ram = inum
+ return True
+ return CX(_("invalid virt ram size"))
+ except:
+ return CX(_("invalid virt ram size"))
+ return True
+
+def set_virt_type(self,vtype):
+ """
+ Virtualization preference, can be overridden by koan.
+ """
+
+ if vtype == "<<inherit>>":
+ self.virt_type == "<<inherit>>"
+ return True
+
+ if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]:
+ raise CX(_("invalid virt type"))
+ self.virt_type = vtype
+ return True
+
+def set_virt_bridge(self,vbridge):
+ """
+ The default bridge for all virtual interfaces under this profile.
+ """
+ self.virt_bridge = vbridge
+ return True
+
+def set_virt_path(self,path):
+ """
+ Virtual storage location suggestion, can be overriden by koan.
+ """
+ self.virt_path = path
+ return True
+
+def set_virt_cpus(self,num):
+ """
+ For Virt only. Set the number of virtual CPUs to give to the
+ virtual machine. This is fed to virtinst RAW, so cobbler
+ will not yelp if you try to feed it 9999 CPUs. No formatting
+ like 9,999 please :)
+ """
+ if num == "<<inherit>>":
+ self.virt_cpus = "<<inherit>>"
+ return True
+
+ try:
+ num = int(str(num))
+ except:
+ raise CX(_("invalid number of virtual CPUs"))
+
+ self.virt_cpus = num
+ return True
+
+def get_kickstart_templates(api):
+ files = {}
+ for x in api.profiles():
+ if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<<inherit>>":
+ if os.path.exists(x.kickstart):
+ files[x.kickstart] = 1
+ for x in api.systems():
+ if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<<inherit>>":
+ if os.path.exists(x.kickstart):
+ files[x.kickstart] = 1
+ for x in glob.glob("/var/lib/cobbler/kickstarts/*"):
+ files[x] = 1
+ for x in glob.glob("/etc/cobbler/*.ks"):
+ files[x] = 1
+
+ return files.keys()
+
+
+
+if __name__ == "__main__":
+ # print redhat_release()
+ print tftpboot_location()
+
diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py
index 9a0cf90..d272281 100644
--- a/cobbler/webui/CobblerWeb.py
+++ b/cobbler/webui/CobblerWeb.py
@@ -60,6 +60,9 @@ class CobblerWeb(object):
# validate that our token is still good
try:
self.remote.token_check(self.token)
+ self.username = self.remote.get_user_from_token(self.token)
+ # ensure config is up2date
+ self.remote.update(self.token)
return True
except Exception, e:
if str(e).find("invalid token") != -1:
@@ -78,6 +81,8 @@ class CobblerWeb(object):
log_exc(self.apache)
return False
self.password = None # don't need it anymore, get rid of it
+ # ensure configuration is up2date
+ self.remote.update(self.token)
return True
# login failed
@@ -162,15 +167,27 @@ class CobblerWeb(object):
input_distro = None
if name is not None:
input_distro = self.remote.get_distro(name, True)
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_distro",name)
+ else:
+ can_edit = self.remote.check_access_no_fail(self.token,"new_distro",None)
+
+ if not can_edit:
+ return self.__render('message.tmpl', {
+ 'message1' : "Access denied.",
+ 'message2' : "You do not have permission to create new objects."
+ })
+
return self.__render( 'distro_edit.tmpl', {
+ 'user' : self.username,
'edit' : True,
+ 'editable' : can_edit,
'distro': input_distro,
} )
def distro_save(self,name=None,oldname=None,new_or_edit=None,editmode='edit',kernel=None,
- initrd=None,kopts=None,ksmeta=None,arch=None,breed=None,
- delete1=None,delete2=None,**args):
+ initrd=None,kopts=None,ksmeta=None,owners=None,arch=None,breed=None,
+ delete1=None,delete2=None,recursive=False,**args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -182,8 +199,12 @@ class CobblerWeb(object):
# handle deletes as a special case
if new_or_edit == 'edit' and delete1 and delete2:
- try:
- self.remote.remove_distro(name,self.token,1) # recursive
+ try:
+ if recursive is None:
+ self.remote.remove_distro(name,self.token,False)
+ else:
+ self.remote.remove_distro(name,self.token,True)
+
except Exception, e:
return self.error_page("could not delete %s, %s" % (name,str(e)))
return self.distro_list()
@@ -220,11 +241,14 @@ class CobblerWeb(object):
self.remote.modify_distro(distro, 'kopts', kopts, self.token)
if ksmeta:
self.remote.modify_distro(distro, 'ksmeta', ksmeta, self.token)
+ if owners:
+ self.remote.modify_distro(distro, 'owners', owners, self.token)
if arch:
self.remote.modify_distro(distro, 'arch', arch, self.token)
if breed:
self.remote.modify_distro(distro, 'breed', breed, self.token)
- self.remote.save_distro(distro, self.token)
+ # now time to save, do we want to run duplication checks?
+ self.remote.save_distro(distro, self.token, editmode)
except Exception, e:
log_exc(self.apache)
return self.error_page("Error while saving distro: %s" % str(e))
@@ -288,8 +312,9 @@ class CobblerWeb(object):
def system_save(self,name=None,oldname=None,editmode="edit",profile=None,
new_or_edit=None,
- kopts=None, ksmeta=None, server_override=None, netboot='n',
- delete1=None, delete2=None, **args):
+ kopts=None, ksmeta=None, owners=None, server_override=None, netboot='n',
+ virtpath=None,virtram=None,virttype=None,virtcpus=None,virtfilesize=None,delete1=None, delete2=None, **args):
+
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -332,11 +357,26 @@ class CobblerWeb(object):
self.remote.modify_system(system, 'kopts', kopts, self.token)
if ksmeta:
self.remote.modify_system(system, 'ksmeta', ksmeta, self.token)
+ if owners:
+ self.remote.modify_system(system, 'owners', owners, self.token)
if netboot:
self.remote.modify_system(system, 'netboot-enabled', netboot, self.token)
if server_override:
self.remote.modify_system(system, 'server', server_override, self.token)
+ if virtfilesize:
+ self.remote.modify_system(system, 'virt-file-size', virtfilesize, self.token)
+ if virtcpus:
+ self.remote.modify_system(system, 'virt-cpus', virtcpus, self.token)
+ if virtram:
+ self.remote.modify_system(system, 'virt-ram', virtram, self.token)
+ if virttype:
+ self.remote.modify_system(system, 'virt-type', virttype, self.token)
+
+ if virtpath:
+ self.remote.modify_system(system, 'virt-path', virtpath, self.token)
+
+
for x in range(0,7):
interface = "intf%s" % x
macaddress = args.get("macaddress-%s" % interface, "")
@@ -364,8 +404,7 @@ class CobblerWeb(object):
mods["gateway-%s" % interface] = gateway
self.remote.modify_system(system,'modify-interface', mods, self.token)
- # now commit the edits
- self.remote.save_system( system, self.token)
+ self.remote.save_system(system, self.token, editmode)
except Exception, e:
log_exc(self.apache)
@@ -390,9 +429,20 @@ class CobblerWeb(object):
input_system = None
if name is not None:
input_system = self.remote.get_system(name,True)
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_system",name)
+ else:
+ can_edit = self.remote.check_access_no_fail(self.token,"new_system",None)
+ if not can_edit:
+ return self.__render('message.tmpl', {
+ 'message1' : "Access denied.",
+ 'message2' : "You do not have permission to create new objects."
+ })
+
return self.__render( 'system_edit.tmpl', {
+ 'user' : self.username,
'edit' : True,
+ 'editable' : can_edit,
'system': input_system,
'profiles': self.remote.get_profiles()
} )
@@ -427,10 +477,21 @@ class CobblerWeb(object):
input_profile = None
if name is not None:
- input_profile = self.remote.get_profile(name,True)
+ input_profile = self.remote.get_profile(name,True)
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_profile",name)
+ else:
+ can_edit = self.remote.check_access_no_fail(self.token,"new_profile",None)
+ if not can_edit:
+ return self.__render('message.tmpl', {
+ 'message1' : "Access denied.",
+ 'message2' : "You do not have permission to create new objects."
+ })
+
return self.__render( 'profile_edit.tmpl', {
+ 'user' : self.username,
'edit' : True,
+ 'editable' : can_edit,
'profile': input_profile,
'distros': self.remote.get_distros(),
'profiles': self.remote.get_profiles(),
@@ -441,9 +502,9 @@ class CobblerWeb(object):
def profile_save(self,new_or_edit=None,editmode='edit',name=None,oldname=None,
distro=None,kickstart=None,kopts=None,
- ksmeta=None,virtfilesize=None,virtram=None,virttype=None,
+ ksmeta=None,owners=None,virtfilesize=None,virtram=None,virttype=None,
virtpath=None,repos=None,dhcptag=None,delete1=None,delete2=None,
- parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None,**args):
+ parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None,recursive=False,**args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -463,7 +524,11 @@ class CobblerWeb(object):
# handle deletes as a special case
if new_or_edit == 'edit' and delete1 and delete2:
try:
- self.remote.remove_profile(name,self.token,1)
+ if recursive:
+ self.remote.remove_profile(name,self.token,True)
+ else:
+ self.remote.remove_profile(name,self.token,False)
+
except Exception, e:
return self.error_page("could not delete %s, %s" % (name,str(e)))
return self.profile_list()
@@ -495,6 +560,8 @@ class CobblerWeb(object):
self.remote.modify_profile(profile, 'kickstart', kickstart, self.token)
if kopts:
self.remote.modify_profile(profile, 'kopts', kopts, self.token)
+ if owners:
+ self.remote.modify_profile(profile, 'owners', owners, self.token)
if ksmeta:
self.remote.modify_profile(profile, 'ksmeta', ksmeta, self.token)
if virtfilesize:
@@ -523,7 +590,7 @@ class CobblerWeb(object):
if dhcptag:
self.remote.modify_profile(profile, 'dhcp-tag', dhcptag, self.token)
- self.remote.save_profile(profile,self.token)
+ self.remote.save_profile(profile,self.token, editmode)
except Exception, e:
log_exc(self.apache)
return self.error_page("Error while saving profile: %s" % str(e))
@@ -565,13 +632,24 @@ class CobblerWeb(object):
input_repo = None
if name is not None:
input_repo = self.remote.get_repo(name, True)
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_repo",name)
+ else:
+ can_edit = self.remote.check_access_no_fail(self.token,"new_repo",None)
+ if not can_edit:
+ return self.__render('message.tmpl', {
+ 'message1' : "Access denied.",
+ 'message2' : "You do not have permission to create new objects."
+ })
+
return self.__render( 'repo_edit.tmpl', {
+ 'user' : self.username,
'repo': input_repo,
+ 'editable' : can_edit
} )
def repo_save(self,name=None,oldname=None,new_or_edit=None,editmode="edit",
- mirror=None,keep_updated=None,priority=99,
+ mirror=None,owners=None,keep_updated=None,mirror_locally=0,priority=99,
rpm_list=None,createrepo_flags=None,arch=None,yumopts=None,
delete1=None,delete2=None,**args):
if not self.__xmlrpc_setup():
@@ -615,6 +693,7 @@ class CobblerWeb(object):
self.remote.modify_repo(repo, 'mirror', mirror, self.token)
self.remote.modify_repo(repo, 'keep-updated', keep_updated, self.token)
self.remote.modify_repo(repo, 'priority', priority, self.token)
+ self.remote.modify_repo(repo, 'mirror-locally', mirror_locally, self.token)
if rpm_list:
self.remote.modify_repo(repo, 'rpm-list', rpm_list, self.token)
@@ -624,8 +703,10 @@ class CobblerWeb(object):
self.remote.modify_repo(repo, 'arch', arch, self.token)
if yumopts:
self.remote.modify_repo(repo, 'yumopts', yumopts, self.token)
+ if owners:
+ self.remote.modify_repo(repo, 'owners', owners, self.token)
- self.remote.save_repo(repo, self.token)
+ self.remote.save_repo(repo, self.token, editmode)
except Exception, e:
log_exc(self.apache)
@@ -650,22 +731,49 @@ class CobblerWeb(object):
'ksfiles': self.remote.get_kickstart_templates(self.token)
} )
+ def ksfile_new(self, name=None,**spam):
+
+
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+
+ can_edit = self.remote.check_access_no_fail(self.token,"add_kickstart",name)
+ return self.__render( 'ksfile_new.tmpl', {
+ 'editable' : can_edit,
+ 'ksdata': ''
+ } )
+
+
+
def ksfile_edit(self, name=None,**spam):
+
+
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
+
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_kickstart",name)
return self.__render( 'ksfile_edit.tmpl', {
'name': name,
+ 'deleteable' : not self.remote.is_kickstart_in_use(name,self.token),
+ 'editable' : can_edit,
'ksdata': self.remote.read_or_write_kickstart_template(name,True,"",self.token)
} )
- def ksfile_save(self, name=None, ksdata=None, **args):
+ def ksfile_save(self, name=None, ksdata=None, delete1=None, delete2=None, isnew=None, **args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
+
+
try:
- self.remote.read_or_write_kickstart_template(name,False,ksdata,self.token)
+ if delete1 and delete2:
+ self.remote.read_or_write_kickstart_template(name,False,-1,self.token)
+ if isnew is not None:
+ name = "/var/lib/cobbler/kickstarts/" + name
+ if not delete1 and not delete2:
+ self.remote.read_or_write_kickstart_template(name,False,ksdata,self.token)
except Exception, e:
return self.error_page("An error occurred while trying to save kickstart file %s:<br/><br/>%s" % (name,str(e)))
- return self.ksfile_edit(name=name)
+ return self.ksfile_list()
# ------------------------------------------------------------------------ #
# Miscellaneous
@@ -675,6 +783,13 @@ class CobblerWeb(object):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
+ can_edit = self.remote.check_access_no_fail(self.token,"sync",None)
+ if not can_edit:
+ return self.__render('message.tmpl', {
+ 'message1' : "Access denied.",
+ 'message2' : "You do not have permission to create new objects."
+ })
+
try:
rc = self.remote.sync(self.token)
if not rc:
@@ -739,6 +854,7 @@ class CobblerWeb(object):
settings_view.exposed = True
ksfile_edit.exposed = True
+ ksfile_new.exposed = True
ksfile_save.exposed = True
ksfile_list.exposed = True
diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py
deleted file mode 100644
index 7ef1b0a..0000000
--- a/cobbler/webui/master.py
+++ /dev/null
@@ -1,262 +0,0 @@
-#!/usr/bin/env python
-
-
-
-
-##################################################
-## DEPENDENCIES
-import sys
-import os
-import os.path
-from os.path import getmtime, exists
-import time
-import types
-import __builtin__
-from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion
-from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple
-from Cheetah.Template import Template
-from Cheetah.DummyTransaction import DummyTransaction
-from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
-from Cheetah.CacheRegion import CacheRegion
-import Cheetah.Filters as Filters
-import Cheetah.ErrorCatchers as ErrorCatchers
-
-##################################################
-## MODULE CONSTANTS
-try:
- True, False
-except NameError:
- True, False = (1==1), (1==0)
-VFFSL=valueFromFrameOrSearchList
-VFSL=valueFromSearchList
-VFN=valueForName
-currentTime=time.time
-__CHEETAH_version__ = '2.0.1'
-__CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0)
-__CHEETAH_genTime__ = 1207686338.5978019
-__CHEETAH_genTimestamp__ = 'Tue Apr 8 16:25:38 2008'
-__CHEETAH_src__ = 'webui_templates/master.tmpl'
-__CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008'
-__CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine'
-
-if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
- raise AssertionError(
- 'This template was compiled with Cheetah version'
- ' %s. Templates compiled before version %s must be recompiled.'%(
- __CHEETAH_version__, RequiredCheetahVersion))
-
-##################################################
-## CLASSES
-
-class master(Template):
-
- ##################################################
- ## CHEETAH GENERATED METHODS
-
-
- def __init__(self, *args, **KWs):
-
- Template.__init__(self, *args, **KWs)
- if not self._CHEETAH__instanceInitialized:
- cheetahKWArgs = {}
- allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split()
- for k,v in KWs.items():
- if k in allowedKWs: cheetahKWArgs[k] = v
- self._initCheetahInstance(**cheetahKWArgs)
-
-
- def body(self, **KWS):
-
-
-
- ## CHEETAH: generated from #block body at line 53, col 1.
- trans = KWS.get("trans")
- if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
- trans = self.transaction # is None unless self.awake() was called
- if not trans:
- trans = DummyTransaction()
- _dummyTrans = True
- else: _dummyTrans = False
- write = trans.response().write
- SL = self._CHEETAH__searchList
- _filter = self._CHEETAH__currentFilter
-
- ########################################
- ## START - generated method body
-
- write('''
- <h1 style="color: red;">Template Failure</h1>
-
-''')
-
- ########################################
- ## END - generated method body
-
- return _dummyTrans and trans.response().getvalue() or ""
-
-
- def respond(self, trans=None):
-
-
-
- ## CHEETAH: main method generated for this template
- if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
- trans = self.transaction # is None unless self.awake() was called
- if not trans:
- trans = DummyTransaction()
- _dummyTrans = True
- else: _dummyTrans = False
- write = trans.response().write
- SL = self._CHEETAH__searchList
- _filter = self._CHEETAH__currentFilter
-
- ########################################
- ## START - generated method body
-
- write('''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
-<head>
- <title>''')
- _v = VFFSL(SL,"title",True) # '$title' on line 5, col 12
- if _v is not None: write(_filter(_v, rawExpr='$title')) # from line 5, col 12.
- write('''</title>
- <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
-
- <link rel="stylesheet" type="text/css" media="all" href="/cobbler/webui/style.css" />
- <link rel="stylesheet" type="text/css" media="all" href="/cobbler/webui/cobblerweb.css" />
-
-<script language="Javascript" src="/cobbler/webui/cobbler.js" ></script>
-
-</head>
-
-
-<body onload="global_onload();">
-
-<div id="wrap">
- <h1 id="masthead">
- <a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 20, col 18
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 20, col 18.
- write('''/index">
- <img alt="Cobbler Logo"
- src="/cobbler/webui/logo-cobbler.png"/>
- </a>
- </h1>
-</div>
-
-<div id="main">
-
-<div id="sidebar">
- <ul id="nav">
- <li><a href="/cobbler/webui/wui.html" class="menu">Docs</a></li>
- <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('''?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('''?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('''?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('''?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('''?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('''?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('''?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('''?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('''?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('''?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('''?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('''?mode=sync">Sync</a></li>
- </ul>
-</div>
-
-<div id="content">
-''')
- self.body(trans=trans)
- write('''</div><!-- content -->
-</div><!-- main -->
-
-</body>
-</html>
-''')
-
- ########################################
- ## END - generated method body
-
- return _dummyTrans and trans.response().getvalue() or ""
-
- ##################################################
- ## CHEETAH GENERATED ATTRIBUTES
-
-
- _CHEETAH__instanceInitialized = False
-
- _CHEETAH_version = __CHEETAH_version__
-
- _CHEETAH_versionTuple = __CHEETAH_versionTuple__
-
- _CHEETAH_genTime = __CHEETAH_genTime__
-
- _CHEETAH_genTimestamp = __CHEETAH_genTimestamp__
-
- _CHEETAH_src = __CHEETAH_src__
-
- _CHEETAH_srcLastModified = __CHEETAH_srcLastModified__
-
- title = "Cobbler Web Interface"
-
- _mainCheetahMethod_for_master= 'respond'
-
-## END CLASS DEFINITION
-
-if not hasattr(master, '_initCheetahAttributes'):
- templateAPIClass = getattr(master, '_CHEETAH_templateClass', Template)
- templateAPIClass._addCheetahPlumbingCodeToClass(master)
-
-
-# CHEETAH was developed by Tavis Rudd and Mike Orr
-# with code, advice and input from many other volunteers.
-# For more information visit http://www.CheetahTemplate.org/
-
-##################################################
-## if run from command line:
-if __name__ == '__main__':
- from Cheetah.TemplateCmdLineIface import CmdLineIface
- CmdLineIface(templateObj=master()).run()
-
-
diff --git a/cobbler/yaml/__init__.py b/cobbler/yaml/__init__.py
index 419d1f3..bd21b40 100644
--- a/cobbler/yaml/__init__.py
+++ b/cobbler/yaml/__init__.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
__version__ = "0.32"
from load import loadFile, load, Parser, l
from dump import dump, dumpToFile, Dumper, d
diff --git a/cobbler/yaml/dump.py b/cobbler/yaml/dump.py
index b8e9d79..eb34955 100644
--- a/cobbler/yaml/dump.py
+++ b/cobbler/yaml/dump.py
@@ -1,3 +1,10 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
+
import types
import string
from types import StringType, UnicodeType, IntType, FloatType
diff --git a/cobbler/yaml/implicit.py b/cobbler/yaml/implicit.py
index 6172564..49d65e0 100644
--- a/cobbler/yaml/implicit.py
+++ b/cobbler/yaml/implicit.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
import re
import string
from timestamp import timestamp, matchTime
diff --git a/cobbler/yaml/inline.py b/cobbler/yaml/inline.py
index 8e647de..d4f6439 100644
--- a/cobbler/yaml/inline.py
+++ b/cobbler/yaml/inline.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
import re
import string
diff --git a/cobbler/yaml/klass.py b/cobbler/yaml/klass.py
index edcf5a8..c182fcf 100644
--- a/cobbler/yaml/klass.py
+++ b/cobbler/yaml/klass.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
import new
import re
diff --git a/cobbler/yaml/load.py b/cobbler/yaml/load.py
index 259178d..54931d6 100644
--- a/cobbler/yaml/load.py
+++ b/cobbler/yaml/load.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
import re, string
from implicit import convertImplicit
from inline import InlineTokenizer
diff --git a/cobbler/yaml/ordered_dict.py b/cobbler/yaml/ordered_dict.py
index b3788b7..5bc2e3e 100644
--- a/cobbler/yaml/ordered_dict.py
+++ b/cobbler/yaml/ordered_dict.py
@@ -1,3 +1,10 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
+
# This is extremely crude implementation of an OrderedDict.
# If you know of a better implementation, please send it to
# the author Steve Howell. You can find my email via
diff --git a/cobbler/yaml/redump.py b/cobbler/yaml/redump.py
index 56ea958..eefd68e 100644
--- a/cobbler/yaml/redump.py
+++ b/cobbler/yaml/redump.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
from ordered_dict import OrderedDict
from load import Parser
from dump import Dumper
diff --git a/cobbler/yaml/stream.py b/cobbler/yaml/stream.py
index cc78c4b..dcd65c3 100644
--- a/cobbler/yaml/stream.py
+++ b/cobbler/yaml/stream.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
import re
import string
diff --git a/cobbler/yaml/timestamp.py b/cobbler/yaml/timestamp.py
index abcb2e6..5c522f6 100644
--- a/cobbler/yaml/timestamp.py
+++ b/cobbler/yaml/timestamp.py
@@ -1,3 +1,10 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
+
import time, re, string
from types import ListType, TupleType
diff --git a/cobbler/yaml/ypath.py b/cobbler/yaml/ypath.py
index 51d9d2f..b183a23 100644
--- a/cobbler/yaml/ypath.py
+++ b/cobbler/yaml/ypath.py
@@ -1,3 +1,10 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
+
from types import ListType, StringType, IntType, DictType, InstanceType
import re
from urllib import quote
diff --git a/cobbler/yumgen.py b/cobbler/yumgen.py
new file mode 100644
index 0000000..8c10be3
--- /dev/null
+++ b/cobbler/yumgen.py
@@ -0,0 +1,110 @@
+"""
+Builds out filesystem trees/data based on the object tree.
+This is the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <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 os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+class YumGen:
+
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.systems = config.systems()
+ self.settings = config.settings()
+ self.repos = config.repos()
+ self.templar = templar.Templar(config)
+
+ def retemplate_all_yum_repos(self):
+ for p in self.profiles:
+ self.retemplate_yum_repos(p,True)
+ for system in self.systems:
+ self.retemplate_yum_repos(system,False)
+
+ def retemplate_yum_repos(self,obj,is_profile):
+ """
+ Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo
+ and also potentially in listed in the source_repos structure of the distro object, however
+ these files have server URLs in them that must be templated out. This function does this.
+ """
+ blended = utils.blender(self.api, False, obj)
+
+ if is_profile:
+ outseg = "repos_profile"
+ else:
+ outseg = "repos_system"
+
+ input_files = []
+
+ # chance old versions from upgrade do not have a source_repos
+ # workaround for user bug
+ if not blended.has_key("source_repos"):
+ blended["source_repos"] = []
+
+ # tack on all the install source repos IF there is more than one.
+ # this is basically to support things like RHEL5 split trees
+ # if there is only one, then there is no need to do this.
+
+ for r in blended["source_repos"]:
+ filename = self.settings.webdir + "/" + "/".join(r[0].split("/")[4:])
+ input_files.append(filename)
+
+ for repo in blended["repos"]:
+ input_files.append(os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo"))
+
+ for infile in input_files:
+ if infile.find("ks_mirror") == -1:
+ dispname = infile.split("/")[-2]
+ else:
+ dispname = infile.split("/")[-1].replace(".repo","")
+ confdir = os.path.join(self.settings.webdir, outseg)
+ outdir = os.path.join(confdir, blended["name"])
+ utils.mkdir(outdir)
+ try:
+ infile_h = open(infile)
+ except:
+ # file does not exist and the user needs to run reposync
+ # before we will use this, cobbler check will mention
+ # this problem
+ continue
+ infile_data = infile_h.read()
+ infile_h.close()
+ outfile = os.path.join(outdir, "%s.repo" % (dispname))
+ self.templar.render(infile_data, blended, outfile, None)
+
+
diff --git a/config/cobbler_svc.conf b/config/cobbler_svc.conf
new file mode 100644
index 0000000..f0c86de
--- /dev/null
+++ b/config/cobbler_svc.conf
@@ -0,0 +1,12 @@
+# This configuration file allows cobbler data
+# to be accessed over HTTP.
+
+# mod_python WebUI/services
+
+<Directory "/var/www/cobbler/svc/">
+ SetHandler mod_python
+ PythonHandler services
+ PythonDebug on
+</Directory>
+
+
diff --git a/config/cobblerd b/config/cobblerd
index 6b52956..7d6d571 100755
--- a/config/cobblerd
+++ b/config/cobblerd
@@ -25,12 +25,23 @@
# Sanity checks.
[ -x /usr/bin/cobblerd ] || exit 0
+DEBIAN_VERSION=/etc/debian_version
# Source function library.
-. /etc/rc.d/init.d/functions
+if [ -e $DEBIAN_VERSION ]; then
+
+ . /etc/init.d/functions
+else
+ . /etc/rc.d/init.d/functions
+fi
SERVICE=cobblerd
PROCESS=cobblerd
CONFIG_ARGS=" "
+if [ -e $DEBIAN_VERSION ]; then
+ LOCKFILE=/var/lock/$SERVICE
+else
+ LOCKFILE=/var/lock/subsys/$SERVICE
+fi
RETVAL=0
@@ -39,7 +50,7 @@ start() {
daemon --check $SERVICE $PROCESS --daemon $CONFIG_ARGS
RETVAL=$?
echo
- [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE
+ [ $RETVAL -eq 0 ] && touch $LOCKFILE
return $RETVAL
}
@@ -49,7 +60,7 @@ stop() {
RETVAL=$?
echo
if [ $RETVAL -eq 0 ]; then
- rm -f /var/lock/subsys/$SERVICE
+ rm -f $LOCKFILE
rm -f /var/run/$SERVICE.pid
fi
}
@@ -69,7 +80,7 @@ case "$1" in
RETVAL=$?
;;
condrestart)
- [ -f /var/lock/subsys/$SERVICE ] && restart || :
+ [ -f $LOCKFILE ] && restart || :
;;
reload)
echo "can't reload configuration, you have to restart it"
diff --git a/config/cobblerd_rotate b/config/cobblerd_rotate
index 0e4bcbf..e739c47 100644
--- a/config/cobblerd_rotate
+++ b/config/cobblerd_rotate
@@ -5,7 +5,7 @@
weekly
postrotate
if [ -f /var/lock/subsys/cobblerd ]; then
- /etc/init.d/cobblerd condrestart
+ /etc/init.d/cobblerd condrestart > /dev/null
fi
endscript
}
@@ -17,7 +17,14 @@
weekly
postrotate
if [ -f /var/lock/subsys/cobblerd ]; then
- /etc/init.d/cobblerd condrestart
+ /etc/init.d/cobblerd condrestart > /dev/null
fi
endscript
}
+
+/var/log/cobbler/install.log {
+ missingok
+ notifempty
+ rotate 4
+ weekly
+}
diff --git a/config/modules.conf b/config/modules.conf
index 2d60d21..8863e02 100644
--- a/config/modules.conf
+++ b/config/modules.conf
@@ -6,7 +6,13 @@ system = serializer_yaml
repo = serializer_yaml
[authentication]
-module = authn_configfile
+module = authn_denyall
[authorization]
-module = authn_allowall
+module = authz_allowall
+
+[dns]
+module = manage_bind
+
+[dhcp]
+module = manage_isc
diff --git a/config/settings b/config/settings
index ba709ae..b1f7bd7 100644
--- a/config/settings
+++ b/config/settings
@@ -1,39 +1,186 @@
---
-allow_cgi_mac_registration: 0
-allow_cgi_profile_change: 0
+# cobbler settings file
+# run "cobbler sync" after making changes
+# (it's a good idea to make backups too)
+#
+# if 1, cobbler will allow insertions of system records that duplicate
+# the mac address information of other system records. In general,
+# this is undesirable.
+allow_duplicate_macs: 0
+
+# if 1, cobbler will allow insertions of system records that duplicate
+# the ip address information of other system records. In general,
+# this is undesirable.
+allow_duplicate_ips: 0
+
+# the path to BIND's executable for this distribution.
+bind_bin: /usr/sbin/named
+
+# where to find various bootloaders on the filesystem
bootloaders:
ia64: /var/lib/cobbler/elilo-3.6-ia64.efi
standard: /usr/lib/syslinux/pxelinux.0
+
+# if no kickstart is specified, use this template (FIXME)
default_kickstart: /etc/cobbler/default.ks
+
+# for libvirt based installs in koan, if no virt bridge
+# is specified, which bridge do we try?
default_virt_bridge: xenbr0
-default_virt_type: auto
+
+# if koan is invoked without --virt-type and no virt-type
+# is set on the profile/system, what virtualization type
+# should be assumed? Values: xenpv, xenfv, qemu, vmware
+default_virt_type: xenpv
+
+# use this as the default disk size for virt guests (GB)
default_virt_file_size: 5
+
+# use this as the default memory size for virt guests (MB)
default_virt_ram: 512
+
+# if using the authz_ownership module (see the Wiki), objects
+# created without specifying an owner are assigned to this
+# owner and/or group. Can be a comma seperated list.
+default_ownership: "admin"
+
+# location for some important binaries and config files
+# that can vary based on the distribution.
dhcpd_bin: /usr/sbin/dhcpd
dhcpd_conf: /etc/dhcpd.conf
dnsmasq_bin: /usr/sbin/dnsmasq
dnsmasq_conf: /etc/dnsmasq.conf
httpd_bin: /usr/sbin/httpd
+
+# change this port if Apache is not running plaintext on port
+# 80. Most people can leave this alone.
http_port: 80
-kerberos_realm: 'example.org'
+
+# kernel options that should be present in every cobbler installation.
+# kernel options can also be applied at the distro/profile/system
+# level.
kernel_options:
ksdevice: eth0
lang: ' '
text: ~
+
+# configuration options if using the authn_ldap module. See the
+# the Wiki for details. This can be ignored if you are not using
+# LDAP for WebUI/XMLRPC authentication.
+ldap_server: "ldap.example.com"
+ldap_base_dn: "DC=example,DC=com"
+ldap_port: 389
+ldap_tls: 1
+ldap_anonymous_bind: 1
+ldap_search_bind_dn: ''
+ldap_search_passwd: ''
+ldap_search_prefix: 'uid='
+
+# set to 1 to enable Cobbler's DHCP management features.
+# the choice of DHCP management engine is in /etc/cobbler/modules.conf
manage_dhcp: 0
-manage_dhcp_mode: isc
+
+# set to 1 to enable Cobbler's DNS management features.
+# the choice of DNS mangement engine is in /etc/cobbler/modules.conf
+manage_dns: 0
+
+# if using cobbler with manage_dhcp, put the IP address
+# of the cobbler server here so that PXE booting guests can find it
next_server: '127.0.0.1'
+
+# if using cobbler with manage_dhcp and ISC, omapi allows realtime DHCP
+# updates without restarting ISC dhcpd.
+omapi_enabled: 1
+omapi_port: 647
+omshell_bin: /usr/bin/omshell
+
+# if this setting is set to 1, cobbler systems that pxe boot
+# will request at the end of their installation to toggle the
+# --netboot-enabled record in the cobbler system record. This eliminates
+# the potential for a PXE boot loop if the system is set to PXE
+# first in it's BIOS order. Enable this if PXE is first in your BIOS
+# boot order, otherwise leave this disabled. See the manpage
+# for --netboot-enabled.
pxe_just_once: 0
-run_post_install_trigger: 0
+
+# if set to 1, new systems doing profile based installations will
+# contact cobbler to have system records created for them containing
+# the mac address information that they have requested for install.
+# this effectively allows for registration of new hardware via PXE
+# without having to manually enter in all of the mac addresses for
+# every machine on your network
+register_new_installs: 0
+
+# install triggers are scripts in /var/lib/cobbler/triggers/install
+# that are triggered in kickstart pre and post sections. Any
+# executable script in those directories is run. They can be used
+# to send email or perform other actions. They are currently
+# run as root so if you do not need this functionality you can
+# disable it, though this will also disable "cobbler status" which
+# uses a logging trigger to audit install progress.
+run_install_triggers: 1
+
+# this is the address of the cobbler server -- as it is used
+# by systems during the install process, it must be the address
+# or hostname of the system as those systems can see the server.
+# if you have a server that appears differently to different subnets
+# (dual homed, etc), you need to read the --server-override section
+# of the manpage for how that works.
server: '127.0.0.1'
+
+# this is a directory of files that cobbler uses to make
+# templating easier. See the Wiki for more information. Changing
+# this directory should not be required.
snippetsdir: /var/lib/cobbler/snippets
+
+# by default, installs are set to send syslog traffic on this port
+# and cobblerd will listen on this port. syslog data (for installs
+# that support it... RHEL 5 and later, etc) is logged in /var/log/cobbler
+# and can be used to help debug problematic installations. Syslog
+# is UDP and may not be available depending on network/firewall configuration.
syslog_port: 25150
-tftpboot: /tftpboot
+
+# locations of the TFTP binary and config file
tftpd_bin: /usr/sbin/in.tftpd
tftpd_conf: /etc/xinetd.d/tftp
+
+# cobbler's web directory. Don't change this setting -- see the
+# Wiki on "relocating your cobbler install" if your /var partition
+# is not large enough.
webdir: /var/www/cobbler
+
+# cobbler's public XMLRPC listens on this port. Change this only
+# if absolutely needed, as you'll have to start supplying a new
+# port option to koan if it is not the default.
xmlrpc_port: 25151
-xmlrpc_rw_enabled: 0
+
+# cobbler's read write XMLRPC is the version of XMLRPC
+# used by the WebUI and some features like system registration.
+# XMLRPC connections here require login information to access.
+# this feature can be disabled to gain increased security but
+# will disable the WebUI, registration, and potentially other
+# cobbler features. Most users should leave XMLRPC RW
+# enabled. The port can be relocated if needed.
+xmlrpc_rw_enabled: 1
xmlrpc_rw_port: 25152
+
+# "cobbler repo add" commands set cobbler up with repository
+# information that can be used during kickstart and is automatically
+# set up in the cobbler kickstart templates. By default, these
+# are only available at install time. To make these repositories
+# usable on installed systems (since cobbler makes a very convient)
+# mirror, set this to 1. Most users can safely set this to 1. Users
+# who have a dual homed cobbler server, or are installing laptops that
+# will not always have access to the cobbler server may wish to leave
+# this as 0. In that case, the cobbler mirrored yum repos are still
+# accessable at http://cobbler.example.org/cblr/repo_mirror and yum
+# configuration can still be done manually. This is just a shortcut.
yum_post_install_mirror: 0
+
+# "cobbler repo" support normally uses rsync or reposync. If --rpm-list
+# is used, it's possible to download only a certain package list, plus
+# dependencies, but --resolve and other flags are not supported in
+# all versions of yumdownloader. This is a list of what flags
+# to pass to it. Only change this if you are experiencing problems
+# with "cobbler reposync" and are using --rpm-list.
yumdownloader_flags: "--resolve"
diff --git a/config/users.conf b/config/users.conf
new file mode 100644
index 0000000..129659a
--- /dev/null
+++ b/config/users.conf
@@ -0,0 +1,28 @@
+# Cobbler WebUI / Web Services authorization config file
+#
+# NOTICE:
+# this file is only used when /etc/cobbler/modules.conf
+# specifies an authorization mode of either:
+#
+# (A) authz_configfile
+# (B) authz_ownership
+#
+# For (A), any user in this file, in any group, are allowed
+# full access to any object in cobbler configuration.
+#
+# For (B), users in the "admins" group are allowed full access
+# to any object, otherwise users can only edit an object if
+# their username/group is listed as an owner of that object. If a
+# user is not listed in this file they will have no access.
+#
+# cobbler command line example:
+#
+# cobbler system edit --name=server1 --owner=dbas,mac,pete,jack
+#
+# NOTE: yes, you do need the equal sign after the names.
+# don't remove that part. It's reserved for future use.
+
+[admins]
+admin = ""
+cobbler = ""
+
diff --git a/docs/cobbler.dot b/docs/cobbler.dot
new file mode 100644
index 0000000..e4eb2fb
--- /dev/null
+++ b/docs/cobbler.dot
@@ -0,0 +1,118 @@
+graph arch {
+
+
+webui -- apache
+
+cobblerd
+mgmt [label="mgmt/sync"]
+// yum stuff
+
+node [color="brown"]
+
+api
+misc -- replication
+misc -- buildiso
+i_dvd [label="DVD"]
+i_other [label="rsync/ssh/filesystem"]
+misc -- import
+import -- i_dvd
+import -- i_other
+misc -- check
+
+api -- mgmt
+api -- misc
+
+api -- yum_mirroring
+api -- triggers
+y_rsync [label="rsync/ssh/local"]
+y_http_ftp [label="http/ftp"]
+yum_mirroring -- y_rsync
+yum_mirroring -- y_http_ftp
+
+node [color="black"]
+
+cli -- api
+cli
+cobblerd -- api
+
+node [color="red"]
+
+// triggers stuff
+triggers_add [label="add/remove"]
+triggers_sync [label="sync"]
+triggers_install [label="install"]
+triggers -- triggers_add
+triggers -- triggers_sync
+triggers -- triggers_install
+
+// mgmt stuff
+
+node [color="grey"]
+
+dns
+dhcp
+mgmt -- tftpboot
+mgmt -- dns
+mgmt -- dhcp
+dns -- bind
+dns -- dnsmasq
+dhcp -- isc
+dhcp -- dnsmasq
+yumconfigs
+mgmt -- yumconfigs
+tftpboot -- templating
+isc -- templating
+dnsmasq -- templating
+bind -- templating
+yumconfigs -- templating
+templating -- snippets
+
+node [color="blue"]
+
+api -- configs
+configs -- settings
+configs -- objects
+objects -- distros
+objects -- systems
+objects -- profiles
+objects -- repos
+configs -- modules_conf
+configs -- cobbler_conf
+configs -- services_conf
+apache -- cobbler_conf
+apache -- services_conf
+
+
+node [color="green"]
+
+cobblerd -- security
+cobblerd -- webui [label="xmlrpc"]
+cobblerd -- avahi
+cobblerd -- mod_python [label="xmlrpc"]
+mod_python -- services [label="http"]
+services -- kickgen
+kickgen -- templating
+services -- registration
+services -- triggers
+
+services -- apache
+
+security -- authn
+security -- authz
+
+node [color="orange"]
+
+cobblerd -- koan [label="xmlrpc"]
+koan -- avahi
+koan -- replaceself
+replaceself -- livecd
+koan -- virt
+koan -- apache [label="http"]
+xen [label="xen fv/pv"]
+virt -- xen
+qemu_kvm [label="qemu/KVM"]
+virt -- qemu_kvm
+virt -- vmware
+
+
+}
diff --git a/docs/cobbler.pod b/docs/cobbler.pod
index a1f174f..eb7000f 100644
--- a/docs/cobbler.pod
+++ b/docs/cobbler.pod
@@ -48,7 +48,7 @@ in network booting via PXE and just want to use koan to install virtual systems
=head2 ADDING A DISTRIBUTION
-This first step towards configurating what you want to provision is to add a distribution to the cobbler's configuration.
+This first step towards configurating what you want to provision is to add a distribution to cobbler's configuration.
If there is an rsync mirror, DVD, NFS, or filesystem tree available that you would rather import instead, skip down to the documentation about the "import" command. It's really a lot easier, and it only requires waiting for the mirror content to be copied and/or scanned. Imported mirrors also save time during install since they don't have to hit external install sources.
@@ -102,6 +102,10 @@ arguments appropriately. Support for other types of distributions is possible
The file used for the answer file, regardless of the breed setting, is the value used for --kickstart when creating the profile.
+=item owners
+
+Users with small sites and a limited number of admins can probably ignore this option. All cobbler objects (distros, profiles, systems, and repos) can take a --owners parameter to specify what cobbler users can edit particular objects. This only applies to the Cobbler WebUI and XMLRPC interface, not the "cobbler" command line tool run from the shell. Furthermore, this is only respected by the "authz_ownership" module which must be enabled in /etc/cobbler/modules.conf. The value for --owners is a comma seperated list of users and groups as specified in /etc/cobbler/users.conf. For more information see the users.conf file as well as the Cobbler Wiki. In the default Cobbler configuration, this value is completely ignored, as is users.conf.
+
=back
=head2 ADDING A PROFILE
@@ -214,7 +218,7 @@ MAC addresses have the format AA:BB:CC:DD:EE:FF.
If cobbler is configured to generate a DHCP configuratition (see advanced section), use this
setting to define a specific IP for this system in DHCP. Leaving off this parameter will result in no DHCP management for this particular system.
-Example: ---ip=192.168.1.50
+Example: --ip=192.168.1.50
Note for Itanium users: this setting is always required for IA64 regardless of whether DHCP management is enabled.
@@ -276,7 +280,7 @@ on your network will result in faster, more up-to-date installations and faster
are only provisioning a home setup, this will probably be overkill, though it can be very useful
for larger setups (labs, datacenters, etc).
-B<cobbler repo add --mirror=url --name=string [--rpmlist=list] [--creatrepo-flags=string] [--keep-updated=Y/N] [--priority=number][--arch=string]>
+B<cobbler repo add --mirror=url --name=string [--rpmlist=list] [--creatrepo-flags=string] [--keep-updated=Y/N] [--priority=number] [--arch=string] [--mirror-locally=Y/N]>
=over
@@ -331,6 +335,10 @@ Specifies optional flags to feed into the createrepo tool, which is called when
Specifies that the named repository should not be updated during a normal "cobbler reposync". The repo may still be updated by name. See "cobbler reposync" below.
+=item mirror-locally
+
+When true, specifies that this yum repo is to be referenced directly via kickstarts and not mirrored locally on the cobbler server. Only http:// and ftp:// mirror urls are supported when using --mirror-locally=1.
+
=item priority
Specifies the priority of the repository (the lower the number, the higher the priority), which applies to installed machines using the repositories that also have the yum priorities plugin installed. The default priority for the plugin is 99, as is that of all cobbler mirrored repositories.
@@ -391,6 +399,14 @@ Objects can also be renamed, as long as other objects don't reference them.
B<cobbler distro|profile|system|repo rename --name=oldname --newname=newname>
+=head2 REPLICATING
+
+Cobbler can replicate distro and profile data from a master cobbler server.
+
+B<cobbler replicate --master=cobbler.example.org>
+
+This will bring over all distro data for which it can find data in /var/www/cobbler/ks_mirror can be found. It will also bring over any default profiles for those distros. A default cobbler master can be set in the settings file. Tree data must still be rsync'd (or otherwise mirrored) manually.
+
=head2 REBUILDING CONFIGURATIONS
B<cobbler sync>
@@ -399,27 +415,29 @@ Cobbler sync is used to repair or rebuild the contents /tftpboot or /var/www/cob
Sync should be run whenever files in /var/lib/cobbler are manually edited (which is not recommended except for the settings file) or when making changes to kickstart files. In practice, this should not happen often, though running sync too many times does not cause any adverse effects.
-If using cobbler to manage a DHCP server (see the advanced section of this manpage), sync does need to be
-run after systems are added to regenerate and reload the DHCP configuration.
+If using cobbler to manage a DHCP and/or DNS server (see the advanced section of this manpage), sync does need to be
+run after systems are added to regenerate and reload the DHCP/DNS configuration.
=head1 EXAMPLES
=head2 IMPORT WORKFLOW
-This example shows how to create a provisioning infrastructure from a distribution mirror.
+This example shows how to create a provisioning infrastructure from a distribution mirror or DVD ISO.
Then a default PXE configuration is created, so that by default systems will PXE boot into
a fully automated install process for that distribution.
You can use a network rsync mirror, a mounted DVD location, or a tree you have available
via a network filesystem.
+Import knows how to autodetect the architecture of what is being imported, though to make sure things are named correctly, it's always a good idea to specify --arch. For instance, if you import a distribution named "fedora8" from an ISO, and it's an x86_64 ISO, specify --arch=x86_64 and the distro will be named "fedora8-x86_64" automatically, and the right architecture field will also be set on the distribution object. If you are batch importing an entire mirror (containing multiple distributions and arches), you don't have to do this, as cobbler will set the names for things based on the paths it finds.
+
B<cobbler check>
-B<cobbler import --path=rsync://yourfavoritemirror.com/foo --name=anyname>
+B<cobbler import --path=rsync://yourfavoritemirror.com/foo --name=rhel5 --arch=x86>
# OR
-B<cobbler import --path=/mnt/dvd --name=anyname>
+B<cobbler import --path=/mnt/dvd --name=rhel5 --arch=x86_64>
# OR (using an eternal NAS box without mirroring)
@@ -531,33 +549,37 @@ Anywhere a kickstart template mentions SNIPPET::snippet_name, the file named /va
To check for potential errors in kickstarts, prior to installation, use "cobbler validateks". This function will check all profile and system kickstarts for detectable errors. Since pykickstart is not future-Anaconda-version aware, there may be some false positives. It should be noted that "cobbler validateks" runs on the rendered kickstart output, not kickstart templates themselves.
-=head2 DHCP CONFIGURATION MANAGEMENT
+=head2 DHCP MANAGEMENT
-Cobbler can optionally help you manage DHCP and (depending on how used) DNS as it relates
-to systems you wish to provision/control. This allows cobbler to essentially maintain a database
-of all of your installed systems, and be a central point of control for aspects related to setting
-up those systems.
+Cobbler can optionally help you manage DHCP server. This feature is off by default.
-This feature is off by default and must be turned on by setting 'manage_dhcp' to 1 in
-/var/lib/cobbler/settings. Choices include ISC dhcpd (default), or DNSmasq, which can be chosen
-by setting manage_dhcp_mode to 'dnsmasq'. If you choose dnsmasq and want to revert to ISC, change
-the setting to 'isc'.
+Choose either "management = isc_and_bind" in /etc/cobbler/dhcp.template or "management = "dnsmasq" in /etc/cobbler/modules.conf. Then set "manage_dhcp" to 1 in /var/lib/cobbler/settings.
+
+This allows DHCP to be managed via "cobbler system add" commands, when you specify the mac address and IP address for systems you add into cobbler.
Depending on your choice, cobbler will use /etc/cobbler/dhcpd.template or /etc/cobbler/dnsmasq.template as a starting point. This file must be user edited for the user's particular networking environment. Read the file and understand how the particular app (ISC dhcpd or dnsmasq) work before proceeding.
If you already have DHCP configuration data that you would like to preserve (say DHCP was manually configured earlier), insert the relevant portions of it into the template file, as running "cobbler sync" will overwrite your previous configuration.
-In summary, if this manage_dhcp bit is enabled, the following features are enabled:
+NOTE: Itanium systems names also need to be assigned to a distro that was created with the "--arch=ia64" parameter. If you have Itanium systems, you must (for now) choose 'dhcp_isc' for /etc/cobbler/modules.conf and manage_dhcp in the /var/lib/cobbler/settings file, and are required to use --ip when creating the system object in order for those systems to PXE. This is due to an elilo limitation.
+
+By default, the DHCP configuration file will be updated each time "cobbler sync" is run, and not until then, so it is important to remember to use "cobbler sync" when using this feature.
+
+If omapi_enabled is set to 1 in /var/lib/cobbler/settings, the need to sync when adding new system records can be eliminated.
+
+=head2 DNS CONFIGURATION MANAGEMENT
-(A) pinning dhcp hostnames to MAC addresses automatically.
-(B) relatively seamless mixing of Itanium and x86/x86_64 machines in a PXE environment (ISC only)
-(C) assigning hostnames to MAC addresses using DNS (dnsmasq only).
+Cobbler can optionally manage DNS configuration using BIND and dnsmasq.
-These options are all enabled by using the --hostname and --ip options when using the "cobbler system add" command.
+Choose either "management = isc_and_bind" or "management = dnsmasq" in /etc/cobbler/modules.conf and then enable manage_dns in /var/lib/cobbler/settings.
-Itanium systems names also need to be assigned to a distro that was created with the "--arch=ia64" parameter. If you have Itanium systems, you must (for now) choose 'isc' for 'manage_dhcp_mode' in the /var/lib/cobbler/settings file, and are required to use --ip when creating the system object in order for those systems to PXE.
+This feature is off by default. If using BIND, you must define the zones to be managed with the options 'manage_forward_zones' and 'manage_reverse_zones'. (See the Wiki for more information on this).
-The dhcpd.conf file will be updated each time "cobbler sync" is run, and not until then, so it is important to remember to use "cobbler sync" when using this feature.
+If using BIND, Cobbler will use /etc/cobbler/bind.template and /etc/cobbler/zone.template as a starting point for the named.conf and individual zone files, respectively. You may drop zone-specific template files in /etc/cobbler/zone_templates/name-of-zone which will override the default. These files must be user edited for the user's particular networking environment. Read the file and understand how BIND works before proceeding.
+
+If using dnsmasq, the template is /etc/cobbler/dnsmasq.template. Read this file and understand how dnsmasq works before proceeding.
+
+All managed files (whether zone files and named.conf for BIND, or dnsmasq.conf for dnsmasq) will be updated each time ``cobbler sync'' is run, and not until then, so it is important to remember to use ``cobbler sync'' when using this feature.
=head2 SERVICE DISCOVERY (AVAHI)
@@ -572,11 +594,11 @@ After an import is run, cobbler will try to detect the distribution type and aut
Mirrored content is saved automatically in /var/www/cobbler/ks_mirror.
-Example: B<cobbler import --mirror=rsync://mirrorserver.example.com/path/ --name=fedora>
+Example: B<cobbler import --mirror=rsync://mirrorserver.example.com/path/ --name=fedora --arch=x86>
Example2: B<cobbler import --mirror=root@192.168.1.10:/stuff --name=bar>
-Example3: B<cobbler import --mirror=/mnt/dvd --name=baz>
+Example3: B<cobbler import --mirror=/mnt/dvd --name=baz --arch=x86_64>
Example4: B<cobbler import --mirror=/path/to/stuff --name=glorp>
@@ -590,7 +612,7 @@ Note that all of the import commands will mirror install tree content into /var/
For import methods using rsync, additional flags can be passed to rsync with the option --rsync-flags.
-Should you want to force the usage of a specific cobbler kickstart template for all profiles created by an import, you can feed the option --kicksart to import, to bypass the built-in kickstart autodetection.
+Should you want to force the usage of a specific cobbler kickstart template for all profiles created by an import, you can feed the option --kickstart to import, to bypass the built-in kickstart auto-detection.
=head2 DEFAULT PXE BOOT BEHAVIOR
@@ -668,6 +690,10 @@ Most of the day-to-day actions in cobbler's command line can be performed in Cob
https://hosted.fedoraproject.org/projects/cobbler/wiki/CobblerWebUi
+=head2 BOOT CD
+
+Cobbler can build all of it's profiles into a bootable CD image using the "cobbler buildiso" command. This allows for PXE-menu like bringup of bare metal in evnvironments where PXE is not possible. Another more advanced method is described in the koan manpage, though this method is easier and sufficient for most applications.
+
=head1 EXIT_STATUS
cobbler's command line returns a zero for success and non-zero for failure.
diff --git a/docs/pyyaml-license.htm b/docs/pyyaml-license.htm
new file mode 100644
index 0000000..6993ea9
--- /dev/null
+++ b/docs/pyyaml-license.htm
@@ -0,0 +1,78 @@
+NOTE: the directory ".../yaml" contains the a derivative
+of the PyYaml library. It follows the license below
+and has been modified to disable the YAML "anchor"
+behavior.
+
+========================================================
+
+A. HISTORY OF THE SOFTWARE
+==========================
+
+The Python library for YAML was started by Steve Howell in
+February 2002. Steve is the primary author of the project,
+but others have contributed. See the README for more on
+the project. The term "PyYaml" refers to the entire
+distribution of this library, including examples, documentation,
+and test files, as well as the core implementation.
+
+This library is intended for general use, and the license
+below protects the "open source" nature of the library. The
+license does, however, allow for use of the library in
+commercial applications as well, subject to the terms
+and conditions listed. The license below is a minor
+rewrite of the Python 2.2 license, with no substantive
+differences.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PyYaml
+===============================================================
+
+LICENSE AGREEMENT FOR PyYaml
+----------------------------
+
+1. This LICENSE AGREEMENT is between Stephen S. Howell ("Author"),
+and the Individual or Organization ("Licensee") accessing and
+otherwise using PyYaml software in source or binary form and its
+associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, Author
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use PyYaml
+alone or in any derivative version, provided, however, that Author's
+License Agreement and Author's notice of copyright, i.e., "Copyright (c)
+2001 Steve Howell and Friends; All Rights Reserved" are never removed
+from PyYaml, and are included in any derivative version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates PyYaml or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to PyYaml.
+
+4. Author is making PyYaml available to Licensee on an "AS IS"
+basis. Author MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, Author MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PyYaml WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. Author SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+2.2 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.2,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between Author and
+Licensee. This License Agreement does not grant permission to use Author
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using PyYaml, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
diff --git a/docs/wui.html b/docs/wui.html
index c05a6f6..ebb9b39 100644
--- a/docs/wui.html
+++ b/docs/wui.html
@@ -26,21 +26,23 @@
<h4>Welcome</h4>
<p>
-This is the Web UI for your local <A HREF="http://cobbler.et.redhat.com">Cobbler</A> Server.
-</p>
+This is the web configuration interface for a <A HREF="http://cobbler.et.redhat.com">Cobbler</A> Server. Cobbler is an automated net install and update server for Linux operating systems. You can use this web interface to decide what you want to install and where -- and then deploy that configuration using network booting (PXE), or do reinstalls and virtual installs with "koan". There is also a koan live CD if you can't set up a PXE environment and still have bare-metal install needs.</p>
<p>
-The Cobbler WebUI is designed for day-to-day usage of the Cobbler provisioning server. It performs
-<i>most</i> but not <i>all</i> of the functions Cobbler can perform. Nearly all of what you would
-need for routine maintaince of your deployment setups can be done through the web. If you have
+The Cobbler WebUI is designed for simplifying day-to-day usage of the Cobbler provisioning server. It performs
+<i>most</i> but not <i>all</i> of the functions the command line tool "cobbler" or the API can perform. If you have
not already done so, you may be interested to read more about Cobbler at <A HREF="http://cobbler.et.redhat.com">
-cobbler.et.redhat.com</A> and <A HREF="https://hosted.fedoraproject.org/projects/cobbler/">hosted.fedoraproject.org</A>.
+cobbler.et.redhat.com</A> and at <A HREF="https://fedorahosted.org/cobbler">fedorahosted.org</A>.
Those pages contain further documentation, tips & tricks, and links to the mailing list and users/developers
IRC channel.
</p>
<p>
-It is expected that you have read the <A HREF="/cobbler/webui/cobbler.html">Cobbler manpage</A>, which for the most part focuses on cobbler as run from the command line. You will need to use the command line some, so please do read the docs. For starters, you should have started your cobbler install with running "cobbler check" locally. If not,
+It is probably a good idea to read the <A HREF="/cobbler/webui/cobbler.html">Cobbler manpage</A>, which for the most part focuses on cobbler as run from the command line. Admins will need to use the command line some, so please do read the docs if you are in charge of the Cobbler server -- or if you need more detail about what some of the options mean.
+</p>
+
+<p>
+The Cobbler server install should have started your cobbler install with running "cobbler check" locally to resolve potential configuration problems. If not,
please do so now before continuing. This will make sure your installation is configured and ready to go. The rest
of this document will mainly be detailing the differences between the CLI (as described in the manpage) and the Web interface.
</p>
@@ -52,12 +54,15 @@ content from a DVD or an rsync mirror -- running cobbler import locally is also
</p>
<p>
-Another command that you cannot run locally is "cobbler reposync", which is a fairly long-running operation
+Another command that you cannot run via the web interface is "cobbler reposync", which can be a fairly long-running operation
that you may want to put on a crontab. You also cannot edit the cobbler settings file (this is more
of a precaution against locking yourself out of the WebUI), but you can view it. For instance this means
that if you want Cobbler to help manage your DHCP config (great!) you can do that through the Web UI
-but you have to turn that on in the settings file. Furthermore, files like /etc/cobbler/dhcp.template
-have to be edited locally.
+but you have to turn that on in the settings file (be sure to restart cobblerd after making changes). Furthermore, other configuration and template files in /etc/cobbler (that are not kickstart templates) also have to be edited locally.
+</p>
+
+<p>
+Your Cobbler server administrator may have set cobbler up to allow access to only certain objects for certain user accounts. In the event of this, you will see messages in the interface that indicate when you do not have permissions to perform certain actions. Contact your Cobbler server administrator to resolve these items or to learn more if you have questions.
</p>
<p>
diff --git a/kickstarts/sample.ks b/kickstarts/sample.ks
index 5208ed7..1a7b731 100644
--- a/kickstarts/sample.ks
+++ b/kickstarts/sample.ks
@@ -39,6 +39,7 @@ zerombr
# Magically figure out how to partition this thing
SNIPPET::partition_select
+$kickstart_start
%packages
diff --git a/kickstarts/sample_end.ks b/kickstarts/sample_end.ks
new file mode 100644
index 0000000..b856a3d
--- /dev/null
+++ b/kickstarts/sample_end.ks
@@ -0,0 +1,55 @@
+# kickstart template for Fedora 8 and later.
+# (includes %end blocks)
+# do not use with earlier distros
+
+#platform=x86, AMD64, or Intel EM64T
+# System authorization information
+auth --useshadow --enablemd5
+# System bootloader configuration
+bootloader --location=mbr
+# Partition clearing information
+clearpart --all --initlabel
+# Use text mode install
+text
+# Firewall configuration
+firewall --enabled
+# Run the Setup Agent on first boot
+firstboot --disable
+# System keyboard
+keyboard us
+# System language
+lang en_US
+# Use network installation
+url --url=$tree
+# If any cobbler repo definitions were referenced in the kickstart profile, include them here.
+$yum_repo_stanza
+# Network information
+network --bootproto=dhcp --device=eth0 --onboot=on
+# Reboot after installation
+reboot
+
+#Root password
+rootpw --iscrypted \$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac.
+# SELinux configuration
+selinux --disabled
+# Do not configure the X Window System
+skipx
+# System timezone
+timezone America/New_York
+# Install OS instead of upgrade
+install
+# Clear the Master Boot Record
+zerombr
+
+# Magically figure out how to partition this thing
+SNIPPET::partition_select
+$kickstart_start
+
+
+%packages
+%end
+
+%post
+$yum_config_stanza
+$kickstart_done
+%end
diff --git a/scripts/change_profile.cgi b/legacy/change_profile.cgi
index f7330f1..f7330f1 100755
--- a/scripts/change_profile.cgi
+++ b/legacy/change_profile.cgi
diff --git a/scripts/findks.cgi b/legacy/findks.cgi
index 39adbcf..39adbcf 100755
--- a/scripts/findks.cgi
+++ b/legacy/findks.cgi
diff --git a/scripts/post_install_trigger.cgi b/legacy/install_trigger.cgi
index 4a79c8b..b83ff57 100644
--- a/scripts/post_install_trigger.cgi
+++ b/legacy/install_trigger.cgi
@@ -11,7 +11,7 @@
# if the triggers are enabled in the settings file.
#
# (C) Tim Verhoeven <tim.verhoeven.be@gmail.com>, 2007
-# tweaked: Michael DeHaan <mdehaan@redhat.com>
+# tweaked: Michael DeHaan <mdehaan@redhat.com>, 2007-2008
import cgi
import cgitb
@@ -41,17 +41,32 @@ def parse_query():
form = cgi.parse()
+ ip = "?"
+ if os.environ.has_key("REMOTE_ADDR"):
+ ip = os.environ["REMOTE_ADDR"]
+
+ name = "?"
+ objtype = "?"
if form.has_key("system"):
- return form["system"][0]
- return 0
+ name = form["system"][0]
+ objtype = "system"
+ elif form.has_key("profile"):
+ name = form["profile"][0]
+ objtype = "profile"
+
+ mode = "?"
+ if form.has_key("mode"):
+ mode = form["mode"][0]
+
+ return (mode,objtype,name,ip)
-def invoke(name):
+def invoke(mode,objtype,name,ip):
"""
Determine if this feature is enabled.
"""
xmlrpc_server = ServerProxy(XMLRPC_SERVER)
- print xmlrpc_server.run_post_install_triggers(name)
+ print xmlrpc_server.run_install_triggers(mode,objtype,name,ip)
return True
@@ -66,7 +81,7 @@ def header():
if __name__ == "__main__":
cgitb.enable(format='text')
header()
- name = parse_query()
- invoke(name)
+ (mode,objtype,name,ip) = parse_query()
+ invoke(mode,objtype,name,ip)
diff --git a/scripts/nopxe.cgi b/legacy/nopxe.cgi
index a2eae88..a2eae88 100755
--- a/scripts/nopxe.cgi
+++ b/legacy/nopxe.cgi
diff --git a/scripts/register_mac.cgi b/legacy/register_mac.cgi
index 5507525..3f251c4 100755
--- a/scripts/register_mac.cgi
+++ b/legacy/register_mac.cgi
@@ -13,7 +13,7 @@
# what is this? This is a
# script to auto add systems who make a wget into cobbler.
# right now it requires "kssendmac" in kernel options and takes only 1 arg
-# ex: wget http://cobbler.example.org/cgi-bin/regsister_mac?profile=foo
+# ex: wget http://cobbler.example.org/cgi-bin/register_mac?profile=foo
# suitable to be called from kickstart,etc
import cgi
diff --git a/scripts/watcher.py b/legacy/watcher.py
index dfa8dc3..dfa8dc3 100755
--- a/scripts/watcher.py
+++ b/legacy/watcher.py
diff --git a/loaders/menu.c32 b/loaders/menu.c32
index 7912a08..a3d8628 100644
--- a/loaders/menu.c32
+++ b/loaders/menu.c32
Binary files differ
diff --git a/scripts/cobbler_auth_help b/scripts/cobbler_auth_help
deleted file mode 100644
index 198e95b..0000000
--- a/scripts/cobbler_auth_help
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/perl
-
-# Kerberos helper for logins
-#
-# Copyright 2007, Red Hat, Inc
-# Michael DeHaan <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=1;
-
-my $result = GetOptions(
- "method=s" => \$method,
- "username=s" => \$username,
- "realm=s" => \$realm,
- "password=s" => \$password,
-);
-
-my $kerberos = Authen::Simple::Kerberos->new(
- realm => $realm
-);
-
-print "authenticating: $username against $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/index.py b/scripts/index.py
index d32a3a6..8d10129 100755
--- a/scripts/index.py
+++ b/scripts/index.py
@@ -18,6 +18,7 @@ from mod_python import util
import xmlrpclib
import cgi
+import os
from cobbler.webui import CobblerWeb
XMLRPC_SERVER = "http://127.0.0.1:25152" # was http://127.0.0.1/cobbler_api_rw"
@@ -70,7 +71,28 @@ def handler(req):
my_user = __get_user(req)
my_uri = req.uri
sess = __get_session(req)
- token = sess['cobbler_token']
+
+ if not sess.has_key('cobbler_token'):
+ # using Kerberos instead of Python Auth handler?
+ # We need to get our own token for use with authn_passthru
+ # which should also be configured in /etc/cobbler/modules.conf
+ # if another auth mode is configured in modules.conf this will
+ # most certaintly fail.
+ try:
+ if not os.path.exists("/var/lib/cobbler/web.ss"):
+ apache.log_error("cannot load /var/lib/cobbler/web.ss")
+ return apache.HTTP_UNAUTHORIZED
+ fd = open("/var/lib/cobbler/web.ss")
+ data = fd.read()
+ my_pw = data
+ fd.close()
+ token = xmlrpc_server.login(my_user,my_pw)
+ except Exception, e:
+ apache.log_error(str(e))
+ return apache.HTTP_UNAUTHORIZED
+ sess['cobbler_token'] = token
+ else:
+ token = sess['cobbler_token']
# needed?
req.add_common_vars()
@@ -100,8 +122,8 @@ def handler(req):
content = func( "Invalid Mode: \"%s\"" % mode )
# apache.log_error("%s:%s ... %s" % (my_user, my_uri, str(form)))
- req.content_type = "text/html"
- req.write(content)
+ req.content_type = "text/html;charset=utf-8"
+ req.write(unicode(content).encode('utf-8'))
return apache.OK
@@ -118,7 +140,6 @@ def authenhandler(req):
my_user = req.user
my_uri = req.uri
- apache.log_error("authenhandler called: %s" % my_user)
try:
token = xmlrpc_server.login(my_user,my_pw)
except Exception, e:
diff --git a/scripts/services.py b/scripts/services.py
new file mode 100755
index 0000000..8fcb8f8
--- /dev/null
+++ b/scripts/services.py
@@ -0,0 +1,90 @@
+"""
+mod_python gateway to cgi-like cobbler web functions
+
+Copyright 2007-2008, 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
+import os
+from cobbler.services import CobblerSvc
+
+#=======================================
+
+def handler(req):
+
+ """
+ Right now, index serves everything.
+
+ Hitting this URL means we've already cleared authn/authz
+ but we still need to use the token for all remote requests.
+ """
+
+ my_uri = req.uri
+
+
+
+ # apache.log_error("cannot load /var/lib/cobbler/web.ss")
+ req.add_common_vars()
+
+ # process form and qs data, if any
+ fs = util.FieldStorage(req)
+ form = {}
+ for x in fs.keys():
+ form[x] = str(fs.get(x,'default'))
+
+ if my_uri.find("?") == -1:
+ # support fake query strings
+ # something log /cobbler/web/op/ks/server/foo
+ # which is needed because of xend parser errors
+ # not tolerating ";" and also libvirt on 5.1 not
+ # tolerating "&amp;" (nor "&").
+
+ tokens = my_uri.split("/")
+ tokens = tokens[3:]
+ label = True
+ field = ""
+ for t in tokens:
+ if label:
+ field = t
+ apache.log_error("field %s" % field)
+ else:
+ form[field] = t
+ apache.log_error("adding %s to %s" % (field,t))
+ label = not label
+
+ form["REMOTE_ADDR"] = req.subprocess_env.get("REMOTE_ADDR",None)
+ form["REMOTE_MAC"] = req.subprocess_env.get("HTTP_X_RHN_PROVISIONING_MAC_0",None)
+
+ # instantiate a CobblerWeb object
+ cw = CobblerSvc(
+ apache = apache,
+ server = "http://127.0.0.1/cobbler_api"
+ )
+
+ # check for a valid path/mode
+ # handle invalid paths gracefully
+ mode = form.get('op','index')
+
+ func = getattr( cw, mode )
+ content = func( **form )
+
+ # apache.log_error("%s:%s ... %s" % (my_user, my_uri, str(form)))
+ req.content_type = "text/plain;charset=utf-8"
+ content = unicode(content).encode('utf-8')
+ req.write(content)
+
+ return apache.OK
+
diff --git a/setup.py b/setup.py
index acd94a7..4b08917 100644
--- a/setup.py
+++ b/setup.py
@@ -4,10 +4,10 @@ import sys
from distutils.core import setup, Extension
import string
-VERSION = "0.8.3"
+VERSION = "1.0.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.
+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/DNS Management. Cobbler also has a Python and XMLRPC API for integration with other applications.
"""
if __name__ == "__main__":
@@ -41,11 +41,13 @@ if __name__ == "__main__":
vw_systems = "/var/www/cobbler/systems"
vw_profiles = "/var/www/cobbler/profiles"
vw_links = "/var/www/cobbler/links"
+ zone_templates = "/etc/cobbler/zone_templates"
tftp_cfg = "/tftpboot/pxelinux.cfg"
tftp_images = "/tftpboot/images"
rotpath = "/etc/logrotate.d"
- cgipath = "/var/www/cgi-bin/cobbler"
+ # cgipath = "/var/www/cgi-bin/cobbler"
modpython = "/var/www/cobbler/web"
+ modpythonsvc = "/var/www/cobbler/svc"
setup(
name="cobbler",
version = VERSION,
@@ -60,22 +62,25 @@ if __name__ == "__main__":
"cobbler/server",
"cobbler/webui",
],
- scripts = ["scripts/cobbler", "scripts/cobblerd", "scripts/cobbler_auth_help"],
+ scripts = ["scripts/cobbler", "scripts/cobblerd"],
data_files = [
(modpython, ['scripts/index.py']),
+ (modpythonsvc, ['scripts/services.py']),
# cgi files
- (cgipath, ['scripts/findks.cgi', 'scripts/nopxe.cgi']),
- (cgipath, ['scripts/post_install_trigger.cgi']),
+ # (cgipath, ['scripts/nopxe.cgi']),
+ # (cgipath, ['scripts/install_trigger.cgi']),
# miscellaneous config files
(rotpath, ['config/cobblerd_rotate']),
(wwwconf, ['config/cobbler.conf']),
+ (wwwconf, ['config/cobbler_svc.conf']),
(cobpath, ['config/cobbler_hosts']),
(etcpath, ['config/modules.conf']),
(etcpath, ['config/users.digest']),
(etcpath, ['config/rsync.exclude']),
+ (etcpath, ['config/users.conf']),
(initpath, ['config/cobblerd']),
- (cobpath, ['config/settings']),
+ (etcpath, ['config/settings']),
# backups for upgrades
(backpath, []),
@@ -87,15 +92,18 @@ if __name__ == "__main__":
# sample kickstart files
(etcpath, ['kickstarts/legacy.ks']),
(etcpath, ['kickstarts/sample.ks']),
+ (etcpath, ['kickstarts/sample_end.ks']),
(etcpath, ['kickstarts/default.ks']),
- # templates for DHCP and syslinux configs
+ # templates for DHCP, DNS, and syslinux configs
(etcpath, ['templates/dhcp.template']),
(etcpath, ['templates/dnsmasq.template']),
+ (etcpath, ['templates/named.template']),
(etcpath, ['templates/pxedefault.template']),
(etcpath, ['templates/pxesystem.template']),
(etcpath, ['templates/pxesystem_ia64.template']),
(etcpath, ['templates/pxeprofile.template']),
+ (etcpath, ['templates/zone.template']),
# kickstart dir
(vl_kick, []),
@@ -127,6 +135,9 @@ if __name__ == "__main__":
(vw_profiles, []),
(vw_links, []),
+ # zone-specific templates directory
+ (zone_templates, []),
+
# tftp directories that we own
(tftp_cfg, []),
(tftp_images, []),
@@ -137,6 +148,7 @@ if __name__ == "__main__":
(wwwtmpl, ['webui_templates/empty.tmpl']),
(wwwtmpl, ['webui_templates/blank.tmpl']),
+ (wwwtmpl, ['webui_templates/enoaccess.tmpl']),
(wwwtmpl, ['webui_templates/distro_list.tmpl']),
(wwwtmpl, ['webui_templates/distro_edit.tmpl']),
(wwwtmpl, ['webui_templates/profile_list.tmpl']),
@@ -156,8 +168,8 @@ if __name__ == "__main__":
# Web UI kickstart file editing
(wwwtmpl, ['webui_templates/ksfile_edit.tmpl']),
+ (wwwtmpl, ['webui_templates/ksfile_new.tmpl']),
(wwwtmpl, ['webui_templates/ksfile_list.tmpl']),
- (wwwtmpl, ['webui_templates/ksfile_view.tmpl']),
# Web UI support files
(wwwgfx, ['docs/wui.html']),
@@ -190,7 +202,8 @@ if __name__ == "__main__":
("%sdelete/repo/pre" % trigpath, []),
("%sdelete/repo/post" % trigpath, []),
("%sdelete/repo/post" % trigpath, []),
- ("%sinstall/post" % trigpath, []),
+ ("%sinstall/pre" % trigpath, [ "triggers/status_pre.trigger"]),
+ ("%sinstall/post" % trigpath, [ "triggers/status_post.trigger"]),
("%ssync/pre" % trigpath, []),
("%ssync/post" % trigpath, [ "triggers/restart-services.trigger" ])
],
diff --git a/templates/dhcp.template b/templates/dhcp.template
index 705cc77..344b108 100644
--- a/templates/dhcp.template
+++ b/templates/dhcp.template
@@ -9,6 +9,9 @@ ddns-update-style interim;
allow booting;
allow bootp;
+#if $omapi_enabled
+omapi-port $omapi_port;
+#end if
ignore client-updates;
set vendorclass = option vendor-class-identifier;
diff --git a/templates/named.template b/templates/named.template
new file mode 100644
index 0000000..f77eadc
--- /dev/null
+++ b/templates/named.template
@@ -0,0 +1,18 @@
+options {
+ listen-on port 53 { 127.0.0.1; };
+ directory "/var/named";
+ dump-file "/var/named/data/cache_dump.db";
+ statistics-file "/var/named/data/named_stats.txt";
+ memstatistics-file "/var/named/data/named_mem_stats.txt";
+ allow-query { localhost; };
+ recursion yes;
+};
+
+logging {
+ channel default_debug {
+ file "data/named.run";
+ severity dynamic;
+ };
+};
+
+$zone_include
diff --git a/templates/pxedefault.template b/templates/pxedefault.template
index d731892..bb09893 100644
--- a/templates/pxedefault.template
+++ b/templates/pxedefault.template
@@ -1,5 +1,5 @@
-DEFAULT local
-PROMPT 1
+DEFAULT menu
+PROMPT 0
MENU TITLE Cobbler | http://cobbler.et.redhat.com
TIMEOUT 200
TOTALTIMEOUT 6000
@@ -11,3 +11,5 @@ LABEL local
LOCALBOOT 0
$pxe_menu_items
+
+MENU end
diff --git a/templates/zone.template b/templates/zone.template
new file mode 100644
index 0000000..5426f78
--- /dev/null
+++ b/templates/zone.template
@@ -0,0 +1,13 @@
+\$TTL 300
+@ IN SOA $cobbler_server. nobody.example.com. (
+ $serial ; Serial
+ 600 ; Refresh
+ 1800 ; Retry
+ 604800 ; Expire
+ 300 ; TTL
+ )
+
+ IN NS $cobbler_server.
+
+
+$host_record
diff --git a/tests/tests.py b/tests/tests.py
index 99ba0b9..02d53be 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -2,22 +2,13 @@
#
# Michael DeHaan <mdehaan@redhat.com>
-TRY_GRAPH = False
-HAS_GRAPH = False
-
-if TRY_GRAPH:
- try:
- import pycallgraph_mod as pycallgraph
- HAS_GRAPH = True
- except:
- pass
-
import sys
import unittest
import os
import subprocess
import tempfile
import shutil
+import traceback
from cobbler.cexceptions import *
@@ -25,6 +16,7 @@ from cobbler import settings
from cobbler import collection_distros
from cobbler import collection_profiles
from cobbler import collection_systems
+import cobbler.modules.authz_ownership as authz_module
from cobbler import api
@@ -54,11 +46,11 @@ class BootTest(unittest.TestCase):
except:
pass
- self.fk_initrd = os.path.join(self.topdir, FAKE_INITRD)
+ self.fk_initrd = os.path.join(self.topdir, FAKE_INITRD)
self.fk_initrd2 = os.path.join(self.topdir, FAKE_INITRD2)
self.fk_initrd3 = os.path.join(self.topdir, FAKE_INITRD3)
- self.fk_kernel = os.path.join(self.topdir, FAKE_KERNEL)
+ self.fk_kernel = os.path.join(self.topdir, FAKE_KERNEL)
self.fk_kernel2 = os.path.join(self.topdir, FAKE_KERNEL2)
self.fk_kernel3 = os.path.join(self.topdir, FAKE_KERNEL3)
@@ -75,9 +67,6 @@ class BootTest(unittest.TestCase):
shutil.rmtree(self.topdir,ignore_errors=True)
self.api = None
- if HAS_GRAPH:
- pycallgraph.save_dot("%s.dot" % self.__class__.__name__)
-
def make_basic_config(self):
distro = self.api.new_distro()
self.assertTrue(distro.set_name("testdistro0"))
@@ -85,7 +74,7 @@ class BootTest(unittest.TestCase):
self.assertTrue(distro.set_initrd(self.fk_initrd))
self.assertTrue(self.api.add_distro(distro))
self.assertTrue(self.api.find_distro(name="testdistro0"))
-
+
profile = self.api.new_profile()
self.assertTrue(profile.set_name("testprofile0"))
self.assertTrue(profile.set_distro("testdistro0"))
@@ -96,10 +85,271 @@ class BootTest(unittest.TestCase):
system = self.api.new_system()
self.assertTrue(system.set_name("drwily.rdu.redhat.com"))
self.assertTrue(system.set_mac_address("BB:EE:EE:EE:EE:FF","intf0"))
+ self.assertTrue(system.set_ip_address("192.51.51.50","intf0"))
self.assertTrue(system.set_profile("testprofile0"))
self.assertTrue(self.api.add_system(system))
self.assertTrue(self.api.find_system(name="drwily.rdu.redhat.com"))
+ repo = self.api.new_repo()
+ try:
+ os.makedirs("/tmp/test_example_cobbler_repo")
+ except:
+ pass
+ fd = open("/tmp/test_example_cobbler_repo/test.file", "w+")
+ fd.write("hello!")
+ fd.close()
+ self.assertTrue(repo.set_name("test_repo"))
+ self.assertTrue(repo.set_mirror("/tmp/test_example_cobbler_repo"))
+ self.assertTrue(self.api.repos().add(repo))
+
+class DuplicateNamesAndIpPrevention(BootTest):
+
+ """
+ The command line (and WebUI) have checks to prevent new system
+ additions from conflicting with existing systems and overwriting
+ them inadvertantly. This class tests that code. NOTE: General API
+ users will /not/ encounter these checks.
+ """
+
+ def test_duplicate_prevention(self):
+
+ # find things we are going to test with
+ distro1 = self.api.find_distro(name="testdistro0")
+ profile1 = self.api.find_profile(name="testprofile0")
+ system1 = self.api.find_system(name="drwily.rdu.redhat.com")
+ repo1 = self.api.find_repo(name="test_repo")
+
+ # make sure we can't overwrite a previous distro with
+ # the equivalent of an "add" (not an edit) on the
+ # command line.
+ distro2 = self.api.new_distro()
+ self.assertTrue(distro2.set_name("testdistro0"))
+ self.assertTrue(distro2.set_kernel(self.fk_kernel))
+ self.assertTrue(distro2.set_initrd(self.fk_initrd))
+ self.assertTrue(distro2.set_owners("canary"))
+ # this should fail
+ try:
+ self.api.add_distro(distro2,check_for_duplicate_names=True)
+ self.assertTrue(1==2,"distro add should fail")
+ except CobblerException:
+ pass
+ except:
+ self.assertTrue(1==2,"exception type")
+ # we caught the exception but make doubly sure there was no write
+ distro_check = self.api.find_distro(name="testdistro0")
+ self.assertTrue("canary" not in distro_check.owners)
+
+ # repeat the check for profiles
+ profile2 = self.api.new_profile()
+ self.assertTrue(profile2.set_name("testprofile0"))
+ self.assertTrue(profile2.set_distro("testdistro0"))
+ # this should fail
+ try:
+ self.api.add_profile(profile2,check_for_duplicate_names=True)
+ self.assertTrue(1==2,"profile add should fail")
+ except CobblerException:
+ pass
+ except:
+ traceback.print_exc()
+ self.assertTrue(1==2,"exception type")
+
+ # repeat the check for systems (just names this time)
+ system2 = self.api.new_system()
+ self.assertTrue(system2.set_name("drwily.rdu.redhat.com"))
+ self.assertTrue(system2.set_profile("testprofile0"))
+ # this should fail
+ try:
+ self.api.add_system(system2,check_for_duplicate_names=True)
+ self.assertTrue(1==2,"system add should fail")
+ except CobblerException:
+ pass
+ except:
+ traceback.print_exc()
+ self.assertTrue(1==2,"exception type")
+
+ # repeat the check for repos
+ repo2 = self.api.new_repo()
+ self.assertTrue(repo2.set_name("test_repo"))
+ self.assertTrue(repo2.set_mirror("http://imaginary"))
+ # self.failUnlessRaises(CobblerException,self.api.add_repo,[repo,check_for_duplicate_names=True])
+ try:
+ self.api.add_repo(repo2,check_for_duplicate_names=True)
+ self.assertTrue(1==2,"repo add should fail")
+ except CobblerException:
+ pass
+ except:
+ self.assertTrue(1==2,"exception type")
+
+ # now one more check to verify we can't add a system
+ # of a different name but duplicate netinfo.
+ system3 = self.api.new_system()
+ self.assertTrue(system3.set_name("unused_name"))
+ self.assertTrue(system3.set_profile("testprofile0"))
+ # MAC is initially accepted
+ self.assertTrue(system3.set_mac_address("BB:EE:EE:EE:EE:FF","intf3"))
+ # can't add as this MAC already exists!
+
+ #self.failUnlessRaises(CobblerException,self.api.add_system,[system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True)
+ try:
+ self.api.add_system(system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True)
+ except CobblerException:
+ pass
+ except:
+ traceback.print_exc()
+ self.assertTrue(1==2,"wrong exception type")
+
+ # set the MAC to a different value and try again
+ self.assertTrue(system3.set_mac_address("FF:EE:EE:EE:EE:DD","intf3"))
+ # it should work
+ self.assertTrue(self.api.add_system(system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True))
+ # now set the IP so that collides
+ self.assertTrue(system3.set_ip_address("192.51.51.50","intf6"))
+ # this should also fail
+
+ # self.failUnlessRaises(CobblerException,self.api.add_system,[system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True)
+ try:
+ self.api.add_system(system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True)
+ self.assertTrue(1==2,"system add should fail")
+ except CobblerException:
+ pass
+ except:
+ self.assertTrue(1==2,"wrong exception type")
+
+ # fix the IP and Mac back
+ self.assertTrue(system3.set_ip_address("192.86.75.30","intf6"))
+ self.assertTrue(system3.set_mac_address("AE:BE:DE:CE:AE:EE","intf3"))
+ # now it works again
+ # note that we will not check for duplicate names as we want
+ # to test this as an 'edit' operation.
+ self.assertTrue(self.api.add_system(system3,check_for_duplicate_names=False,check_for_duplicate_netinfo=True))
+
+ # FIXME: note -- how netinfo is handled when doing renames/copies/edits
+ # is more involved and we probably should add tests for that also.
+
+class Ownership(BootTest):
+
+ def test_ownership_params(self):
+
+ fd = open("/tmp/test_cobbler_kickstart","w+")
+ fd.write("")
+ fd.close()
+
+ # find things we are going to test with
+ distro = self.api.find_distro(name="testdistro0")
+ profile = self.api.find_profile(name="testprofile0")
+ system = self.api.find_system(name="drwily.rdu.redhat.com")
+ repo = self.api.find_repo(name="test_repo")
+
+ # as we didn't specify an owner for objects, the default
+ # ownership should be as specified in settings
+ default_owner = self.api.settings().default_ownership
+ for obj in [ distro, profile, system, repo ]:
+ self.assertTrue(obj is not None)
+ self.assertEquals(obj.owners, default_owner, "default owner for %s" % obj)
+
+ # verify we can test things
+ self.assertTrue(distro.set_owners(["superlab","basement1"]))
+ self.assertTrue(profile.set_owners(["superlab","basement1"]))
+ self.assertTrue(profile.set_kickstart("/tmp/test_cobbler_kickstart"))
+ self.assertTrue(system.set_owners(["superlab","basement1","basement3"]))
+ self.assertTrue(repo.set_owners([]))
+ self.api.add_distro(distro)
+ self.api.add_profile(profile)
+ self.api.add_system(system)
+ self.api.add_repo(repo)
+
+ # now edit the groups file. We won't test the full XMLRPC
+ # auth stack here, but just the module in question
+
+ authorize = authz_module.authorize
+
+ # if the users.conf file exists, back it up for the tests
+ if os.path.exists("/etc/cobbler/users.conf"):
+ shutil.copyfile("/etc/cobbler/users.conf","/tmp/cobbler_ubak")
+
+ fd = open("/etc/cobbler/users.conf","w+")
+ fd.write("\n")
+ fd.write("[admins]\n")
+ fd.write("admin1 = 1\n")
+ fd.write("\n")
+ fd.write("[superlab]\n")
+ fd.write("superlab1 = 1\n")
+ fd.write("superlab2 = 1\n")
+ fd.write("\n")
+ fd.write("[basement]\n")
+ fd.write("basement1 = 1\n")
+ fd.write("basement2 = 1\n")
+ fd.write("basement3 = 1\n")
+ fd.close()
+
+
+ xo = self.api.find_distro("testdistro0")
+ xn = "testdistro0"
+ ro = self.api.find_repo("test_repo")
+ rn = "test_repo"
+
+ # WARNING: complex test explanation follows!
+ # we must ensure those who can edit the kickstart are only those
+ # who can edit all objects that depend on the said kickstart
+ # in this test, superlab & basement1 can edit test_profile0
+ # superlab & basement1/3 can edit test_system0
+ # the systems share a common kickstart record (in this case
+ # explicitly set, which is a bit arbitrary as they are parent/child
+ # nodes, but the concept is not limited to this).
+ # Therefore the correct result is that the following users can edit:
+ # admin1, superlab1, superlab2
+ # And these folks can't
+ # basement1, basement2
+ # Basement2 is rejected because the kickstart is shared by something
+ # basmeent2 can not edit.
+
+ for user in [ "admin1", "superlab1", "superlab2", "basement1" ]:
+ self.assertTrue(1==authorize(self.api, user, "modify_kickstart", "/tmp/test_cobbler_kickstart"), "%s can modify_kickstart" % user)
+
+ for user in [ "basement2", "dne" ]:
+ self.assertTrue(0==authorize(self.api, user, "modify_kickstart", "/tmp/test_cobbler_kickstart"), "%s can modify_kickstart" % user)
+
+ # ensure admin1 can edit (he's an admin) and do other tasks
+ # same applies to basement1 who is explicitly added as a user
+ # and superlab1 who is in a group in the ownership list
+ for user in ["admin1","superlab1","basement1"]:
+ self.assertTrue(1==authorize(self.api, user, "save_distro", xo),"%s can save_distro" % user)
+ self.assertTrue(1==authorize(self.api, user, "modify_distro", xo),"%s can modify_distro" % user)
+ self.assertTrue(1==authorize(self.api, user, "copy_distro", xo),"%s can copy_distro" % user)
+ self.assertTrue(1==authorize(self.api, user, "remove_distro", xn),"%s can remove_distro" % user)
+
+ # ensure all users in the file can sync
+ for user in [ "admin1", "superlab1", "basement1", "basement2" ]:
+ self.assertTrue(1==authorize(self.api, user, "sync"))
+
+ # make sure basement2 can't edit (not in group)
+ # and same goes for "dne" (does not exist in users.conf)
+
+ for user in [ "basement2", "dne" ]:
+ self.assertTrue(0==authorize(self.api, user, "save_distro", xo), "user %s cannot save_distro" % user)
+ self.assertTrue(0==authorize(self.api, user, "modify_distro", xo), "user %s cannot modify_distro" % user)
+ self.assertTrue(0==authorize(self.api, user, "remove_distro", xn), "user %s cannot remove_distro" % user)
+
+ # basement2 is in the file so he can still copy
+ self.assertTrue(1==authorize(self.api, "basement2", "copy_distro", xo), "basement2 can copy_distro")
+
+ # dne can not copy or sync either (not in the users.conf)
+ self.assertTrue(0==authorize(self.api, "dne", "copy_distro", xo), "dne cannot copy_distro")
+ self.assertTrue(0==authorize(self.api, "dne", "sync"), "dne cannot sync")
+
+ # unlike the distro testdistro0, testrepo0 is unowned
+ # so any user in the file will be able to edit it.
+ for user in [ "admin1", "superlab1", "basement1", "basement2" ]:
+ self.assertTrue(1==authorize(self.api, user, "save_repo", ro), "user %s can save_repo" % user)
+
+ # though dne is still not listed and will be denied
+ self.assertTrue(0==authorize(self.api, "dne", "save_repo", ro), "dne cannot save_repo")
+
+ # if we survive, restore the users file as module testing is done
+ if os.path.exists("/tmp/cobbler_ubak"):
+ shutil.copyfile("/etc/cobbler/users.conf","/tmp/cobbler_ubak")
+
+
class MultiNIC(BootTest):
def test_multi_nic_support(self):
@@ -493,7 +743,7 @@ class SyncContents(BootTest):
fh = open("/tftpboot/pxelinux.cfg/%s" % converted)
data = fh.read()
- self.assertTrue(data.find("kickstarts_sys") != -1)
+ self.assertTrue(data.find("/op/ks/") != -1)
fh.close()
# ensure that after sync is applied, the blender cache still allows
@@ -503,7 +753,7 @@ class SyncContents(BootTest):
self.api.sync()
fh = open("/tftpboot/pxelinux.cfg/%s" % converted)
data = fh.read()
- self.assertTrue(data.find("kickstarts_sys") != -1)
+ self.assertTrue(data.find("/op/ks/") != -1)
fh.close()
@@ -563,28 +813,24 @@ class TestListings(BootTest):
self.assertTrue(len(self.api.profiles().printable()) > 0)
self.assertTrue(len(self.api.distros().printable()) > 0)
-class TestCLIBasic(BootTest):
-
- def test_cli(self):
- # just invoke the CLI to increase coverage and ensure
- # nothing major is broke at top level. Full CLI command testing
- # is not included (yet) since the API tests hit that fairly throughly
- # and it would easily double the length of the tests.
- app = "/usr/bin/python"
- self.assertTrue(subprocess.call([app,"cobbler/cobbler.py","list"]) == 0)
+#class TestCLIBasic(BootTest):
+#
+# def test_cli(self):
+# # just invoke the CLI to increase coverage and ensure
+# # nothing major is broke at top level. Full CLI command testing
+# # is not included (yet) since the API tests hit that fairly throughly
+# # and it would easily double the length of the tests.
+# app = "/usr/bin/python"
+# self.assertTrue(subprocess.call([app,"cobbler/cobbler.py","list"]) == 0)
if __name__ == "__main__":
if not os.path.exists("setup.py"):
print "tests: must invoke from top level directory"
sys.exit(1)
- if HAS_GRAPH:
- pycallgraph.start_trace()
loader = unittest.defaultTestLoader
test_module = __import__("tests") # self import considered harmful?
tests = loader.loadTestsFromModule(test_module)
runner = unittest.TextTestRunner()
runner.run(tests)
- if HAS_GRAPH:
- pycallgraph.make_graph('cg_dot.png', tool='dot')
sys.exit(0)
diff --git a/triggers/restart-services.trigger b/triggers/restart-services.trigger
index b65b825..d586f46 100644
--- a/triggers/restart-services.trigger
+++ b/triggers/restart-services.trigger
@@ -4,19 +4,29 @@ import cobbler.api as capi
import os
import sys
+#!/usr/bin/python
+
bootapi = capi.BootAPI()
settings = bootapi.settings()
manage_dhcp = str(settings.manage_dhcp).lower()
manage_dhcp_mode = str(settings.manage_dhcp_mode).lower()
+manage_dns = str(settings.manage_dns).lower()
+omapi_enabled = settings.omapi_enabled
+omapi_port = settings.omapi_port
rc = 0
if manage_dhcp != "0":
if manage_dhcp_mode == "isc":
- rc = os.system("/sbin/service dhcpd restart")
+ if not omapi_enabled:
+ rc = os.system("/sbin/service dhcpd restart")
elif manage_dhcp_mode == "dnsmasq":
rc = os.system("/sbin/service dnsmasq restart")
else:
print "- error: unknown DHCP engine: %s" % manage_dhcp_mode
rc = 411
+if manage_dns != "0":
+ rc = os.system("/sbin/service named restart")
+
sys.exit(rc)
+
diff --git a/triggers/status_post.trigger b/triggers/status_post.trigger
new file mode 100644
index 0000000..f69afe2
--- /dev/null
+++ b/triggers/status_post.trigger
@@ -0,0 +1,15 @@
+#!/usr/bin/python
+
+import os
+import sys
+import time
+
+objtype = sys.argv[1] # "system" or "profile"
+name = sys.argv[2] # name of system or profile
+ip = sys.argv[3] # ip or "?"
+
+fd = open("/var/log/cobbler/install.log","a+")
+fd.write("%s\t%s\t%s\tstop\t%s\n" % (objtype,name,ip,time.time()))
+fd.close()
+
+sys.exit(0)
diff --git a/triggers/status_pre.trigger b/triggers/status_pre.trigger
new file mode 100644
index 0000000..95df1fb
--- /dev/null
+++ b/triggers/status_pre.trigger
@@ -0,0 +1,15 @@
+#!/usr/bin/python
+
+import os
+import sys
+import time
+
+objtype = sys.argv[1] # "system" or "profile"
+name = sys.argv[2] # name of system or profile
+ip = sys.argv[3] # ip or "?"
+
+fd = open("/var/log/cobbler/install.log","a+")
+fd.write("%s\t%s\t%s\tstart\t%s\n" % (objtype,name,ip,time.time()))
+fd.close()
+
+sys.exit(0)
diff --git a/website/codepush.sh b/website/codepush.sh
deleted file mode 100644
index e997938..0000000
--- a/website/codepush.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-scp ../rpm-build/*.src.rpm et.redhat.com:/var/www/sites/cobbler.et.redhat.com/download
-scp ../rpm-build/*.tar.gz et.redhat.com:/var/www/sites/cobbler.et.redhat.com/download
diff --git a/website/new/about.html b/website/new/about.html
index 2d8370a..b9dfc1a 100644
--- a/website/new/about.html
+++ b/website/new/about.html
@@ -1,12 +1,15 @@
<h2>About Cobbler</h2>
-<p>Cobbler is a Linux boot server that allows for rapid setup of network installation environments. With a simple series of commands, network installs can be configured for PXE, reinstallations, and virtualized installs using Xen or KVM. Cobbler uses a helper program called 'Koan' (which interacts with Cobbler) for reinstallation and virtualization support.</p>
+<p>Cobbler is a Linux provisioning server that allows for rapid setup of network installation environments. With a simple series of commands, network installs can be configured for PXE, reinstallations, and virtualized installs using Xen or KVM. Cobbler uses a helper program called 'Koan' (which interacts with Cobbler) for reinstallation and virtualization support.</p>
-<p>Setting up Cobbler is simple. Installation trees can be <A HREF="./cobbler-import.php">imported</A> directly from media you already have (or copied from a mirror location), and turned into network install sources within minutes. RHEL, Fedora, and Centos are all supported for both the boot server and installation targets.</p>
+<p>Setting up a new Cobbler server is trivial as Cobbler eliminates much of the complexity of configuring the underlying install components. Simply install cobbler via yum, import media you may already have from mirrors or DVD ISOs, and start adding new records into cobbler using the command line or web interface. Extensive documentation is provided on the <A HREF="https://fedorahosted.org/cobbler">Cobbler Wiki</A>.</p>
-<p>Cobbler can, if desired, also assist in <A HREF="./cobbler-dhcp.php">managing of DHCP infrastructure</A> for provisioned systems (using <A HREF="http://www.isc.org/sw/dhcp/">ISC dhcp</A> or <A HREF="http://www.thekelleys.org.uk/dnsmasq/doc.html">dnsmasq</A>), mirroring install trees, templating kickstart files, automatically creating PXE menus, and locally <A HREF="./cobbler-repos.php">mirroring repositories</A> used by systems on your network. Lots of additional information on advanced features can be found on the <A HREF="https://hosted.fedoraproject.org/projects/cobbler/">Cobbler Wiki</A>.</p>
+<p>When creating deployment infrastructure, many components must be dealt with. Cobbler glues together all of these diverse components and makes them easy to manage. As low-level bringup often involves adding records to DHCP and DNS, cobbler "system add" commands can (optionally) be configured to help manage DHCP and DNS using templates. For static IP networks, cobbler still provides solid deployment solutions, including the ability to build DVD network install CDs and reinstall existing Linux systems via the koan command line.</p>
-<p>In short, Cobbler helps build and maintain network installation infrastructure really easily. It's highly customizable to your particular methods of operation through a wide variety of options, a pluggable extension mechanism, and (for developers) its own Python API. Cobbler lets administrators forget <i>how</i> software gets installed and delivered and lets them concentrate instead on what they want to install where.</p>
+<p>Cobbler also contains a very powerful kickstart templating system that can help you manage all of the differences in the answer files that drive your automated deployments. Additionally, Cobbler can help mirror packages via yum and rsync, and associate those local mirrors with profiles in cobbler, so they are available to all installed systems. </p>
+
+<p>In short, Cobbler helps build and maintain network installation infrastructure really easily. It's highly customizable to your particular methods of operation through a wide variety of options, a powerful command line, a Web interface, a pluggable extension mechanism, and (for developers) its own Python API. Cobbler lets administrators forget <i>how</i> software gets installed and delivered and lets them concentrate instead on what they want to install where.</p>
<p>Whether you run a large datacenter, a campus lab, or just have a handfull of machines on a home network, cobbler can help you perform installations and updates faster.</p>
+
diff --git a/website/new/cobbler-dhcp.php b/website/new/cobbler-dhcp.php
deleted file mode 100755
index 08f7ea4..0000000
--- a/website/new/cobbler-dhcp.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
- $current_page = 'documentation';
-?>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-<meta name="description" content="" />
-<meta name="keywords" content="" />
-<title>Cobbler</title>
-
- <link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
-</head>
-
-<body>
-<div id="wrap">
-<?php
- include("top.html");
-?>
-
-<div id="main">
- <div id="sidebar">
-<?php
- include("nav.php");
-?>
- </div>
-
-<div id="content">
-<?php
- include("docs/cobbler-dhcp.html");
-?>
-
-</div>
-</div>
-<div id="footer">
-<?php
- include("footer.html");
-?>
-</div>
-</div>
-</body>
-</html>
diff --git a/website/new/cobbler-import.php b/website/new/cobbler-import.php
deleted file mode 100755
index 11310fb..0000000
--- a/website/new/cobbler-import.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
- $current_page = 'documentation';
-?>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-<meta name="description" content="" />
-<meta name="keywords" content="" />
-<title>Cobbler</title>
-
- <link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
-</head>
-
-<body>
-<div id="wrap">
-<?php
- include("top.html");
-?>
-
-<div id="main">
- <div id="sidebar">
-<?php
- include("nav.php");
-?>
- </div>
-
-<div id="content">
-<?php
- include("docs/cobbler-import.html");
-?>
-
-</div>
-</div>
-<div id="footer">
-<?php
- include("footer.html");
-?>
-</div>
-</div>
-</body>
-</html>
diff --git a/website/new/cobbler-manpage.php b/website/new/cobbler-manpage.php
deleted file mode 100755
index ebbc56c..0000000
--- a/website/new/cobbler-manpage.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
- $current_page = 'documentation';
-?>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-<meta name="description" content="" />
-<meta name="keywords" content="" />
-<title>Cobbler</title>
-
- <link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
-</head>
-
-<body>
-<div id="wrap">
-<?php
- include("top.html");
-?>
-
-<div id="main">
- <div id="sidebar">
-<?php
- include("nav.php");
-?>
- </div>
-
-<div id="content">
-<?php
- include("docs/cobbler.html");
-?>
-
-</div>
-</div>
-<div id="footer">
-<?php
- include("footer.html");
-?>
-</div>
-</div>
-</body>
-</html>
diff --git a/website/new/cobbler-repos.php b/website/new/cobbler-repos.php
deleted file mode 100755
index a5a81db..0000000
--- a/website/new/cobbler-repos.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
- $current_page = 'documentation';
-?>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-<meta name="description" content="" />
-<meta name="keywords" content="" />
-<title>Cobbler</title>
-
- <link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
-</head>
-
-<body>
-<div id="wrap">
-<?php
- include("top.html");
-?>
-
-<div id="main">
- <div id="sidebar">
-<?php
- include("nav.php");
-?>
- </div>
-
-<div id="content">
-<?php
- include("docs/cobbler-repos.html");
-?>
-
-</div>
-</div>
-<div id="footer">
-<?php
- include("footer.html");
-?>
-</div>
-</div>
-</body>
-</html>
diff --git a/website/new/communicate.html b/website/new/communicate.html
deleted file mode 100644
index 6274b16..0000000
--- a/website/new/communicate.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<h3>Mailing List</h3>
-
-<p>Send comments, questions, patches, and suggestions to the <a href="https://www.redhat.com/mailman/listinfo/et-mgmt-tools">et-mgmt-tools mailing list</a>. You can send mail even if you aren't a list member. This is a community project, so if you feel like contributing, download the source, check it out, and join the list.</p>
-
-<ul>
-<li>Mailing List Signup Form: <a href="https://www.redhat.com/mailman/listinfo/et-mgmt-tools">https://www.redhat.com/mailman/listinfo/et-mgmt-tools</a></li>
-<li>Mailling List Archives: <a href="https://www.redhat.com/archives/et-mgmt-tools/">https://www.redhat.com/archives/et-mgmt-tools/</a></li>
-<li>gmane.org RSS feed of et-mgmt-tools: <a href="http://blog.gmane.org/gmane.linux.redhat.et-mgmt-tools"><img src="img/rss_entries.png" alt="rss entries" style="vertical-align: middle;"></a></li>
-</ul>
-
-<h3>Internet Relay Chat (IRC)</h3>
-<p>Cobbler has a chat room on <strong><a href="http://www.freenode.net/">irc.freenode.net</a></strong> ... <strong>#cobbler</strong>:</p>
-
-<p> If no one seems to be responding to your requests in IRC, feel free to ask your questions on the mailing list. (It's quite common for IRC users to 'idle' their nicks in a channel so they can catch up on any conversation they missed later on.)</p>
-<p class="note"><strong>Note:</strong> irc.freenode.net has an IRC 'nick' (nickname, the name you go by in chat) registration system. Some irc.freenode.net channels require that you identify with the server every time you log in. This identification is also required for private messages on the network (no, they're not ignoring you; you probably just haven't identified with the registration system!) For more information, please consult <a href="http://freenode.net/faq.shtml#contents-userregistration">the Freenode Frequently-Asked Questions page on user registration</a>.</p>
-
-<h3>User Submitted Content</h3>
-
-As mentioned in the documentation section, <A HREF="http://cobbler.et.redhat.com/documentation.php">Cobbler has a Wiki</A> for user submitted content.
-
-<h3>Bugs/Features</h3>
-
-Bugs and Features for all distributions can be filed on the <A HREF="https://hosted.fedoraproject.org/projects/cobbler/">Cobbler</A> and <A HREF="https://hosted.fedoraproject.org/projects/koan/">Koan</A> Trac pages. Cobbler and koan also have <A HREF="http://bugzilla.redhat.com">bugzilla</A> components if you are using Fedora and prefer to post items there. Regardless, the mailing list and the IRC channel are very active -- so if you need help, you do not need to file a bug report unless you want to do so.
-
-
-
-
diff --git a/website/new/communicate.php b/website/new/communicate.php
deleted file mode 100755
index 59af7ae..0000000
--- a/website/new/communicate.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
- $current_page = 'communicate';
-?>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-<head>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-<meta name="description" content="" />
-<meta name="keywords" content="" />
-<title>Cobbler</title>
-
- <link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
-</head>
-
-<body>
-<div id="wrap">
-<?php
- include("top.html");
-?>
-
-<div id="main">
- <div id="sidebar">
-<?php
- include("nav.php");
-?>
- </div>
-
-<div id="content">
-
-<h2>Communicate</h2>
-
-<?php
- include('communicate.html');
-?>
-
-</div>
-</div>
-<div id="footer">
-<?php
- include("footer.html");
-?>
-</div>
-</div>
-</body>
-</html>
diff --git a/website/new/css/style.css b/website/new/css/style.css
index 1d43324..d3bfe13 100644
--- a/website/new/css/style.css
+++ b/website/new/css/style.css
@@ -40,7 +40,7 @@ ul#nav {
}
ul#nav li a {
- color: #59cbe1;
+ color: #c98b07;
text-decoration: none;
}
@@ -49,7 +49,7 @@ ul#nav li#active {
}
ul#nav li#active a, ul#nav li#active a:link, ul#nav li#active a:visited {
- color: white;
+ color: #c98b07;
}
div#feed {
@@ -73,7 +73,7 @@ div#feed ul {
}
div#main {
- background-color: #212121;
+ /** background-color: #212121; **/
border-top: 1px solid #59cbe1;
border-bottom: 1px solid #59cbe1;
overflow: auto;
@@ -108,7 +108,7 @@ div#content p {
}
a:link {
- color: #59cbe1;
+ color: #c98b07;
}
a:hover {
@@ -174,11 +174,134 @@ tt {
padding-left: 30px;
}
+
#footer {
- width: 100%;
- font-size: x-small;
- color: #aaa;
- text-align: center;
- padding-top: 16px;
- padding-bottom: 16px;
+ clear: both;
+ position: relative;
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+ width: 100%;
+ background: #757575;
+ height: 290px;
+}
+
+/* This is hidden from IE <= 6 because it can't do transparency */
+body > #footer {
+ background: #757575 url(img/footer_pattern.png) repeat-x;
+}
+
+#footer p {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ margin: 0px;
+ border: 0px;
+ width: 220px;
+ text-align: center;
+}
+
+#footer p a img {
+ border: 0px;
}
+
+#projects {
+ margin: 0px;
+ border: 0px;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+}
+
+
+#projects dl {
+ margin: 0px;
+ border: 0px solid white;
+ height: 180px;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+}
+/* This is hidden from IE <= 6 because it can't do transparency */
+head:first-child+body #projects dl {
+ background: url(img/footer_corner.png) no-repeat;
+}
+
+
+#projects #p1 {
+ margin-left: 25%;
+ width: 75%;
+}
+
+#projects #p2 {
+ margin-left: 50%;
+ width: 50%;
+}
+
+#projects #p3 {
+ margin-left: 75%;
+ width: 25%;
+}
+
+#projects dt, #projects dd {
+ padding: 0px;
+ margin: 0px;
+}
+
+#projects #p1 dt, #projects #p1 dd {
+ width: 33%;
+ }
+#projects #p2 dt, #projects #p2 dd {
+ width: 50%;
+}
+#projects #p3 dt, #projects #p3 dd {
+ width: 99%;
+}
+
+#projects {
+}
+
+#projects span {
+ font-size: 0.8em;
+ display: block;
+ padding-left: 1em;
+ padding-top: 0.5em;
+}
+
+#projects a {
+ font-size: 0.8em;
+ display: block;
+ padding-left: 0.8em;
+ padding-top: 1em;
+}
+
+#projects a {
+ color: white;
+ text-decoration: inherit;
+}
+
+#projects span {
+ color: #ccc;
+}
+
+
+.screenshot img {
+ border: 0px;
+}
+.screenshot {
+ padding: 1em;
+ background: #6a7178;
+ color: #dddddd;
+ text-align: center;
+ width: 70%;
+ margin-left: auto ! important;
+ margin-right: auto ! important;
+ margin-top: 1em;
+ margin-bottom: 3em;
+}
+
+.screenshot a {
+ color: inherit;
+}
+
diff --git a/website/new/docs/cobbler.html b/website/new/docs/cobbler.html
index 36ad3e0..06a8b14 100644
--- a/website/new/docs/cobbler.html
+++ b/website/new/docs/cobbler.html
@@ -661,6 +661,20 @@ the setting to 'isc'.</p>
<p>The dhcpd.conf file will be updated each time ``cobbler sync'' is run, and not until then, so it is important to remember to use ``cobbler sync'' when using this feature.</p>
<p>
</p>
+<h2><a name="bind_configuration_management">BIND CONFIGURATION MANAGEMENT</a></h2>
+<p>Cobbler can optionally manage DNS configuration using BIND.</p>
+<p>This feature is off by default and must be turned on by setting 'manage_dns' to 1 in
+/var/lib/cobbler/settings. You may restrict the scope of zones
+managed with the options 'manage_forward_zones' and
+'manage_reverse_zones'.
+<p>Cobbler will use /etc/cobbler/bind.template and
+ /etc/cobbler/zone.template as a starting point for the named.conf
+ and individual zone files, respectively. These files must be user
+ edited for the user's particular networking environment. Read the
+ file and understand how BIND works before proceeding.</p>
+<p>The named.conf file as well as all zone files will be updated each time ``cobbler sync'' is run, and not until then, so it is important to remember to use ``cobbler sync'' when using this feature.</p>
+<p>
+</p>
<h2><a name="service_discovery__avahi_">SERVICE DISCOVERY (AVAHI)</a></h2>
<p>If the avahi-tools package is installed, cobblerd will broadcast it's presence on the network, allowing it to be discovered by koan with the koan --server=DISCOVER parameter.</p>
<p>
diff --git a/website/new/download.html b/website/new/download.html
index 821d684..ddf3978 100644
--- a/website/new/download.html
+++ b/website/new/download.html
@@ -1,4 +1,4 @@
-<p>Cobbler is licensed under the <a href="http://www.gnu.org/copyleft/gpl.html">General Public License (GPL)</a>.</p>
+<p>Cobbler is licensed under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">General Public License (GPL)</a>.</p>
<h3>Download Cobbler</h3>
@@ -48,31 +48,24 @@ The lastest stable releases of Cobbler and Koan are included in <A HREF="http://
<font size="-1">
<blockquote>
<ul>
- <li>wget http://www.python.org/pyvault/centos-4-i386/python23-cheetah-0.9.18-1.el4.pyv.i386.rpm</li>
- <li>wget ftp://ftp.freshrpms.net/pub/dag/redhat/el4/en/i386/dag/RPMS/syslinux-3.50-1.el4.rf.i386.rpm</li>
- <li>rpm -i python*cheetah*.rpm</i>
- <li>rpm -Uvh syslinux*.rpm</li>
- <li>wget http://cobbler.et.redhat.com/download/cobbler-$version.src.rpm</li>
- <li>rpmbuild --rebuild cobbler-$version.src.rpm</li>
- <li>rpm -i /usr/src/redhat/RPMS/noarch/cobbler-$version.src.rpm</li>
+ <li>For starters, grab python-setuputils, python-cheetah, and yum from <A HREF="http://fedoraproject.org/wiki/EPEL">EPEL</A> and install them. These are found in the main EPEL repo, not EPEL testing -- here's the link: <A HREF="http://download.fedora.redhat.com/pub/epel/4/i386/">i386</A> and <A HREF="http://download.fedora.redhat.com/pub/epel/4/x86_64/">x86_64</A>.</li>
+ <li>now rpmbuild --rebuild cobbler*.src.rpm</li>
+ <li>install the RPM, which is now built in /usr/src/redhat/RPMS/noarch</li>
+ <li>satisfy any dependencies you have by using yum and the EPEL 4 repos</li>
</ul>
</blockquote>
</font>
</p>
-<h4>Source RPM Build Instructions for RHEL5</h4>
+<h4>Source RPM Build Instructions for RHEL 4/5</h4>
<p>
<font size="-1">
<blockquote>
<ul>
-<!--
- <li>wget ftp://ftp.freshrpms.net/pub/dag/redhat/el5/en/i386/RPMS.dag/syslinux-3.50-1.el5.rf.i386.rpm</i>
- <li>rpm -Uvh syslinux*.rpm</li>
--->
- <li>Grab python-setuputils from <A HREF="ftp://rpmfind.net/linux/fedora/extras/6/SRPMS/python-setuptools-0.6c5-1.fc6.src.rpm">here</A></li>
- <li>Grab python-cheetah and yum-utils from here: <A HREF="ftp://mirror.linux.duke.edu/pub/fedora/linux/extras/6/SRPMS/">here</A></li>
- <li>rpmbuild --rebuild those RPMs and then install them</li>
- <li>now rebuild --rebuild and install Cobbler from it's source RPM</li>
+ <li>Grab python-setuputils and python-cheetah from <A HREF="http://fedoraproject.org/wiki/EPEL">EPEL</A> and install them. These are found in the main EPEL repo, not EPEL testing -- here's the link: <A HREF="http://download.fedora.redhat.com/pub/epel/5/i386/">i386</A> and <A HREF="http://download.fedora.redhat.com/pub/epel/5/x86_64/">x86_64</A>.</li>
+ <li>now rpmbuild --rebuild cobbler*.src.rpm</li>
+ <li>install the RPM, which is now built in /usr/src/redhat/RPMS/noarch</li>
+ <li>satisfy any dependencies you have by using yum and the EPEL 5 repos</li>
</ul>
</blockquote>
</font>
diff --git a/website/new/faq.html b/website/new/faq.html
deleted file mode 100644
index 9147adb..0000000
--- a/website/new/faq.html
+++ /dev/null
@@ -1,66 +0,0 @@
-
-<a name="questions">
-<h3>Questions</h3>
-</a>
-<ul id="faq">
-<li><a href="#whouses">Who Uses Cobbler?</a></li>
-<li><a href="#supported">What Operating Systems Are Supported?</a></li>
-<li><a href="#reinstalls">What are my non-PXE install options?</a></li>
-<li><a href="#virt">How are virtual systems installed?</a></li>
-<li><a href="#architecture">How does Cobbler work internally?</a></li>
-<li><a href="#contribute">How can I contribute?</a></li>
-</ul>
-
-<h3>Answers</h3>
-
-<a name="whouses">
-<dt>Who Uses Cobbler?</dt>
-</a>
- <dd>Cobbler is in use in a lot of diverse configurations. Large companies, hosting datacenters, college labs, consultants, ISVs, developers, and home Linux users all use cobbler and koan in different ways. It is intended to cover the large configurations just as easily as the smaller ones -- and be non-complex for all classes of users.
- <br />
- <a href="#questions" class="back-to-top">Back to top</a>
-</dd>
-<a name="supported">
-<dt>What Operating Systems Are Supported?</dt>
-</a>
- <dd>
-Cobbler runs on RHEL 4 and later, Fedora 5 and later, and Centos 4 and later. Koan, cobbler's helper program, also works with RHEL 3.
- <br />
- <a href="#questions" class="back-to-top">Back to top</a>
-</dd>
-<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.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.html">koan</A> manpage documentation.
- <br />
- <a href="#questions" class="back-to-top">Back to top</a>
-</dd>
-
-<a name="architecture">
-<dt>How does Cobbler work internally?</dt>
-</a>
-<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.html">Cobbler</A> manpage documentation.
-<a href="#questions" class="back-to-top">Back to top</a>
-</dd>
-
-<a name="contribute">
-<dt>How can I contribute?</dt>
-</a>
- <dd>
-Send in bug reports, patches, ideas, or comments. We're interested in hearing about your real-world provisioning challenges and how we can solve them for system administrators everywhere.
- <br />
- <a href="#questions" class="back-to-top">Back to top</a>
-</dd>
-
-
-
diff --git a/website/new/faq.php b/website/new/faq.php
deleted file mode 100755
index 0fbd05e..0000000
--- a/website/new/faq.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
- $current_page = 'faq';
-?>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-<head>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-<meta name="description" content="" />
-<meta name="keywords" content="" />
-<title>Cobbler</title>
-
- <link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
-</head>
-
-<body>
-<div id="wrap">
-<?php
- include("top.html");
-?>
-
-<div id="main">
-<div id="sidebar">
-<?php
- include("nav.php");
-?>
-</div>
-
-<div id="content">
-
-<h2>Frequently-Asked Questions</h2>
-
-<?php
- include('faq.html');
-?>
-
-</div>
-</div>
-<div id="footer">
-<?php
- include("footer.html");
-?>
-</div>
-</div>
-</body>
-</html>
diff --git a/website/new/feed.php b/website/new/feed.php
deleted file mode 100755
index a92f985..0000000
--- a/website/new/feed.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<div id="feed">
-<p>Below are the latest 3 message subjects from the <a href="https://www.redhat.com/mailman/listinfo/et-mgmt-tools">et-mgmt-tools list</a>:</p>
-<ul>
-<?php
-
-require_once('magpierss/rss_fetch.inc');
-
-$url = 'http://rss.gmane.org/gmane.linux.redhat.et-mgmt-tools';
-$rss = fetch_rss($url);
-
- for ($i = 0; $i < 3; $i++)
- {
- $item_array = $rss->items;
- $item = $item_array[$i];
- $href = $item['link'];
- $title = $item['title'];
- $body = $item['description'];
- $author = $item['dc']['creator'];
- $raw_timestamp = $item['dc']['date'];
- $unixtime = strtotime($raw_timestamp);
- $timestamp = date('g:i A T j F Y', $unixtime);
-
- echo "<li><a href=$href>$title</a></li>";
- }
-
-
-?>
-</ul>
-<p>
-[ <a href="https://www.redhat.com/archives/et-mgmt-tools/">View More ...</a> ]</p>
-</div>
diff --git a/website/new/footer.html b/website/new/footer.html
index 35be552..68993c8 100644
--- a/website/new/footer.html
+++ b/website/new/footer.html
@@ -1,6 +1,35 @@
-<p>Copyright &copy; 2007 Red Hat, Inc. and others.
-A project from the <a href="http://et.redhat.com/">Red Hat Emerging Technologies Group</a>.<br />
-Website design is <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a>.
+ <div id="footer">
+ <div id="projects">
+ <dl id="p1">
+ <dt><a href="http://augeas.net/">Augeas</a></dt>
+ <dd><span>A configuration editing tool and API</span></dd>
+
+ <dt><a href="http://libvirt.org/">libvirt</a></dt>
+ <dd><span>The open source virtualization API</span></dd>
+ <dt><a href="https://fedorahosted.org/func">Func</A></dt>
+ <dd><span>A secure, scriptable remote control framework & API</span></dd>
+ </dl>
+ <dl id="p2">
+ <dt><a href="http://cobbler.et.redhat.com/">Cobbler</a></dt>
+ <dd><span>OS provisioning and profile management</span></dd>
+
+ <dt><a href="http://ovirt.org/">oVirt</a></dt>
+ <dd><span>Virtualization management across the data center</span></dd>
+ </dl>
+ <dl id="p3">
+ <dt><a href="http://freeipa.org/">FreeIPA</a></dt>
+ <dd><span>Identity, policy and audit management</span></dd>
+
+ <dt><a href="http://virt-manager.org/">Virtual Machine Manager</a></dt>
+ <dd><span>Virtualization management from the desktop</span></dd>
+
+ </dl>
+ </div>
+ <p id="et">
+ <a href="http://et.redhat.com/"><img src="./img/et_logo.png" alt="A Red Hat Emerging Technology Project"></a>
+ </p>
+
+ </div>
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
diff --git a/website/new/img/et_logo.png b/website/new/img/et_logo.png
new file mode 100644
index 0000000..e094b59
--- /dev/null
+++ b/website/new/img/et_logo.png
Binary files differ
diff --git a/website/new/img/footer_corner.png b/website/new/img/footer_corner.png
new file mode 100644
index 0000000..090bfce
--- /dev/null
+++ b/website/new/img/footer_corner.png
Binary files differ
diff --git a/website/new/img/footer_pattern.png b/website/new/img/footer_pattern.png
new file mode 100644
index 0000000..647c52a
--- /dev/null
+++ b/website/new/img/footer_pattern.png
Binary files differ
diff --git a/website/new/koan-manpage.php b/website/new/koan-manpage.php
deleted file mode 100755
index ad400df..0000000
--- a/website/new/koan-manpage.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
- $current_page = 'documentation';
-?>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-<meta name="description" content="" />
-<meta name="keywords" content="" />
-<title>Cobbler</title>
-
- <link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
-</head>
-
-<body>
-<div id="wrap">
-<?php
- include("top.html");
-?>
-
-<div id="main">
- <div id="sidebar">
-<?php
- include("nav.php");
-?>
- </div>
-
-<div id="content">
-<?php
- include("docs/koan.html");
-?>
-
-</div>
-</div>
-<div id="footer">
-<?php
- include("footer.html");
-?>
-</div>
-</div>
-</body>
-</html>
diff --git a/website/new/nav.php b/website/new/nav.php
index b093a75..8f4ee69 100755
--- a/website/new/nav.php
+++ b/website/new/nav.php
@@ -7,59 +7,20 @@
echo "<ul id=\"nav\">
<li id=\"active\"><a href=\"index.php\">About</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>
+ <li><a href=\"https://fedorahosted.org/projects/cobbler\">Wiki</a></li>
</ul>";
break;
case "download":
echo "<ul id=\"nav\">
<li><a href=\"index.php\">About</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>
- <li><a href=\"communicate.php\">Communicate</a></li>
<li><a href=\"http://hosted.fedoraproject.org/projects/cobbler/\">Wiki</a></li>
</ul>";
break;
- case "faq":
- echo "<ul id=\"nav\">
- <li><a href=\"index.php\">About</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>
- <li><a href=\"communicate.php\">Communicate</a></li>
- <li><a href=\"http://hosted.fedoraproject.org/projects/cobbler/\">Wiki</a></li>
- </ul>";
- break;
- case "communicate":
- echo "<ul id=\"nav\">
- <li><a href=\"index.php\">About</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 id=\"active\"><a href=\"communicate.php\">Communicate</a></li>
- <li><a href=\"http://hosted.fedoraproject.org/projects/cobbler/\">Wiki</a></li>
- </ul>";
- break;
- case "documentation":
- echo "<ul id=\"nav\">
- <li><a href=\"index.php\">About</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>
- <li><a href=\"communicate.php\">Communicate</a></li>
- <li><a href=\"http://hosted.fedoraproject.org/projects/cobbler/\">Wiki</a></li>
- </ul>";
- break;
default:
echo " <ul id=\"nav\">
<li id=\"active\"><a href=\"index.php\">About</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;
diff --git a/website/new/news-feed.php b/website/new/news-feed.php
deleted file mode 100644
index e69de29..0000000
--- a/website/new/news-feed.php
+++ /dev/null
diff --git a/website/new/news.html b/website/new/news.html
deleted file mode 100644
index 2981fd1..0000000
--- a/website/new/news.html
+++ /dev/null
@@ -1 +0,0 @@
-news goes here.
diff --git a/website/new/rss-aggregator.php b/website/new/rss-aggregator.php
deleted file mode 100755
index 9cbb837..0000000
--- a/website/new/rss-aggregator.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<h1>Simple RSS agregator</h1>
-<a href="http://www.webdot.cz/lastrss/">
-<img src="lastrss_button.gif" alt="" width="88" height="31" border="0">
-</a><hr>
-<!-- / Heading -->
-
-<?php
-/*
- ======================================================================
- lastRSS usage DEMO 3 - Simple RSS agregator
- ----------------------------------------------------------------------
- This example shows, how to create simple RSS agregator
- - create lastRSS object
- - set transparent cache
- - show a few RSS files at once
- ======================================================================
-*/
-
-function ShowOneRSS($url) {
- global $rss;
- if ($rs = $rss->get($url)) {
- echo "<big><b><a href=\"$rs[link]\">$rs[title]</a></b></big><br />\n";
- echo "$rs[description]<br />\n";
-
- echo "<ul>\n";
- foreach ($rs['items'] as $item) {
- echo "\t<li><a href=\"$item[link]\" title=\"$item[description]\">$item[title]</a></li>\n";
- }
- if ($rs['items_count'] <= 0) { echo "<li>Sorry, no items found in the RSS file :-(</li>"; }
- echo "</ul>\n";
- }
- else {
- echo "Sorry: It's not possible to reach RSS file $url\n<br />";
- // you will probably hide this message in a live version
- }
-}
-
-// ===============================================================================
-
-// include lastRSS
-include "./rss-parser.php";
-
-// List of RSS URLs
-$rss_left = array(
- 'http://freshmeat.net/backend/fm.rdf',
- 'http://slashdot.org/slashdot.rdf'
-);
-$rss_right = array(
- 'http://www.freshfolder.com/rss.php',
- 'http://phpbuilder.com/rss_feed.php'
-);
-
-// Create lastRSS object
-$rss = new lastRSS;
-
-// Set cache dir and cache time limit (5 seconds)
-// (don't forget to chmod cahce dir to 777 to allow writing)
-$rss->cache_dir = './temp';
-$rss->cache_time = 1200;
-
-
-// Show all rss files
-echo "<table cellpadding=\"10\" border=\"0\"><tr><td width=\"50%\" valign=\"top\">";
-foreach ($rss_left as $url) {
- ShowOneRSS($url);
-}
-echo "</td><td width=\"50%\" valign=\"top\">";
-foreach ($rss_right as $url) {
- ShowOneRSS($url);
-}
-echo "</td></tr></table>";
-?>
-
diff --git a/website/new/rss-parser.php b/website/new/rss-parser.php
deleted file mode 100755
index cfe1637..0000000
--- a/website/new/rss-parser.php
+++ /dev/null
@@ -1,221 +0,0 @@
-<?php
-/*
- ======================================================================
- lastRSS 0.9.1
-
- Simple yet powerfull PHP class to parse RSS files.
-
- by Vojtech Semecky, webmaster @ oslab . net
-
- Latest version, features, manual and examples:
- http://lastrss.oslab.net/
-
- ----------------------------------------------------------------------
- LICENSE
-
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License (GPL)
- as published by the Free Software Foundation; either version 2
- of the License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- To read the license please visit http://www.gnu.org/copyleft/gpl.html
- ======================================================================
-*/
-
-/**
-* lastRSS
-* Simple yet powerfull PHP class to parse RSS files.
-*/
-class lastRSS {
- // -------------------------------------------------------------------
- // Public properties
- // -------------------------------------------------------------------
- var $default_cp = 'UTF-8';
- var $CDATA = 'nochange';
- var $cp = '';
- var $items_limit = 0;
- var $stripHTML = False;
- var $date_format = '';
-
- // -------------------------------------------------------------------
- // Private variables
- // -------------------------------------------------------------------
- var $channeltags = array ('title', 'link', 'description', 'language', 'copyright', 'managingEditor', 'webMaster', 'lastBuildDate', 'rating', 'docs');
- var $itemtags = array('title', 'link', 'description', 'author', 'category', 'comments', 'enclosure', 'guid', 'pubDate', 'source');
- var $imagetags = array('title', 'url', 'link', 'width', 'height');
- var $textinputtags = array('title', 'description', 'name', 'link');
-
- // -------------------------------------------------------------------
- // Parse RSS file and returns associative array.
- // -------------------------------------------------------------------
- function Get ($rss_url) {
- // If CACHE ENABLED
- if ($this->cache_dir != '') {
- $cache_file = $this->cache_dir . '/rsscache_' . md5($rss_url);
- $timedif = @(time() - filemtime($cache_file));
- if ($timedif < $this->cache_time) {
- // cached file is fresh enough, return cached array
- $result = unserialize(join('', file($cache_file)));
- // set 'cached' to 1 only if cached file is correct
- if ($result) $result['cached'] = 1;
- } else {
- // cached file is too old, create new
- $result = $this->Parse($rss_url);
- $serialized = serialize($result);
- if ($f = @fopen($cache_file, 'w')) {
- fwrite ($f, $serialized, strlen($serialized));
- fclose($f);
- }
- if ($result) $result['cached'] = 0;
- }
- }
- // If CACHE DISABLED >> load and parse the file directly
- else {
- $result = $this->Parse($rss_url);
- if ($result) $result['cached'] = 0;
- }
- // return result
- return $result;
- }
-
- // -------------------------------------------------------------------
- // Modification of preg_match(); return trimed field with index 1
- // from 'classic' preg_match() array output
- // -------------------------------------------------------------------
- function my_preg_match ($pattern, $subject) {
- // start regullar expression
- preg_match($pattern, $subject, $out);
-
- // if there is some result... process it and return it
- if(isset($out[1])) {
- // Process CDATA (if present)
- if ($this->CDATA == 'content') { // Get CDATA content (without CDATA tag)
- $out[1] = strtr($out[1], array('<![CDATA['=>'', ']]>'=>''));
- } elseif ($this->CDATA == 'strip') { // Strip CDATA
- $out[1] = strtr($out[1], array('<![CDATA['=>'', ']]>'=>''));
- }
-
- // If code page is set convert character encoding to required
- if ($this->cp != '')
- //$out[1] = $this->MyConvertEncoding($this->rsscp, $this->cp, $out[1]);
- $out[1] = iconv($this->rsscp, $this->cp.'//TRANSLIT', $out[1]);
- // Return result
- return trim($out[1]);
- } else {
- // if there is NO result, return empty string
- return '';
- }
- }
-
- // -------------------------------------------------------------------
- // Replace HTML entities &something; by real characters
- // -------------------------------------------------------------------
- function unhtmlentities ($string) {
- // Get HTML entities table
- $trans_tbl = get_html_translation_table (HTML_ENTITIES, ENT_QUOTES);
- // Flip keys<==>values
- $trans_tbl = array_flip ($trans_tbl);
- // Add support for &apos; entity (missing in HTML_ENTITIES)
- $trans_tbl += array('&apos;' => "'");
- // Replace entities by values
- return strtr ($string, $trans_tbl);
- }
-
- // -------------------------------------------------------------------
- // Parse() is private method used by Get() to load and parse RSS file.
- // Don't use Parse() in your scripts - use Get($rss_file) instead.
- // -------------------------------------------------------------------
- function Parse ($rss_url) {
- // Open and load RSS file
- if ($f = @fopen($rss_url, 'r')) {
- $rss_content = '';
- while (!feof($f)) {
- $rss_content .= fgets($f, 4096);
- }
- fclose($f);
-
- // Parse document encoding
- $result['encoding'] = $this->my_preg_match("'encoding=[\'\"](.*?)[\'\"]'si", $rss_content);
- // if document codepage is specified, use it
- if ($result['encoding'] != '')
- { $this->rsscp = $result['encoding']; } // This is used in my_preg_match()
- // otherwise use the default codepage
- else
- { $this->rsscp = $this->default_cp; } // This is used in my_preg_match()
-
- // Parse CHANNEL info
- preg_match("'<channel.*?>(.*?)</channel>'si", $rss_content, $out_channel);
- foreach($this->channeltags as $channeltag)
- {
- $temp = $this->my_preg_match("'<$channeltag.*?>(.*?)</$channeltag>'si", $out_channel[1]);
- if ($temp != '') $result[$channeltag] = $temp; // Set only if not empty
- }
- // If date_format is specified and lastBuildDate is valid
- if ($this->date_format != '' && ($timestamp = strtotime($result['lastBuildDate'])) !==-1) {
- // convert lastBuildDate to specified date format
- $result['lastBuildDate'] = date($this->date_format, $timestamp);
- }
-
- // Parse TEXTINPUT info
- preg_match("'<textinput(|[^>]*[^/])>(.*?)</textinput>'si", $rss_content, $out_textinfo);
- // This a little strange regexp means:
- // Look for tag <textinput> with or without any attributes, but skip truncated version <textinput /> (it's not beggining tag)
- if (isset($out_textinfo[2])) {
- foreach($this->textinputtags as $textinputtag) {
- $temp = $this->my_preg_match("'<$textinputtag.*?>(.*?)</$textinputtag>'si", $out_textinfo[2]);
- if ($temp != '') $result['textinput_'.$textinputtag] = $temp; // Set only if not empty
- }
- }
- // Parse IMAGE info
- preg_match("'<image.*?>(.*?)</image>'si", $rss_content, $out_imageinfo);
- if (isset($out_imageinfo[1])) {
- foreach($this->imagetags as $imagetag) {
- $temp = $this->my_preg_match("'<$imagetag.*?>(.*?)</$imagetag>'si", $out_imageinfo[1]);
- if ($temp != '') $result['image_'.$imagetag] = $temp; // Set only if not empty
- }
- }
- // Parse ITEMS
- preg_match_all("'<item(| .*?)>(.*?)</item>'si", $rss_content, $items);
- $rss_items = $items[2];
- $i = 0;
- $result['items'] = array(); // create array even if there are no items
- foreach($rss_items as $rss_item) {
- // If number of items is lower then limit: Parse one item
- if ($i < $this->items_limit || $this->items_limit == 0) {
- foreach($this->itemtags as $itemtag) {
- $temp = $this->my_preg_match("'<$itemtag.*?>(.*?)</$itemtag>'si", $rss_item);
- if ($temp != '') $result['items'][$i][$itemtag] = $temp; // Set only if not empty
- }
- // Strip HTML tags and other bullshit from DESCRIPTION
- if ($this->stripHTML && $result['items'][$i]['description'])
- $result['items'][$i]['description'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['description'])));
- // Strip HTML tags and other bullshit from TITLE
- if ($this->stripHTML && $result['items'][$i]['title'])
- $result['items'][$i]['title'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['title'])));
- // If date_format is specified and pubDate is valid
- if ($this->date_format != '' && ($timestamp = strtotime($result['items'][$i]['pubDate'])) !==-1) {
- // convert pubDate to specified date format
- $result['items'][$i]['pubDate'] = date($this->date_format, $timestamp);
- }
- // Item counter
- $i++;
- }
- }
-
- $result['items_count'] = $i;
- return $result;
- }
- else // Error in opening return False
- {
- return False;
- }
- }
-}
-
-?>
-
diff --git a/website/push.sh b/website/push.sh
index 594c50b..9623250 100644
--- a/website/push.sh
+++ b/website/push.sh
@@ -1,2 +1,3 @@
#!/bin/sh
-scp -r new/* et.redhat.com:/var/www/sites/cobbler.et.redhat.com
+#scp -r new/* et.redhat.com:/var/www/sites/cobbler.et.redhat.com
+cp -r new/* /var/www/html/stage
diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl
index 4729816..f72ab11 100644
--- a/webui_templates/distro_edit.tmpl
+++ b/webui_templates/distro_edit.tmpl
@@ -15,6 +15,11 @@ function disablename(value)
</script>
#end if
+#if $editable != True
+#set global $owners = $distro.owners
+#include "/usr/share/cobbler/webui_templates/enoaccess.tmpl"
+#end if
+
<form method="POST" action="$base_url">
<fieldset id="cform">
@@ -61,6 +66,8 @@ function disablename(value)
<p class="context-tip">How do you want to modify this object?</p>
</td>
</tr>
+ #else
+ <input type="hidden" name="editmode" value="new"/>
#end if
@@ -172,7 +179,27 @@ function disablename(value)
</td>
</tr>
+ <tr>
+ <td>
+ <label for="owners">Access Allowed For</label>
+ </td>
+ <td>
#if $distro
+ #set ownerslist = ','.join($distro.owners)
+ #end if
+ <input type="text" size="255" style="width: 400px;" name="owners" id="owners"
+ #if $distro
+ value="$ownerslist"
+ #else
+ value="$user"
+ #end if
+ />
+ <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p>
+ </td>
+ </tr>
+
+
+ #if $distro and $editable == True
<tr>
<td>
<label for="delete">Delete</label>
@@ -180,11 +207,13 @@ function disablename(value)
<td>
<input type="checkbox" name="delete1" value="delete1">Yes
<input type="checkbox" name="delete2" value="delete2">Really
+ <input type="checkbox" name="recursive" value="recursive">Delete child objects?
<p class="context-tip">Check both buttons and click save to delete this object</p>
</td>
</tr>
#end if
+ #if $editable == True
<tr>
<td>
</td>
@@ -193,6 +222,7 @@ function disablename(value)
<input type="reset" name="reset" value="Reset"/>
</td>
</tr>
+ #end if
</table>
</fieldset>
diff --git a/webui_templates/enoaccess.tmpl b/webui_templates/enoaccess.tmpl
new file mode 100644
index 0000000..b3a001e
--- /dev/null
+++ b/webui_templates/enoaccess.tmpl
@@ -0,0 +1,12 @@
+#set $myowners = ", ".join($owners)
+
+<blockquote>
+WARNING: You do not have permission to make changes to this
+object. To recieve access, contact your Cobbler server administrator.
+</br>
+
+#if $owners != []
+The access control list for this object is: <B><U>$myowners</U></B>.
+#end if
+</blockquote>
+
diff --git a/webui_templates/ksfile_edit.tmpl b/webui_templates/ksfile_edit.tmpl
index 5e0c5ae..6303442 100644
--- a/webui_templates/ksfile_edit.tmpl
+++ b/webui_templates/ksfile_edit.tmpl
@@ -1,17 +1,65 @@
#extends cobbler.webui.master
-#attr $title = "Cobbler: Edit Kickstart File $ksfile"
+##attr $title = "Cobbler: Edit Kickstart File $ksfile"
#block body
+
+#if $editable != True
+<blockquote>
+NOTE: You do not have permission to make changes to this
+kickstart template and can only read it. It is possible that
+other Cobbler users has secured permissions on Cobbler
+profiles/systems that depend on this template -- changing this
+template would ultimately affect those profile/system records which
+you do not have access to. Alternatively, you may not have access
+to edit *any* kickstart templates. Contact your Cobbler server administrator
+if you need to resolve this.
+</blockquote>
+</br>
+#end if
+
<form method="post" action="$base_url?mode=ksfile_save">
<input type="hidden" name="name" value="$name"/>
<fieldset id="cform">
- <legend>Edit Kickstart File</legend>
+ <legend>Edit Kickstart Template</legend>
+
+ <table>
+ <tr><td colspan="2">
+ <label for="ksdata">Editing: $name</label>
+ <td></tr>
+
+ <tr><td colspan="2">
<pre><textarea rows="40" cols="120" name="ksdata" id="ksdata">$ksdata</textarea></pre>
<br/>
- <input type="submit" name="submit" value="Save"/>
- <input type="reset" name="reset" value="Reset"/>
+ </td></tr>
+
+
+ #if $deleteable
+ <tr><td>
+ <label>Delete</label>
+ </td>
+ <td>
+ <input type="checkbox" name="delete1" value="delete1">Yes
+ <input type="checkbox" name="delete2" value="delete2">Really
+ <p class="context-tip">Check both buttons and click save to delete this object</p>
+ </td></tr>
+ #else
+ <tr><td colspan="2">
+ NOTE: This kickstart template is currently in-use.
+ </td></tr>
+ #end if
+
+ <tr><td colspan="2">
+ #if $editable == True
+ <input type="submit" name="submit" value="Save"/>
+ <input type="reset" name="reset" value="Reset"/>
+
+ #end if
+ </td></tr>
+
+ </table>
</fieldset>
</form>
+
#end block body
diff --git a/webui_templates/ksfile_list.tmpl b/webui_templates/ksfile_list.tmpl
index dcfaa0a..6ae1a9c 100644
--- a/webui_templates/ksfile_list.tmpl
+++ b/webui_templates/ksfile_list.tmpl
@@ -3,7 +3,7 @@
#block body
<table class="sortable">
<thead>
- <caption>Cobbler Kickstart Files</caption>
+ <caption>Cobbler Kickstart Templates</caption>
<tr>
<th class="text">File</th>
<th class="text">Edit/View</th>
@@ -12,6 +12,7 @@
<tbody>
#set $evenodd = 1
#for $ksfile in $ksfiles
+ <!-- $ksfile -->
#if $evenodd % 2 == 0
#set $tr_class = "roweven"
#else
diff --git a/webui_templates/ksfile_new.tmpl b/webui_templates/ksfile_new.tmpl
new file mode 100644
index 0000000..c68bbdd
--- /dev/null
+++ b/webui_templates/ksfile_new.tmpl
@@ -0,0 +1,48 @@
+#extends cobbler.webui.master
+
+#block body
+
+#if $editable != True
+<blockquote>
+NOTE: You do not have permission to create new kickstart templates.
+</blockquote>
+</br>
+#end if
+
+<form method="post" action="$base_url?mode=ksfile_save">
+ <fieldset id="cform">
+ <legend>Create New Kickstart Template</legend>
+
+ <input type="hidden" name="isnew" value="1"/>
+
+ <table>
+ <tr>
+ <td>
+ <label for="name">Filename</label>
+ </td>
+ <td>
+ <input type="text" size="128" style="width: 150px;" name="name" id="name"
+ <p class="context-tip">Example: foo.ks (to be saved in /var/lib/cobbler/kickstarts/)</p>
+ </td>
+ </tr>
+
+
+ <tr><td colspan="2">
+
+ <pre><textarea rows="40" cols="120" name="ksdata" id="ksdata">$ksdata</textarea></pre>
+ <br/>
+
+ </td></tr>
+
+ <tr><td colspan="2">
+ #if $editable == True
+ <input type="submit" name="submit" value="Save"/>
+ <input type="reset" name="reset" value="Reset"/>
+ #end if
+ </td></tr>
+ </table>
+
+ </fieldset>
+</form>
+
+#end block body
diff --git a/webui_templates/ksfile_view.tmpl b/webui_templates/ksfile_view.tmpl
deleted file mode 100644
index b6abf67..0000000
--- a/webui_templates/ksfile_view.tmpl
+++ /dev/null
@@ -1,6 +0,0 @@
-#extends cobbler.webui.master
-
-#block body
-<pre class="config_data">$ksdata</pre>
-#end block body
-
diff --git a/webui_templates/master.tmpl b/webui_templates/master.tmpl
index 96fbe75..5e9438f 100644
--- a/webui_templates/master.tmpl
+++ b/webui_templates/master.tmpl
@@ -43,6 +43,7 @@
<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=ksfile_new" class="menu">Kickstart</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?mode=sync">Sync</a></li>
diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl
index f9eca09..0c8f156 100644
--- a/webui_templates/profile_edit.tmpl
+++ b/webui_templates/profile_edit.tmpl
@@ -13,6 +13,11 @@ function disablename(value)
</script>
#end if
+#if $editable != True
+#set global $owners = $profile.owners
+#include "/usr/share/cobbler/webui_templates/enoaccess.tmpl"
+#end if
+
<form method="post" action="$base_url?mode=profile_save">
<fieldset id="cform">
@@ -75,6 +80,8 @@ function disablename(value)
<p class="context-tip">How do you want to modify this object?</p>
</td>
</tr>
+ #else
+ <input type="hidden" name="editmode" value="new"/>
#end if
#if $subprofile
@@ -308,8 +315,28 @@ function disablename(value)
</td>
</tr>
-
+ <tr>
+ <td>
+ <label for="owners">Access Allowed For</label>
+ </td>
+ <td>
#if $profile
+ #set ownerslist = ','.join($profile.owners)
+ #end if
+ <input type="text" size="255" style="width: 400px;" name="owners" id="owners"
+ #if $profile
+ value="$ownerslist"
+ #else
+ value="$user"
+ #end if
+
+ />
+ <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p>
+ </td>
+ </tr>
+
+
+ #if $profile and $editable == True
<tr>
<td>
<label for="delete">Delete</label>
@@ -317,11 +344,13 @@ function disablename(value)
<td>
<input type="checkbox" name="delete1" value="delete1">Yes
<input type="checkbox" name="delete2" value="delete2">Really
+ <input type="checkbox" name="recursive" value="recursive">Delete child objects?
<p class="context-tip">Check both buttons and click save to delete this object</p>
</td>
</tr>
#end if
+ #if $editable == True
<tr>
<td>
</td>
@@ -330,6 +359,7 @@ function disablename(value)
<input type="reset" name="reset" value="Reset"/>
</td>
</tr>
+ #end if
</fieldset>
#end block body
diff --git a/webui_templates/profile_list.tmpl b/webui_templates/profile_list.tmpl
index f213775..fefe753 100644
--- a/webui_templates/profile_list.tmpl
+++ b/webui_templates/profile_list.tmpl
@@ -38,7 +38,24 @@
<a href="$base_url?mode=profile_edit&name=$profile.parent">$profile.parent</A>
#end if
</td>
- <td>$profile.kickstart</td>
+ <td>
+ #if $profile.kickstart and $profile.kickstart !=""
+ #set $kick = $profile.kickstart
+ #if $kick.startswith("http://") or $kick.startswith("ftp://") or $kick.startswith("nfs://")
+ #if not $kick.startswith("nfs://")
+ <A HREF="$kick">$kick</A>
+ #else
+ $kick
+ #end if
+ #else
+ #set $name = $profile.name
+ <A HREF="/cblr/svc/op/ks/profile/$name">(view rendered)</A>&nbsp;
+ #if $kick != "" and $kick != "<<inherit>>":
+ <A HREF="$base_url?mode=ksfile_edit&name=$kick">(edit template)</A>
+ #end if
+ #end if
+ #end if
+ </td>
</tr>
#end for
</tbody>
diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl
index 445218f..c259a13 100644
--- a/webui_templates/repo_edit.tmpl
+++ b/webui_templates/repo_edit.tmpl
@@ -13,6 +13,10 @@ function disablename(value)
</script>
#end if
+#if $editable != True
+#set global $owners = $repo.owners
+#include "/usr/share/cobbler/webui_templates/enoaccess.tmpl"
+#end if
<form method="post" action="$base_url?mode=repo_save">
<fieldset id="cform">
@@ -58,6 +62,8 @@ function disablename(value)
<p class="context-tip">How do you want to modify this object?</p>
</td>
</tr>
+ #else
+ <input type="hidden" name="editmode" value="new"/>
#end if
<tr>
@@ -84,10 +90,24 @@ function disablename(value)
checked="true"
#end if
/>
- <p class="context-tip">Disable to prevent the mirror from being updated.</p>
+ <p class="context-tip">Uncheck to prevent the mirror from being updated again.</p>
</td>
</tr>
+ <br/>
+ <tr>
+ <td>
+ <label for="keep_updated">Mirror Locally</label>
+ </td>
+ <td>
+ <input type="checkbox" name="mirror_locally" id="mirror_locally"
+ #if (not $repo) or $repo.mirror_locally
+ checked="true"
+ #end if
+ />
+ <p class="context-tip">Uncheck to reference the repository directly instead of mirroring.</p>
+ </td>
+ </tr>
<br/>
<tr>
@@ -124,7 +144,7 @@ function disablename(value)
<tr>
<td>
- <label for="createrepo_flags">createrepo flags</label>
+ <label for="createrepo_flags">Createrepo flags</label>
</td>
<td>
<input type="text" size="255" style="width: 150px;" name="creatrepoflags" id="createrepoflags"
@@ -138,7 +158,7 @@ function disablename(value)
<tr>
<td>
- <label for="arch">arch</label>
+ <label for="arch">Arch</label>
</td>
<td>
<input type="text" size="255" style="width: 150px;" name="arch" id="arch"
@@ -152,7 +172,7 @@ function disablename(value)
<tr>
<td>
- <label for="yumopts">yum options</label>
+ <label for="yumopts">Yum options</label>
</td>
<td>
<input type="text" size="255" style="width: 150px;" name="yumopts" id="yumopts"
@@ -164,8 +184,27 @@ function disablename(value)
</td>
</tr>
-
+ <tr>
+ <td>
+ <label for="owners">Access Allowed For</label>
+ </td>
+ <td>
#if $repo
+ #set ownerslist = ','.join($repo.owners)
+ #end if
+ <input type="text" size="255" style="width: 400px;" name="owners" id="owners"
+ #if $repo
+ value="$ownerslist"
+ #else
+ value="$user"
+ #end if
+ />
+ <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p>
+ </td>
+ </tr>
+
+
+ #if $repo and $editable == True
<tr>
<td>
<label for="delete">Delete</label>
@@ -178,6 +217,7 @@ function disablename(value)
</tr>
#end if
+ #if $editable == True
<tr>
<td>
</td>
@@ -185,6 +225,7 @@ function disablename(value)
<input type="submit" name="submit" value="Save"/>
<input type="reset" name="reset" value="Reset"/>
</tr>
+ #end if
</table>
</fieldset>
@@ -192,10 +233,9 @@ function disablename(value)
<br/>
<blockquote>
-Note: Newly added repos contain no package content until "cobbler reposync" is run
-from the command line, which means that profiles relying on these repositories will
-not install. Placing "cobbler reposync" on a crontab to ensure frequent updates
-is recommended procedure.
+Note: Newly added repo definitions will not be usable until
+"cobbler reposync" is run from the command line on this system.
+Placing "cobbler reposync" on a crontab is recommended procedure.
</blockquote>
<br/>
diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl
index 22bdda4..b996520 100644
--- a/webui_templates/system_edit.tmpl
+++ b/webui_templates/system_edit.tmpl
@@ -14,9 +14,11 @@
function delete_interface(num)
{
- #for $field in $fields
- document.getElementById("${field}-intf" + num).value = "";
- #end for
+ #if $editable == True
+ #for $field in $fields
+ document.getElementById("${field}-intf" + num).value = "";
+ #end for
+ #end if
toggleRowVisibility("id" + num);
}
@@ -65,7 +67,6 @@ function get_random_mac(field)
#set $defined_interfaces = [ "intf0" ]
#end if
-
###
### now generate the onload function.
###
@@ -87,6 +88,11 @@ function page_onload() {
}
</script>
+#if $editable != True
+#set global $owners = $system.owners
+#include "/usr/share/cobbler/webui_templates/enoaccess.tmpl"
+#end if
+
<form method="post" action="$base_url?mode=system_save">
<fieldset id="cform">
@@ -132,6 +138,8 @@ function page_onload() {
<p class="context-tip">How do you want to modify this object?</p>
</td>
</tr>
+ #else
+ <input type="hidden" name="editmode" value="new"/>
#end if
<tr id="id9002">
@@ -208,6 +216,126 @@ function page_onload() {
</td>
</tr>
+ <tr>
+ <td>
+ <label for="owners">Access Allowed For</label>
+ </td>
+ <td>
+ #if $system
+ #set ownerslist = ','.join($system.owners)
+ #end if
+ <input type="text" size="255" style="width: 400px;" name="owners" id="owners"
+ #if $system
+ value="$ownerslist"
+ #else
+ value="$user"
+ #end if
+ />
+ <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td>
+ <label for="virtfilesize">Virt Disk (GB)</label>
+ </td>
+ <td>
+ <input type="text" size="5" style="width: 150px;" name="virtfilesize" id="virtfilesize"
+ #if $system
+ value="$system.virt_file_size"
+ #end if
+ />
+ <p class="context-tip">For virtual installs only, require this disk size in GB.</p>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <label for="virtram">Virt RAM (MB)</label>
+ </td>
+ <td>
+ <input type="text" size="5" style="width: 150px;" name="virtram" id="virtram"
+ #if $system
+ value="$system.virt_ram"
+ #end if
+ />
+ <p class="context-tip">For virtual installs only, allocate this amount of RAM, in MB.</p>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <label for="virttype">Virt Type</label>
+ </td>
+ <td>
+
+ #if $system and $system.virt_type == "<<inherit>>"
+ <input type="radio" name="virttype" id="virttype" value="<<inherit>>" checked>Inherit
+ #else
+ #if $system
+ <input type="radio" name="virttype" id="virttype" value="<<inherit>>">Inherit
+ #else
+ <input type="radio" name="virttype" id="virttype" value="<<inherit>>" checked>Inherit
+ #end if
+ #end if
+
+
+ #if $system and $system.virt_type == "auto"
+ <input type="radio" name="virttype" id="virttype" value="auto" checked>Any
+ #else
+ #if $system
+ <input type="radio" name="virttype" id="virttype" value="auto">Any
+ #else
+ <input type="radio" name="virttype" id="virttype" value="auto">Any
+ #end if
+ #end if
+
+ #if $system and $system.virt_type == "xenpv"
+ <input type="radio" name="virttype" id="virttype" value="xenpv" checked>Xen (pv)
+ #else
+ <input type="radio" name="virttype" id="virttype" value="xenpv">Xen (pv)
+ #end if
+
+ #if $system and $system.virt_type == "qemu"
+ <input type="radio" name="virttype" id="virttype" value="qemu" checked>qemu/KVM
+ #else
+ <input type="radio" name="virttype" id="virttype" value="qemu">qemu/KVM
+ #end if
+ <p class="context-tip">What virtualization technology should koan use?</p>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <label for="virtpath">Virt Path</label>
+ </td>
+ <td>
+ <input type="text" size="255" style="width: 400px;" name="virtpath" id="virtpath"
+ #if $system
+ value="$system.virt_path"
+ #end if
+ />
+ <p class="context-tip">Sets koan's storage preferences, read manpage or leave blank.</p>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <label for="virtpath">Virt CPUs</label>
+ </td>
+ <td>
+ <input type="text" size="255" style="width: 150px;" name="virtcpus" id="virtcpus"
+ #if $system
+ value="$system.virt_cpus"
+ #end if
+ />
+ <p class="context-tip">How many virtual CPUs? This is an integer.</p>
+ </td>
+ </tr>
+
+
+
## ====================================== start of looping through interfaces
@@ -321,8 +449,6 @@ function page_onload() {
</td>
</tr>
- ## FIXME: add virt_bridge editing (like above)
-
<tr class="listrow" id="child-id${counter}-5">
<td>
<label for="virtbridge-$interface">Virt Bridge</label>
@@ -335,8 +461,6 @@ function page_onload() {
</td>
</tr>
- ## FIXME: add subnet editing (like above)
-
<tr class="listrow" id="child-id${counter}-6">
<td>
<label for="subnet-$interface">Subnet</label>
@@ -349,8 +473,6 @@ function page_onload() {
</td>
</tr>
- ## FIXME: add gateway editing (like above)
-
<tr class="listrow" id="child-id${counter}-7">
<td>
<label for="gateway-$interface">Gateway</label>
@@ -366,11 +488,20 @@ function page_onload() {
#if $interface != "intf0"
<tr class="listrow" id="child-id${counter}-8">
<td>
- <label for="enabled-$interface">Remove</label>
+ #if $editable == True
+ <label for="enabled-$interface">Remove</label>
+ #else
+ <label for="enabled-$interface">Hide</label>
+ #end if
</td>
<td>
- <input type="button" name="delete-$interface" value="remove" onclick="delete_interface($counter)">
- <p class="context-tip">Clicking this button removes the interface from the configuration.</p>
+ #if $editable == True
+ <input type="button" name="delete-$interface" value="remove" onclick="delete_interface($counter)">
+ <p class="context-tip">Clicking this button removes the interface from the configuration.</p>
+ #else
+ <input type="button" name="delete-$interface" value="hide" onclick="delete_interface($counter)">
+
+ #end if
</td>
</tr>
#end if
@@ -392,7 +523,7 @@ function page_onload() {
</td>
</tr>
- #if $system
+ #if $system and $editable == True
<tr id="id10001">
<td>
<label for="delete">Delete</label>
@@ -405,6 +536,7 @@ function page_onload() {
</tr>
#end if
+ #if $editable == True
<tr id="9008">
<td>
</td>
@@ -413,6 +545,7 @@ function page_onload() {
<input type="reset" name="reset" value="Reset"/>
</td>
</tr>
+ #end if
</table>
diff --git a/webui_templates/system_list.tmpl b/webui_templates/system_list.tmpl
index 5c70ffc..6ab5fac 100644
--- a/webui_templates/system_list.tmpl
+++ b/webui_templates/system_list.tmpl
@@ -14,9 +14,7 @@
<tr>
<th class="text">Name</th>
<th class="text">Profile</th>
- ## FIXME: how to handle for multiple interface listing? <th class="text">MAC</th>
- ## <th class="text">IP</th>
- ## <th class="text">Hostname</th>
+ <th class="text">Kickstart</th>
</tr>
</thead>
<tbody>
@@ -39,7 +37,27 @@
## <td> ${system.mac_address} </td>
## <td> ${system.ip_address} </td>
## <td> ${system.hostname} </td>
+
+ <td>
+ #set $kick = $system.kickstart
+ #if $kick.startswith("http://") or $kick.startswith("ftp://") or $kick.startswith("nfs://")
+ #if not $kick.startswith("nfs://")
+ <A HREF="$kick">$kick</A>
+ #else
+ $kick
+ #end if
+ #else
+ #set $name = $system.name
+ #set $pname = $system.profile
+ <A HREF="/cblr/svc/op/ks/system/$name">(view rendered)</A>&nbsp;
+ #if $kick != "<<inherit>>" and $kick != ""
+ <A HREF="$base_url?mode=ksfile_edit&name=$kick">(edit template)</A>
+ #end if
+ #end if
+ </td>
</tr>
+
+
#end for
</tbody>
</table>