Merge lp:~racb/ubuntu/oneiric/cobbler/858878_858883 into lp:ubuntu/oneiric/cobbler

Proposed by Robie Basak
Status: Rejected
Rejected by: Robie Basak
Proposed branch: lp:~racb/ubuntu/oneiric/cobbler/858878_858883
Merge into: lp:ubuntu/oneiric/cobbler
Diff against target: 11280 lines (+10424/-53)
55 files modified
.pc/58_fix_egg_cache.patch/web/cobbler.wsgi (+10/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/filter.tmpl (+155/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/generic_edit.tmpl (+481/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/generic_list.tmpl (+192/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/import.tmpl (+47/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/ksfile_edit.tmpl (+58/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/login.tmpl (+29/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/master.tmpl (+66/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/paginate.tmpl (+22/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/snippet_edit.tmpl (+54/-0)
.pc/59_add_csrf_protection.patch/web/cobbler_web/views.py (+1162/-0)
.pc/59_add_csrf_protection.patch/web/settings.py (+69/-0)
.pc/60_yaml_safe_load.patch/cobbler/api.py (+947/-0)
.pc/60_yaml_safe_load.patch/cobbler/item.py (+427/-0)
.pc/60_yaml_safe_load.patch/cobbler/modules/serializer_catalog.py (+241/-0)
.pc/60_yaml_safe_load.patch/cobbler/modules/serializer_couch.py (+136/-0)
.pc/60_yaml_safe_load.patch/cobbler/remote.py (+2547/-0)
.pc/60_yaml_safe_load.patch/cobbler/services.py (+462/-0)
.pc/60_yaml_safe_load.patch/cobbler/utils.py (+2074/-0)
.pc/60_yaml_safe_load.patch/scripts/cobbler-ext-nodes (+21/-0)
.pc/60_yaml_safe_load.patch/scripts/index.py (+199/-0)
.pc/60_yaml_safe_load.patch/scripts/services.py (+99/-0)
.pc/applied-patches (+3/-0)
cobbler/api.py (+1/-1)
cobbler/item.py (+1/-1)
cobbler/modules/serializer_catalog.py (+4/-4)
cobbler/modules/serializer_couch.py (+1/-1)
cobbler/remote.py (+2/-2)
cobbler/services.py (+1/-1)
cobbler/utils.py (+2/-2)
debian/changelog (+21/-0)
debian/cobbler-common.install (+0/-1)
debian/cobbler-web.dirs (+1/-0)
debian/cobbler-web.postinst (+3/-0)
debian/cobbler.postinst (+1/-0)
debian/control (+4/-4)
debian/patches/58_fix_egg_cache.patch (+19/-0)
debian/patches/59_add_csrf_protection.patch (+569/-0)
debian/patches/60_yaml_safe_load.patch (+158/-0)
debian/patches/series (+3/-0)
scripts/cobbler-ext-nodes (+1/-1)
scripts/index.py (+1/-1)
scripts/services.py (+1/-1)
web/cobbler.wsgi (+1/-1)
web/cobbler_web/templates/filter.tmpl (+8/-2)
web/cobbler_web/templates/generic_edit.tmpl (+1/-0)
web/cobbler_web/templates/generic_list.tmpl (+14/-4)
web/cobbler_web/templates/import.tmpl (+1/-0)
web/cobbler_web/templates/ksfile_edit.tmpl (+1/-0)
web/cobbler_web/templates/login.tmpl (+1/-0)
web/cobbler_web/templates/master.tmpl (+13/-6)
web/cobbler_web/templates/paginate.tmpl (+16/-4)
web/cobbler_web/templates/snippet_edit.tmpl (+1/-0)
web/cobbler_web/views.py (+70/-16)
web/settings.py (+2/-0)
To merge this branch: bzr merge lp:~racb/ubuntu/oneiric/cobbler/858878_858883
Reviewer Review Type Date Requested Status
Dave Walker Pending
Review via email: mp+81996@code.launchpad.net

Description of the change

This is based on top of Clint's branch which is already awaiting review.

To post a comment you must log in.

Unmerged revisions

54. By Robie Basak

Backport safe YAML load from upstream. (LP: #858883)

53. By Robie Basak

Backport CSRF protection from upstream. (LP: #858878)

52. By Clint Byrum

debian/cobbler-web.postinst: fix perms on webui_sessions to
be more secure (LP: #863755)

51. By Clint Byrum

debian/cobbler-common.install: remove users.digest as it is
not required and contains a known password that would leave
cobblerd vulnerable if started before configuration is done.

50. By Clint Byrum

* debian/cobbler.postinst: create users.digest mode 0600 so it
  is not world readable. (LP: #858860)
* debian/control: cobbler needs to depend on python-cobbler
  (LP: #863738)
* debian/patches/58_fix_egg_cache.patch: Do not point dangerous
  PYTHON_EGG_CACHE at world writable directory. (LP: #858875)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.pc/58_fix_egg_cache.patch'
2=== added file '.pc/58_fix_egg_cache.patch/.timestamp'
3=== added directory '.pc/58_fix_egg_cache.patch/web'
4=== added file '.pc/58_fix_egg_cache.patch/web/cobbler.wsgi'
5--- .pc/58_fix_egg_cache.patch/web/cobbler.wsgi 1970-01-01 00:00:00 +0000
6+++ .pc/58_fix_egg_cache.patch/web/cobbler.wsgi 2011-11-11 16:30:32 +0000
7@@ -0,0 +1,10 @@
8+import os
9+import sys
10+
11+os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
12+os.environ['PYTHON_EGG_CACHE'] = '/tmp'
13+sys.path.append('/usr/share/cobbler/web')
14+sys.path.append('/usr/share/cobbler/web/cobbler_web')
15+
16+import django.core.handlers.wsgi
17+application = django.core.handlers.wsgi.WSGIHandler()
18
19=== added directory '.pc/59_add_csrf_protection.patch'
20=== added file '.pc/59_add_csrf_protection.patch/.timestamp'
21=== added directory '.pc/59_add_csrf_protection.patch/web'
22=== added directory '.pc/59_add_csrf_protection.patch/web/cobbler_web'
23=== added directory '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates'
24=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/filter.tmpl'
25--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/filter.tmpl 1970-01-01 00:00:00 +0000
26+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/filter.tmpl 2011-11-11 16:30:32 +0000
27@@ -0,0 +1,155 @@
28+{% if pageinfo %}
29+<script type="text/javascript">
30+function add_filter() {
31+ field_name = document.getElementById("filter_field");
32+ field_value = document.getElementById("filter_value");
33+ if (field_name.value == "") {
34+ alert("You must select a field to filter on.");
35+ } else if (field_value.value == "") {
36+ alert("You must select a filter value.");
37+ } else {
38+ location = '/cobbler_web/{{ what }}/modifylist/addfilter/'+field_name.value+':'+field_value.value;
39+ }
40+}
41+</script>
42+<ul id="filter-adder">
43+ <li>
44+ <label for="filter_field">Filter</label>
45+ <select id="filter_field" name="filter_field" class="filter">
46+ <option value=""></option>
47+ <option value="name">name</option>
48+ {% ifequal what "distro" %}<option value="arch">arch</option>
49+ <option value="breed">breed</option>
50+ <option value="comment">comment</option>
51+ <option value="kernel">kernel</option>
52+ <option value="initrd">initrd</option>
53+ <option value="kernel_options">kernel options</option>
54+ <option value="kernel_options_post">kernel options (post install)</option>
55+ <option value="ks_meta">kickstart metadata</option>
56+ <option value="mgmt_classes">management classes</option>
57+ <option value="os_version">os version</option>
58+ <option value="owners">owners</option>
59+ <option value="redhat_management_key">red hat management key</option>
60+ <option value="redhat_management_server">red hat management server</option>
61+ <option value="fetchable_files">fetchable files</option>
62+ <option value="template_files">template files</option>{% endifequal %}
63+ {% ifequal what "profile" %}<option value="comment">comment</option>
64+ <option value="dhcp_tag">dhcp tag</option>
65+ <option value="distro">distro</option>
66+ <option value="owners">owners</option>
67+ <option value="kickstart">kickstart</option>
68+ <option value="kernel_options">kernel options</option>
69+ <option value="kernel_options_post">kernel options (post install)</option>
70+ <option value="ks_meta">kickstart metadata</option>
71+ <option value="mgmt_classes">management classes</option>
72+ <option value="mgmt_parameters">management parameters</option>
73+ <option value="name_servers">name servers</option>
74+ <option value="name_servers_search">name servers search</option>
75+ <option value="parent">parent (subprofiles only)</option>
76+ <option value="redhat_management_key">red hat management key</option>
77+ <option value="redhat_management_server">red hat management server</option>
78+ <option value="repos">repos</option>
79+ <option value="fetchable_files">fetchable files</option>
80+ <option value="template_files">template files</option>
81+ <option value="virt_auto_boot">virt autoboot</option>
82+ <option value="virt_bridge">virt bridge</option>
83+ <option value="virt_cpus">virt cpus</option>
84+ <option value="virt_file_size">virt file size (GB)</option>
85+ <option value="virt_path">virt path</option>
86+ <option value="virt_ram">virt ram (MB)</option>
87+ <option value="virt_type">virt type</option>
88+ <option value="server">server override</option>{% endifequal %}
89+ {% ifequal what "system" %}<option value="bonding">bonding</option>
90+ <option value="bonding_master">bonding master</option>
91+ <option value="bonding_opts">bonding opts</option>
92+ <option value="comment">comment</option>
93+ <option value="dhcp_tag">dhcp tag</option>
94+ <option value="distro">distro</option>
95+ <option value="dns_name">dns name</option>
96+ <option value="gateway">gateway</option>
97+ <option value="hostname">hostname</option>
98+ <option value="image">image</option>
99+ <option value="ip_address">ip address</option>
100+ <option value="kickstart">kickstart</option>
101+ <option value="kernel_options">kernel options</option>
102+ <option value="kernel_options_post">kernel options (post install)</option>
103+ <option value="ks_meta">kickstart metadata</option>
104+ <option value="mac_address">mac address</option>
105+ <option value="mgmt_classes">management classes</option>
106+ <option value="name_servers">name servers</option>
107+ <option value="name_servers_search">name servers search</option>
108+ <option value="netboot_enabled">netboot enabled</option>
109+ <option value="owners">owners</option>
110+ <option value="power_address">power address</option>
111+ <option value="power_id">power id</option>
112+ <option value="power_pass">power password</option>
113+ <option value="power_type">power type</option>
114+ <option value="power_user">power user</option>
115+ <option value="profile">profile</option>
116+ <option value="redhat_management_key">red hat management key</option>
117+ <option value="redhat_management_server">red hat management server</option>
118+ <option value="repos">repos</option>
119+ <option value="server">server override</option>
120+ <option value="subnet">subnet</option>
121+ <option value="static">static</option>
122+ <option value="static_routes">static_routes</option>
123+ <option value="fetchable_files">fetchable files</option>
124+ <option value="template_files">template files</option>
125+ <option value="virt_auto_boot">virt autoboot</option>
126+ <option value="virt_bridge">virt bridge</option>
127+ <option value="virt_cpus">virt cpus</option>
128+ <option value="virt_file_size">virt file size (GB)</option>
129+ <option value="virt_path">virt path</option>
130+ <option value="virt_ram">virt ram (MB)</option>
131+ <option value="virt_type">virt type</option>{% endifequal %}
132+ {% ifequal what "repo" %}<option value="arch">arch</option>
133+ <option value="breed">breed</option>
134+ <option value="comment">comment</option>
135+ <option value="createrepo_flags">createrepo flags</option>
136+ <option value="environment">environment</option>
137+ <option value="keep_updated">keep updated</option>
138+ <option value="mirror">mirror</option>
139+ <option value="mirror_locally">mirror locally</option>
140+ <option value="owners">owners</option>
141+ <option value="priority">priority</option>
142+ <option value="rpm_list">rpm list</option>
143+ <option value="yumopts">yum options</option>{% endifequal %}
144+ {% ifequal what "image" %}<option value="arch">arch</option>
145+ <option value="breed">breed</option>
146+ <option value="comment">comment</option>
147+ <option value="file">file</option>
148+ <option value="image_type">image type</option>
149+ <option value="network_count">network count</option>
150+ <option value="os_version">os version</option>
151+ <option value="owners">owners</option>
152+ <option value="virt_auto_boot">virt autoboot</option>
153+ <option value="virt_ram">virt ram (MB)</option>
154+ <option value="virt_path">virt path</option>
155+ <option value="virt_type">virt type</option>
156+ <option value="virt_cpus">virt cpus</option>
157+ <option value="virt_bridge">virt bridge</option>
158+ <option value="virt_file_size">virt file size (GB)</option>{% endifequal %}
159+ {% ifequal what "mgmtclass" %}<option value="comment">comment</option>
160+ <option value="owners">owners</option>
161+ <option value="packages">packages</option>
162+ <option value="files">files</option>{% endifequal %}
163+ {% ifequal what "network" %}<option value="address">address</option>
164+ <option value="broadcast">broadcast</option>
165+ <option value="cidr">cidr</option>
166+ <option value="gateway">gateway</option>
167+ <option value="name_servers">name servers</option>
168+ <option value="owners">owners</option>
169+ {% endifequal %}
170+ </select>
171+ <label for="filter_value">on</label><input type="text" name="filter_value" id="filter_value" onkeypress="javascript:if (event.keyCode == 13) add_filter();" />
172+ <input class="button" type="button" name="filter_add" id="filter_add" onclick="javascript: add_filter();" value="Add" />
173+ </li>
174+</ul>
175+ {% if filters %}
176+<ul id="filter-remover">
177+ {% for key,value in filters.items %}
178+ <li><a href="/cobbler_web/{{ what }}/modifylist/removefilter/{{ key }}" title="remove">✖</a> {{ key }} = {{ value }}</li>
179+ {% endfor %}
180+</ul>
181+ {% endif %}
182+{% endif %}
183
184=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/generic_edit.tmpl'
185--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/generic_edit.tmpl 1970-01-01 00:00:00 +0000
186+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/generic_edit.tmpl 2011-11-11 16:30:32 +0000
187@@ -0,0 +1,481 @@
188+{% extends "master.tmpl" %}
189+{% load site %}
190+{% block content %}
191+<script type="text/javascript">
192+{% ifequal what "system" %}
193+function intf_enable_field(field,enabled)
194+{
195+ if (enabled) {
196+ document.getElementById(field+"_row").style.display="table-row"
197+ } else {
198+ document.getElementById(field+"_row").style.display="none"
199+ }
200+}
201+
202+function intf_update_visibility()
203+{
204+ is_slave = false
205+ is_master = false
206+ value = document.getElementById("bonding").value
207+ if (value == "slave") {
208+ is_slave = true;
209+ }
210+ else if (value == "master") {
211+ is_master = true;
212+ }
213+
214+ is_static = document.getElementById("static").checked
215+
216+ intf_enable_field("static",!is_slave)
217+ intf_enable_field("ip_address",(!is_slave))
218+ intf_enable_field("subnet",(!is_slave) && is_static)
219+ intf_enable_field("dns_name",!is_slave)
220+ intf_enable_field("static_routes",!is_slave)
221+ intf_enable_field("dhcp_tag",!is_slave)
222+ intf_enable_field("virt_bridge",!is_master)
223+ intf_enable_field("bonding_opts",is_master)
224+ intf_enable_field("bonding_master",is_slave)
225+ intf_enable_field("mtu",!is_slave)
226+ intf_enable_field("ipv6_address",!is_slave)
227+ intf_enable_field("ipv6_secondaries",!is_slave)
228+ intf_enable_field("ipv6_mtu",!is_slave)
229+ intf_enable_field("ipv6_static_routes",!is_slave)
230+ intf_enable_field("ipv6_default_gateway",!is_slave)
231+}
232+
233+function get_selected_interface()
234+{
235+ return document.getElementById("interfaces").value
236+}
237+
238+last_interface = ""
239+
240+function on_interface_change()
241+{
242+ // called when the user picks something new from the interface selector
243+ save_intf(last_interface);
244+ clear_intf();
245+ last_interface = get_selected_interface()
246+ load_intf()
247+}
248+
249+function populate_widgets()
250+{
251+ var buf = "";
252+ buf = buf + "<input type='text' name='newinterfacename' id='newinterfacename' value=''>";
253+ buf = buf + "<input class='button' type='button' name='addinterface' value='Add' onclick='javascript:on_interface_add()' />";
254+ document.getElementById("widget_network_widget_a").innerHTML = buf;
255+
256+ buf = "";
257+ buf = buf + "<select name='interfaces' id='interfaces' onchange='javascript:on_interface_change()'>";
258+ buf = buf + "</select>";
259+ buf = buf + "<input class='button' type='button' name='deleteinterface' value='Delete' onclick='javascript:on_interface_delete()' />";
260+ document.getElementById("widget_network_widget_b").innerHTML = buf;
261+}
262+
263+function on_interface_add()
264+{
265+ // called when the user hits the "new interface" button
266+
267+ var iname = document.getElementById("newinterfacename").value
268+
269+ if ((iname == "") || (iname == " ")) {
270+ alert("invalid interface name")
271+ return
272+ }
273+
274+ if (interface_table[iname] != null) {
275+ alert("interface already exists")
276+ return
277+ }
278+
279+ if (interface_table[iname] == null) {
280+ interface_table[iname] = new Array()
281+ }
282+
283+ // fixme: would be nice to have this more autogenerated from the template
284+ interface_table[iname]["mac_address"] = ""
285+ interface_table[iname]["bonding"] = ""
286+ interface_table[iname]["bonding_master"] = ""
287+ interface_table[iname]["bonding_opts"] = ""
288+ interface_table[iname]["ip_address"] = ""
289+ interface_table[iname]["dns_name"] = ""
290+ interface_table[iname]["static_routes"] = ""
291+ interface_table[iname]["dhcp_tag"] = ""
292+ interface_table[iname]["virt_bridge"] = ""
293+ interface_table[iname]["subnet"] = ""
294+ interface_table[iname]["static"] = false
295+ interface_table[iname]["present"] = "1"
296+ interface_table[iname]["original"] = "0"
297+ interface_table[iname]["mtu"] = ""
298+ interface_table[iname]["ipv6_address"] = ""
299+ interface_table[iname]["ipv6_secondaries"] = ""
300+ interface_table[iname]["ipv6_mtu"] = ""
301+ interface_table[iname]["ipv6_static_routes"] = ""
302+ interface_table[iname]["ipv6_default_gateway"] = ""
303+
304+ var interfaces = document.getElementById("interfaces")
305+ ilen = interfaces.length
306+ var new_option = new Option(iname,iname)
307+ interfaces.options[ilen] = new_option
308+ interfaces.selectedIndex = ilen
309+ on_interface_change() // explicit firing required
310+}
311+
312+function on_interface_delete()
313+{
314+ selected = get_selected_interface()
315+ interfaces = document.getElementById("interfaces")
316+
317+ if (interfaces.length == 1) {
318+ alert("systems must always have at least one interface")
319+ return
320+ }
321+
322+ clear_intf()
323+ for (i = interfaces.options.length - 1; i>=0; i--) {
324+ if (interfaces.options[i].value == selected) {
325+ interfaces.remove(i)
326+ }
327+ }
328+ interface_table[selected]["present"] = 0
329+ interfaces.selectedIndex = 0
330+ load_intf()
331+}
332+
333+function get_enabled_field(field,enabled)
334+{
335+ if (enabled) {
336+ return document.getElementById(field).value
337+ } else {
338+ return ""
339+ }
340+}
341+
342+function save_intf(which)
343+{
344+ // this populates the interface widget with the data for the currently selected interface
345+ // and is called when the user picks a certain interface from the drop-down
346+
347+ iname = which
348+ var itable = interface_table[iname]
349+ if (itable == null) {
350+ interface_table[iname] = new Array()
351+ itable = interface_table[iname]
352+ }
353+
354+ var bond = document.getElementById("bonding").value
355+ var is_slave = false;
356+ var is_master = false;
357+ if (bond == "master") {
358+ is_master = true;
359+ }
360+ if (bond == "slave") {
361+ is_slave = true;
362+ }
363+ is_static=document.getElementById("static").checked
364+
365+ itable["name"] = iname
366+ itable["mac_address"] = document.getElementById("mac_address").value
367+ itable["bonding"] = bond
368+ itable["bonding_master"] = get_enabled_field("bonding_master",is_slave)
369+ itable["bonding_opts"] = get_enabled_field("bonding_opts",is_master)
370+ itable["static"] = is_static
371+ itable["ip_address"] = get_enabled_field("ip_address",(!is_slave))
372+ itable["subnet"] = get_enabled_field("subnet",(!is_slave) && is_static)
373+ itable["dns_name"] = get_enabled_field("dns_name",!is_slave)
374+ itable["static_routes"] = get_enabled_field("static_routes",!is_slave)
375+ itable["dhcp_tag"] = get_enabled_field("dhcp_tag",!is_slave)
376+ itable["virt_bridge"] = get_enabled_field("virt_bridge",!is_master)
377+ itable["present"] = document.getElementById("present").value
378+ itable["original"] = document.getElementById("original").value
379+ itable["mtu"] = get_enabled_field("mtu",!is_slave)
380+ itable["ipv6_address"] = get_enabled_field("ipv6_address",!is_slave)
381+ itable["ipv6_secondaries"] = get_enabled_field("ipv6_secondaries",!is_slave)
382+ itable["ipv6_mtu"] = get_enabled_field("ipv6_mtu",!is_slave)
383+ itable["ipv6_static_routes"] = get_enabled_field("ipv6_static_routes",!is_slave)
384+ itable["ipv6_default_gateway"] = get_enabled_field("ipv6_default_gateway",!is_slave)
385+}
386+
387+function load_intf()
388+{
389+ // this populates the interface widget with the data for the currently selected interface
390+ // and is called when the user picks a certain interface from the drop-down
391+ // FIXME: can we load this up from the template data?
392+ intf = get_selected_interface()
393+ document.getElementById("mac_address").value = interface_table[intf]["mac_address"]
394+ document.getElementById("bonding").value = interface_table[intf]["bonding"]
395+ document.getElementById("bonding_master").value = interface_table[intf]["bonding_master"]
396+ document.getElementById("bonding_opts").value = interface_table[intf]["bonding_opts"]
397+ document.getElementById("static").checked = interface_table[intf]["static"]
398+ document.getElementById("ip_address").value = interface_table[intf]["ip_address"]
399+ document.getElementById("subnet").value = interface_table[intf]["subnet"]
400+ document.getElementById("dns_name").value = interface_table[intf]["dns_name"]
401+ document.getElementById("static_routes").value = interface_table[intf]["static_routes"]
402+ document.getElementById("dhcp_tag").value = interface_table[intf]["dhcp_tag"]
403+ document.getElementById("virt_bridge").value = interface_table[intf]["virt_bridge"]
404+ document.getElementById("present").value = interface_table[intf]["present"]
405+ document.getElementById("original").value = interface_table[intf]["original"]
406+ document.getElementById("mtu").value = interface_table[intf]["mtu"]
407+ document.getElementById("ipv6_address").value = interface_table[intf]["ipv6_address"]
408+ document.getElementById("ipv6_secondaries").value = interface_table[intf]["ipv6_secondaries"]
409+ document.getElementById("ipv6_mtu").value = interface_table[intf]["ipv6_mtu"]
410+ document.getElementById("ipv6_static_routes").value = interface_table[intf]["ipv6_static_routes"]
411+ document.getElementById("ipv6_default_gateway").value = interface_table[intf]["ipv6_default_gateway"]
412+
413+ intf_update_visibility()
414+}
415+
416+function clear_intf()
417+{
418+ // this clears the interface list and populates it with the currently selected interface data
419+
420+ document.getElementById("mac_address").value = ""
421+ document.getElementById("bonding").value = "na"
422+ document.getElementById("bonding_master").value = ""
423+ document.getElementById("bonding_opts").value = ""
424+ document.getElementById("static").checked = true
425+ document.getElementById("ip_address").value = ""
426+ document.getElementById("subnet").value = ""
427+ document.getElementById("dns_name").value = ""
428+ document.getElementById("static_routes").value = ""
429+ document.getElementById("dhcp_tag").value = ""
430+ document.getElementById("virt_bridge").value = ""
431+ document.getElementById("present").value = "1"
432+ document.getElementById("original").value = "0"
433+ document.getElementById("mtu").value = ""
434+ document.getElementById("ipv6_address").value = ""
435+ document.getElementById("ipv6_secondaries").value = ""
436+ document.getElementById("ipv6_mtu").value = ""
437+ document.getElementById("ipv6_static_routes").value = ""
438+ document.getElementById("ipv6_default_gateway").value = ""
439+
440+
441+}
442+
443+function build_interface_table()
444+{
445+ // called during onload, this stores all of the interfaces from Cheetah in javascript
446+ // so that we can manipulate them dynamically in more interesting ways
447+ interface_table = new Array()
448+ var last = ""
449+ {% smart_if interface_length > 0 %}
450+ var ifound = 0
451+ {% for key,value in interfaces.items %}
452+ interface_table['{{ key }}'] = new Array()
453+ interface_table['{{ key }}']["mac_address"] = "{{ value.mac_address }}"
454+ interface_table['{{ key }}']["bonding"] = "{{ value.bonding }}"
455+ interface_table['{{ key }}']["bonding_master"] = "{{ value.bonding_master }}"
456+ interface_table['{{ key }}']["bonding_opts"] = "{{ value.bonding_opts }}"
457+ interface_table['{{ key }}']["static"] = {{ value.static|lower }}
458+ interface_table['{{ key }}']["ip_address"] = "{{ value.ip_address }}"
459+ interface_table['{{ key }}']["subnet"] = "{{ value.subnet }}"
460+ interface_table['{{ key }}']["dns_name"] = "{{ value.dns_name }}"
461+ interface_table['{{ key }}']["static_routes"] = "{{ value.static_routes|join:" " }}"
462+ interface_table['{{ key }}']["dhcp_tag"] = "{{ value.dhcp_tag }}"
463+ interface_table['{{ key }}']["virt_bridge"] = "{{ value.virt_bridge }}"
464+ interface_table['{{ key }}']["present"] = "1"
465+ interface_table['{{ key }}']["original"] = "1"
466+ interface_table['{{ key }}']["mtu"] = "{{ value.mtu }}"
467+ interface_table['{{ key }}']["ipv6_address"] = "{{ value.ipv6_address }}"
468+ interface_table['{{ key }}']["ipv6_secondaries"] = "{{ value.ipv6_secondaries|join:" " }}"
469+ interface_table['{{ key }}']["ipv6_mtu"] = "{{ value.ipv6_mtu }}"
470+ interface_table['{{ key }}']["ipv6_static_routes"] = "{{ value.ipv6_static_routes|join:" " }}"
471+ interface_table['{{ key }}']["ipv6_default_gateway"] = "{{ value.ipv6_default_gateway }}"
472+ last = "{{ key }}"
473+ {% endfor %}
474+ {% endsmart_if %}
475+ return interface_table
476+}
477+
478+function populate_interfaces() {
479+ interfaces = document.getElementById("interfaces")
480+ ilen = interfaces.length
481+ {% for x in interface_names %}
482+ var new_option = new Option("{{ x }}","{{ x }}")
483+ interfaces.options[ilen] = new_option
484+ interfaces.selectedIndex = ilen
485+ ilen = ilen + 1
486+ {% endfor %}
487+ interfaces.selectedIndex = 0
488+ document.getElementById("static").onchange=intf_update_visibility;
489+ document.getElementById("bonding").onchange=intf_update_visibility;
490+}
491+{% endifequal %}
492+function on_form_submit()
493+{
494+{% ifequal what "system" %}
495+ // form submission handler
496+ save_intf(get_selected_interface())
497+ var listing = ""
498+ for (var iname in interface_table) {
499+ if (listing == "") {
500+ listing = iname
501+ } else {
502+ listing = iname + "," + listing
503+ }
504+ for (var ikey in interface_table[iname]) {
505+ var field_name = ikey + "-" + iname
506+ var current_value = interface_table[iname][ikey]
507+ var new_input=document.createElement('input')
508+ new_input.name=field_name
509+ new_input.value=current_value
510+ new_input.style.display='none'
511+ document.forms[0].appendChild(new_input)
512+ }
513+ }
514+ document.getElementById("interface_list").value = listing
515+ document.getElementById("name").disabled= false
516+{% endifequal %}
517+ // we have to select all items all multiselects
518+ // so they'll be submitted and saved correctly
519+{% for item in fields %}
520+ {% ifequal item.html_element "multiselect" %}
521+ var cur_multi = document.getElementById("{{ item.dname }}");
522+ if (cur_multi) {
523+ for (i=0; i <= cur_multi.options.length - 1; i++) {
524+ cur_multi.options[i].selected = true;
525+ }
526+ }
527+ {% endifequal %}
528+{% endfor %}
529+ document.forms[0].submit()
530+}
531+
532+function page_onload() {
533+{% ifequal what "system" %}
534+ populate_widgets();
535+ interface_table = build_interface_table();
536+ populate_interfaces();
537+ last_interface = get_selected_interface();
538+ load_intf();
539+{% endifequal %}
540+}
541+</script>
542+
543+<script type="text/javascript">
544+$(document).ready(function()
545+{
546+ //hide the all of the element with class msg_body
547+ $(".sectionbody").hide();
548+ $("#block_General").show();
549+ //toggle the componenet with class sectionbody
550+ $(".sectionheader").click(function()
551+ {
552+ $(this).next(".sectionbody").slideToggle(500);
553+ });
554+});
555+</script>
556+
557+<h1>{% ifequal editmode 'edit' %}Editing{% else %}Adding{% endifequal %} a {{ what|capfirst }}{%ifequal editmode 'edit' %}: {{ name }}{% endifequal %}</h1>
558+<hr />
559+<form method="post" action="/cobbler_web/{{ what }}/save">
560+ <input type="hidden" name="editmode" value="{{ editmode }}" />
561+ <input type="hidden" name="subobject" value="{{ subobject }}" />
562+ <ol>
563+ <li class="hidden"></li>
564+{% for item in fields %}
565+ {% ifchanged item.block_section %}
566+ </ol>
567+ <fieldset><legend class="sectionheader">&rArr; {{ item.block_section }}</legend>
568+ <ol class="sectionbody" id="block_{{ item.block_section }}">
569+ {% endifchanged %}
570+ <li id="{{ item.dname }}_row" class="editrow">
571+ <label for="{{ item.dname }}" id="{{ item.dname }}_caption">{{ item.caption }}</label>
572+ {% ifequal item.html_element "widget" %}
573+ <span id="widget_{{ item.dname }}"></span>
574+ {% else %}
575+ {% ifequal item.html_element "text" %}
576+ {% smart_if editmode == "edit" and item.dname == "name" %}
577+ {{ item.value }} (editing, value is read-only)
578+ <input type="hidden" name="{{ item.dname }}" id="{{ item.dname }}" value="{{ item.value }}" />
579+ {% else %}
580+ <input type="text" name="{{ item.dname }}" id="{{ item.dname }}" value="{{ item.value }}" />
581+ {% endsmart_if %}
582+ {% endifequal %}
583+ {% ifequal item.html_element "textarea" %}
584+ <textarea name="{{ item.dname }}" id="{{ item.dname }}">{{ item.value }}</textarea>
585+ {% endifequal %}
586+ {% ifequal item.html_element "radio" %}
587+ {% for choice in item.choices %}
588+ {% ifequal item.value choice %}
589+ <input type="radio" name="{{ item.dname }}" id="{{ item.dname }}" value="{{ choice }}" checked="checked">{{ choice }}&nbsp;
590+ {% else %}
591+ <input type="radio" name="{{ item.dname }}" id="{{ item.dname }}" value="{{ choice }}">{{ choice }}&nbsp;
592+ {% endifequal %}
593+ {% endfor %}
594+ {% endifequal %}
595+ {% ifequal item.html_element "checkbox" %}
596+ {% ifequal item.value 1 %}
597+ <input type="checkbox" name="{{ item.dname }}" id="{{ item.dname }}" value="{{ item.dname }}" checked="checked" />
598+ {% else %}
599+ <input type="checkbox" name="{{ item.dname }}" id="{{ item.dname }}" value="{{ item.dname }}" />
600+ {% endifequal %}
601+ {% endifequal %}
602+ {% ifequal item.html_element "select" %}
603+ <select id="{{ item.dname }}" name="{{ item.dname }}">
604+ {% for choice in item.choices %}
605+ {% ifequal item.value choice %}
606+ <option value="{{ choice }}" selected>{{ choice }}</A>
607+ {% else %}
608+ <option value="{{ choice }}">{{ choice }}</A>
609+ {% endifequal %}
610+ {% endfor %}
611+ </select>
612+ {% endifequal %}
613+ {% ifequal item.html_element "multiselect" %}
614+ <div class="multiselect">
615+ <div class="multileft">
616+ <label for="from_{{ item.dname }}">Available:</label>
617+ <select multiple id="from_{{ item.dname }}" name="from_{{ item.dname }}" class="edit">
618+ {% for choice in item.choices %}
619+ {% smart_if choice not in item.value_raw %}
620+ <option value="{{ choice }}">{{ choice }}</a>
621+ {% endsmart_if %}
622+ {% endfor %}
623+ </select>
624+ </div>
625+ <div class="multibuttons">
626+ <input class="button" type="button" value="&gt;&gt;" id="add_{{ item.dname }}" onclick="$('#from_{{ item.dname }} option:selected').remove().appendTo('#{{ item.dname }}');" />
627+ <input class="button" type="button" value="&lt;&lt;" id="remove_{{ item.dname }}" onclick="$('#{{ item.dname }} option:selected').remove().appendTo('#from_{{ item.dname }}');" />
628+ </div>
629+ <div class="multiright">
630+ <label for="{{ item.dname }}">Selected:</label>
631+ <select multiple id="{{ item.dname }}" name="{{ item.dname }}" class="edit">
632+ {% for choice in item.choices %}
633+ {% smart_if choice in item.value_raw %}
634+ <option value="{{ choice }}">{{ choice }}</a>
635+ {% endsmart_if %}
636+ {% endfor %}
637+ </select>
638+ </div>
639+ </div>
640+ {% endifequal %}
641+ {% smart_if editmode == "edit" and item.dname != "name" %}
642+ {% if item.tooltip %}
643+ <span class="context-tip">{{ item.tooltip }}</span>
644+ {% endif %}
645+ {% endsmart_if %}
646+ </li>
647+ {% endifequal %}
648+{% endfor %}
649+ </ol>
650+ </fieldset>
651+
652+{% ifequal what "system" %}
653+ <input type="hidden" id="interface_list" name="interface_list" value="1" />
654+ <input type="hidden" id="present" name="present" value="1" />
655+ <input type="hidden" id="original" name="original" value="1" />
656+{% endifequal %}
657+
658+ <div>
659+{% if editable %}
660+ <input class="button" type="submit" name="submit" onClick="on_form_submit();" value="Save" />
661+ <input class="button" type="reset" name="reset" value="Reset" />
662+{% else %}
663+ This user does not have permissions to edit this object.
664+{% endif %}
665+ </div>
666+
667+</form>
668+{% endblock content %}
669
670=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/generic_list.tmpl'
671--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/generic_list.tmpl 1970-01-01 00:00:00 +0000
672+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/generic_list.tmpl 2011-11-11 16:30:32 +0000
673@@ -0,0 +1,192 @@
674+{% extends "master.tmpl" %}
675+{% load site %}
676+{% block content %}
677+
678+<script type="text/javascript">
679+function items_check_all(){
680+ var checkall = document.getElementById("itemsall").checked
681+ var items = document.getElementsByName("items")
682+ for(i=0; i<items.length; ++i) {
683+ items[i].checked=checkall;
684+ items_check(items[i])
685+ }
686+}
687+
688+function items_check(obj) {
689+ obj.parentNode.parentNode.className=(obj.checked)? 'selected' : '';
690+}
691+
692+function items_checked_values() {
693+ var items = document.getElementsByName("items")
694+ var values = new Array();
695+ for(i=0; i<items.length; ++i) {
696+ if (items[i].checked) {
697+ values.push(items[i].value)
698+ }
699+ }
700+ s = values.join(" ")
701+ return s;
702+}
703+
704+function obj_rename(old) {
705+ var newname = window.prompt("Change {{ what }} name to?",old);
706+ if (newname != null) {
707+ window.location = "/cobbler_web/{{ what }}/rename/" + old + "/" + newname;
708+ }
709+}
710+function obj_copy(old) {
711+ var newname = window.prompt("Name for the new {{ what }}?",old);
712+ if (newname != null) {
713+ window.location = "/cobbler_web/{{ what }}/copy/" + old + "/" + newname;
714+ }
715+}
716+function obj_delete(old) {
717+ if (confirm("Delete {{ what }} (" + old + ") and all child objects?")) {
718+ window.location = "/cobbler_web/{{ what }}/delete/" + old;
719+ }
720+}
721+
722+function action(otype) {
723+ sel_action = document.getElementById("actions").value
724+ what = sel_action.split("|")[0]
725+ action = sel_action.split("|")[1]
726+ document.location = "/cobbler_web/" + what + "/" + action
727+}
728+
729+function action_multi(otype) {
730+ var values = items_checked_values()
731+ if (values == "") {
732+ return
733+ }
734+ document.getElementById("names").value = values
735+
736+ sel_batchaction = document.getElementById("batchactions").value
737+ action = sel_batchaction.split("|")[0]
738+ param = sel_batchaction.split("|")[1]
739+
740+ if (action == "profile") {
741+ param = window.prompt("New installation profile name for checked systems?","")
742+ if ((param == null) || (param == "")) {
743+ return
744+ }
745+ value = 1
746+ }
747+ else {
748+ value = null
749+ }
750+ if ((action == "power") && (param == "on")) {
751+ value = confirm("Confirm: Really power up all checked systems?")
752+ }
753+ if ((action == "power") && (param == "off")) {
754+ value = confirm("Confirm: Really power down all checked systems?")
755+ }
756+ if ((action == "power") && (param == "reboot")) {
757+ value = confirm("Confirm: Really reboot all checked systems?")
758+ }
759+ if ((action == "netboot") && (param == "enable")) {
760+ value = confirm("Confirm: Really flag all checked systems for PXE reinstallation?")
761+ }
762+ if ((action == "netboot") && (param == "disable")) {
763+ value = confirm("Confirm: Really disable all checked systems for PXE reinstallation?")
764+ }
765+ if ((action == "delete" ) && (param == "delete")) {
766+ value = confirm("Confirm: Really delete all checked " + otype + "?" )
767+ }
768+ if ((action == "reposync")) {
769+ value = "reposync"
770+ }
771+
772+ if (value) {
773+ document.myform.action = "/cobbler_web/" + otype + "/multi/" + action + "/" + param
774+ document.myform.submit()
775+ }
776+ else {
777+ alert("Operation aborted.")
778+ }
779+}
780+</script>
781+<h1>{{ what|title }}s</h1>
782+<hr />
783+<ul id="submenubar">
784+ <li>
785+ <a class="action" href="/cobbler_web/{{what}}/edit">Create new {{ what }}</a>
786+ {% ifequal what "profile" %}<a class="action" href="/cobbler_web/sub{{what}}/edit">Create new sub-{{ what }}</a>{% endifequal %}
787+ </li>
788+ <li>
789+ <select id="batchactions">
790+ <option value="" selected="selected">Batch Actions</option>
791+ {% for title,action,value in batchactions %}<option value="{{ action }}|{{ value }}">{{ title }}</option>{% endfor %}
792+ </select>
793+ <input class="button" type="button" value="go" onclick="javascript:action_multi('{{ what }}')" />
794+ </li>
795+ {% include "paginate.tmpl" %}
796+</ul>
797+
798+<form name="myform" method="post" action="/cobbler_web/{{ what }}/action">
799+ <table id="listitems" cellspacing="0">
800+ <thead>
801+ <tr>
802+ <th>
803+ <input type="checkbox" id="itemsall" onclick="javascript:items_check_all();" />
804+ </th>
805+{% for value in columns %}
806+ <th>
807+ <a href="/cobbler_web/{{ what }}/modifylist/sort/{{ value.0 }}">{{ value.0|title }}</a>
808+ {% ifequal value.1 "asc" %}
809+ &darr;
810+ {% endifequal %}
811+ {% ifequal value.1 "desc" %}
812+ &uarr;
813+ {% endifequal %}
814+ </th>
815+{% endfor %}
816+ <th>Actions</th>
817+ </tr>
818+ </thead>
819+ <tbody>
820+{% for item in items %}
821+ <tr class="{% cycle 'rowodd' 'roweven' %}">
822+ <td><input type="checkbox" name="items" value="{{ item.0.1 }}" onclick="javascript:items_check(this);" /></td>
823+ {% for value in item %}
824+ <td>
825+ {% ifequal value.0 "name" %}
826+ <a href="/cobbler_web/{{ what }}/edit/{{ value.1 }}">{{ value.1 }}</a>
827+ {% endifequal %}
828+ {% ifequal value.2 "editlink" %}
829+ {% ifnotequal value.1 "~" %}
830+ <a href="/cobbler_web/{{ value.0 }}/edit/{{ value.1 }}">{{ value.1 }}</a>
831+ {% endifnotequal %}
832+ {% endifequal %}
833+ {% ifequal value.2 "checkbox" %}
834+ {% ifequal value.1 1 %}
835+ <input type="checkbox" name="{{ item.name }}" id="{{ item.name }}" value="{{ item.name }}" disabled="disabled" checked="checked" />
836+ {% else %}
837+ <input type="checkbox" name="{{ item.name }}" id="{{ item.name }}" value="{{ item.name }}" disabled="disabled" />
838+ {% endifequal %}
839+ {% endifequal %}
840+ {% ifequal value.2 "text" %}
841+ {{ value.1 }}
842+ {% endifequal %}
843+ </td>
844+ {% endfor %}
845+ <td>
846+ <a class="action" href="/cobbler_web/{{ what }}/edit/{{ item.0.1 }}">Edit</a>
847+ <span class="action" onClick="javascript:obj_copy('{{ item.0.1 }}')">Copy</span>
848+ <span class="action" onClick="javascript:obj_rename('{{ item.0.1 }}')">Rename</span>
849+ <span class="action" onClick="javascript:obj_delete('{{ item.0.1 }}')">Delete</span>
850+ {% ifequal what "system" %}
851+ <span class="action" onClick="window.location='/cblr/svc/op/ks/system/{{ item.0.1 }}'">View kickstart</span>
852+ {% endifequal %}
853+ {% ifequal what "profile" %}
854+ <span class="action" onClick="window.location='/cblr/svc/op/ks/profile/{{ item.0.1 }}'">View kickstart</span>
855+ {% endifequal %}
856+ </td>
857+ </tr>
858+{% endfor %}
859+ </tbody>
860+ </table>
861+ <input type="hidden" name="names" id="names" value=""/>
862+</form>
863+{% include "filter.tmpl" %}
864+
865+{% endblock content %}
866
867=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/import.tmpl'
868--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/import.tmpl 1970-01-01 00:00:00 +0000
869+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/import.tmpl 2011-11-11 16:30:32 +0000
870@@ -0,0 +1,47 @@
871+{% extends 'master.tmpl' %}
872+{% block content %}
873+<h1>DVD Importer</h1>
874+<hr />
875+<form method="post" action="/cobbler_web/import/run">
876+ <div class="sectionbody">
877+ <ul>
878+ <li class="editrow">
879+ <label for="name" class="inputlabel">Prefix</label>
880+ <input type="text" size="128" name="name" id="name" />
881+ <span class="context-tip">Example: rhel5u3, fedora11 (do not include the arch name)</span>
882+ </li>
883+ <li class="editrow">
884+ <label for="arch" class="inputlabel">Arch</label>
885+ <select name="arch" id="arch">
886+ <option value="i386">i386</option>
887+ <option value="x86_64">x86_64</option>
888+ <option value="ia64">ia64</option>
889+ <option value="ppc">ppc</option>
890+ <option value="ppc64">ppc64</option>
891+ <option value="s390">s390</option>
892+ <option value="s390x">s390x</option>
893+ <option value="arm">arm</option>
894+ </select>
895+ <span class="context-tip">Architecture of the DVD you are importing</span>
896+ </li>
897+ <li class="editrow">
898+ <label for="breed" class="inputlabel">Breed</label>
899+ <select name="breed" id="breed">
900+ <option value="redhat">Red Hat based (includes Fedora, CentOS, Scientific Linux)</option>
901+ <option value="debian">Debian</option>
902+ <option value="ubuntu">Ubuntu</option>
903+ <option value="suse">SUSE</option>
904+ </select>
905+ <span class="context-tip">Type of OS you are importing. If yours is not listed here (ex: SUSE), you will have to add a distro manually. Other distro imports may be supported in the future. Non Red Hat based distros may require additional instructions, see the <a href="http://fedorahosted.org/cobbler">Wiki</a> for details.</span>
906+ </li>
907+ <li class="editrow">
908+ <label for="path" class="inputlabel">Path</label>
909+ <input type="text" size="128" name="path" id="path" />
910+ <span class="context-tip">Full path to mounted DVD contents only (ex: /mnt/cdrom). No CD ISOs!. Content will be copied by Cobbler to /var/www/cobbler/ks_mirror automatically and then cobbler will create distro and profile objects for each ISO imported. If you need more control about where files are sourced or end up, create new distro and profile object manually.</span>
911+ </li>
912+ <li class="editrow">
913+ <input class="button" type="submit" name="submit" value="Run" />
914+ </li>
915+ </ul>
916+</form>
917+{% endblock content %}
918
919=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/ksfile_edit.tmpl'
920--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/ksfile_edit.tmpl 1970-01-01 00:00:00 +0000
921+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/ksfile_edit.tmpl 2011-11-11 16:30:32 +0000
922@@ -0,0 +1,58 @@
923+{% extends 'master.tmpl' %}
924+{% block content %}
925+
926+{% if not editable %}
927+<blockquote>
928+ NOTE: You do not have permission to make changes to this
929+ kickstart template and can only read it. It is possible that
930+ other Cobbler users has secured permissions on Cobbler
931+ profiles/systems that depend on this template -- changing this
932+ template would ultimately affect those profile/system records which
933+ you do not have access to. Alternatively, you may not have access
934+ to edit *any* kickstart templates. Contact your Cobbler server administrator
935+ if you need to resolve this.
936+</blockquote>
937+<br />
938+{% else %}
939+<h1>{% ifequal editmode 'edit' %}Editing{% else %}Adding{% endifequal %} a Kickstart Template</h1>
940+<hr />
941+<form id="ksform" method="post" action="/cobbler_web/ksfile/save">
942+ <ol>
943+ <li>
944+ <label for="ksdata">{% if ksfile_name %}Editing: {{ ksfile_name }}{% else %}Filename:{% endif %}</label>
945+ {% ifnotequal editmode 'edit' %}
946+ <input type="text" name="ksfile_name" id="ksfile_name" />
947+ <span class="context-tip">Example: foo.ks (to be saved in /var/lib/cobbler/kickstarts/)</span>
948+ {% else %}
949+ <input type="hidden" name="ksfile_name" value="{{ ksfile_name }}" />
950+ {% endifnotequal %}
951+ </li>
952+ <li>
953+ <pre><textarea name="ksdata" id="ksdata">{{ ksdata }}</textarea></pre>
954+ </li>
955+ {% if deleteable %}
956+ <li>
957+ <label class="delete" for="delete1">Delete</label>
958+ <input type="checkbox" name="delete1" value="delete1" />
959+ <label class="delete" for="delete2">Really?</label>
960+ <input type="checkbox" name="delete2" value="delete2" />
961+ <span class="context-tip">Check both buttons and click save to delete this object</span>
962+ </li>
963+ {% else %}
964+ {% ifequal editmode "edit" %}
965+ <li>
966+ <span class="warn">NOTE: This kickstart template is currently in-use.</span>
967+ </li>
968+ {% endifequal %}
969+ {% endif %}
970+ {% if editable %}
971+ <li>
972+ <input type="hidden" name="editmode" value="{{ editmode }}" />
973+ <input class="button" type="submit" name="submit" value="Save" />
974+ <input class="button" type="reset" name="reset" value="Reset" />
975+ </li>
976+ {% endif %}
977+ </ol>
978+</form>
979+{% endif %}
980+{% endblock content %}
981
982=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/login.tmpl'
983--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/login.tmpl 1970-01-01 00:00:00 +0000
984+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/login.tmpl 2011-11-11 16:30:32 +0000
985@@ -0,0 +1,29 @@
986+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
987+<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
988+<head>
989+ <title>{% block title %}Cobbler Web Interface{% endblock %}</title>
990+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
991+ <link rel="stylesheet" type="text/css" media="all" href="/cobbler_webui_content/style.css" />
992+</head>
993+<body class="loginscreen">
994+ <div id="login">
995+ <img src="/cobbler_webui_content/logo-ubuntu.png" width="185" />
996+ <b><big>Orchestra</big></b><br/>(powered by <a href="https://fedorahosted.org/cobbler/">Cobbler</a>)<br/><br/>
997+{% if form.errors %}
998+ <p class="error">Sorry, that's not a valid username or password</p>
999+{% endif %}
1000+ <form action="/cobbler_web/do_login" method="post">
1001+ {% if next %}<input type="hidden" name="next" value="{{ next|escape }}" />{% endif %}
1002+ <div id="username">
1003+ <label for="username">Username: </label>
1004+ <input type="text" name="username" value="" id="username" autofocus>
1005+ </div>
1006+ <div id="password">
1007+ <label for="password">Password: </label>
1008+ <input type="password" name="password" value="" id="password">
1009+ </div>
1010+ <input class="button" type="submit" value="login" />
1011+ </form>
1012+ </div>
1013+</body>
1014+</html>
1015
1016=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/master.tmpl'
1017--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/master.tmpl 1970-01-01 00:00:00 +0000
1018+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/master.tmpl 2011-11-11 16:30:32 +0000
1019@@ -0,0 +1,66 @@
1020+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
1021+<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
1022+<head>
1023+ <title>{% block title %}Cobbler Web Interface{% endblock %}</title>
1024+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
1025+ <script src="/cobbler_webui_content/jquery-1.3.2.js" type="text/javascript"></script>
1026+ <script src="/cobbler_webui_content/jsGrowl.js" type="text/javascript"></script>
1027+ <script src="/cobbler_webui_content/jsGrowl_jquery.js" type="text/javascript"></script>
1028+ <script src="/cobbler_webui_content/cobbler.js" type="text/javascript"></script>
1029+ <link rel="stylesheet" type="text/css" media="all" href="/cobbler_webui_content/style.css" />
1030+ <link rel="stylesheet" type="text/css" media="all" href="/cobbler_webui_content/jsGrowl.css" />
1031+</head>
1032+<body onload="go_go_gadget()">
1033+<div id="container">
1034+ <div id='user'>
1035+ <input type="hidden" name="username" id="username" value="{{ username }}" />
1036+ Logged in: <b>{{ username }}</b> <a class="action" href="/cobbler_web/logout">Logout</a>
1037+ </div>
1038+ <div id="menubar">
1039+ <big><b>Orchestra</b></big><br/><i>powered by <a href="https://fedorahosted.org/cobbler/">Cobbler</a></i><br/><br/>
1040+ <h1>Configuration</h1>
1041+ <ul>
1042+ <li><a href="/cobbler_web/distro/list" class="edit">Distros</a></li>
1043+ <li><a href="/cobbler_web/profile/list" class="edit">Profiles</a></li>
1044+ <li><a href="/cobbler_web/system/list" class="edit">Systems</a></li>
1045+ <li><a href="/cobbler_web/repo/list" class="edit">Repos</a></li>
1046+ <li><a href="/cobbler_web/image/list" class="edit">Images</a></li>
1047+ <li><a href="/cobbler_web/ksfile/list" class="edit">Kickstart Templates</a></li>
1048+ <li><a href="/cobbler_web/snippet/list" class="edit">Snippets</a></li>
1049+ <li><a href="/cobbler_web/mgmtclass/list" class="edit">Management Classes</a></li>
1050+ </ul>
1051+ <h1>Resources</h1>
1052+ <ul>
1053+ <li><a href="/cobbler_web/package/list" class="edit">Packages</a></li>
1054+ <li><a href="/cobbler_web/file/list" class="edit">Files</a></li>
1055+ </ul>
1056+ <h1>Actions</h1>
1057+ <ul>
1058+ <li><a href="/cobbler_web/import/prompt">Import DVD</a></li>
1059+ <li><a href="/cobbler_web/sync">Sync</a> ☼</li>
1060+ <li><a href="/cobbler_web/reposync">Reposync</a> ☼ </li>
1061+ <li><a href="/cobbler_web/hardlink">Hardlink</a> ☼ </li>
1062+ <!-- <li><a href="/cobbler_web/replicate">Replicate</a> ☼ </li> -->
1063+ <li><a href="/cobbler_web/buildiso">Build ISO</a> ☼ </li>
1064+ </ul>
1065+ <h1>Cobbler</h1>
1066+ <ul>
1067+ <li><a href="/cobbler_web/settings" class="menu">Settings</a></li>
1068+ <li><a href="/cobbler_web/check" class="menu">Check</a></li>
1069+ <li><a href="/cobbler_web/events" class="menu">Events</a></li>
1070+ <li><a href="https://fedorahosted.org/cobbler/wiki/UserDocs">Online Documentation</a></li>
1071+ <li><a href="http://webchat.freenode.net?channels=#cobbler">Online Help Chat</a></li>
1072+ </ul>
1073+ </div>
1074+ <div id="content">
1075+{% block content %}
1076+ <h1 class="error">Template Failure</h1>
1077+{% endblock %}
1078+ </div>
1079+ <div id="jsGrowl"></div>
1080+</div>
1081+<div id="footer">
1082+ <a href="http://fedorahosted.org/cobbler">Cobbler {{ version }}</a>
1083+</div>
1084+</body>
1085+</html>
1086
1087=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/paginate.tmpl'
1088--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/paginate.tmpl 1970-01-01 00:00:00 +0000
1089+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/paginate.tmpl 2011-11-11 16:30:32 +0000
1090@@ -0,0 +1,22 @@
1091+{% if pageinfo %}
1092+<li class="paginate"><label for="limit">Items/page:</label>
1093+ <select name="limit" id="limit" onchange="javascript:location='/cobbler_web/{{ what }}/modifylist/limit/'+this.value">
1094+ {% for p in pageinfo.items_per_page_list %}
1095+ <option value="{{ p }}"{% ifequal pageinfo.items_per_page p %} selected="selected"{% endifequal %}>{{ p }}</option>
1096+ {% endfor %}
1097+ </select>
1098+ {% ifnotequal pageinfo.prev_page "~" %}
1099+ <a href="/cobbler_web/{{ what }}/modifylist/page/{{ pageinfo.prev_page }}"><span class="lpointers">&lArr;</span></a>
1100+ {% else %}
1101+ <span class="lpointers">&lArr;</span>
1102+ {% endifnotequal %}
1103+ <select name="page" id="page" onchange="javascript:location='/cobbler_web/{{ what }}/modifylist/page/'+this.value">
1104+ {% for p in pageinfo.pages %}<option value="{{ p }}"{% ifequal pageinfo.page p %} selected="selected"{% endifequal %}>Page {{ p }}</option>{% endfor %}
1105+ </select>
1106+ {% ifnotequal pageinfo.next_page "~" %}
1107+ <a href="/cobbler_web/{{ what }}/modifylist/page/{{ pageinfo.next_page }}"><span class="rpointers">&rArr;</span></a>
1108+ {% else %}
1109+ <span class="rpointers">&rArr;</span>
1110+ {% endifnotequal %}
1111+</li>
1112+{% endif %}
1113
1114=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/templates/snippet_edit.tmpl'
1115--- .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/snippet_edit.tmpl 1970-01-01 00:00:00 +0000
1116+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/templates/snippet_edit.tmpl 2011-11-11 16:30:32 +0000
1117@@ -0,0 +1,54 @@
1118+{% extends 'master.tmpl' %}
1119+{% block content %}
1120+
1121+{% if not editable %}
1122+<blockquote>
1123+NOTE: You do not have permission to make changes to this
1124+snippet and can only read it.
1125+
1126+Contact your Cobbler server administrator
1127+if you need to resolve this.
1128+</blockquote>
1129+<br />
1130+{% endif %}
1131+<h1>{% ifequal editmode 'edit' %}Editing{% else %}Adding{% endifequal %} a Snippet</h1>
1132+<hr />
1133+<form id="snippetform" method="post" action="/cobbler_web/snippet/save">
1134+ <ol>
1135+ <li>
1136+ <label for="snippetdata">{% if snippet_name %}Snippet: {{ snippet_name }}{% else %}Filename:{% endif %}</label>
1137+{% ifnotequal editmode 'edit' %}
1138+ <input type="text" name="snippet_name" id="snippet_name" />
1139+ <span class="context-tip">Example: foo.ks (to be saved in /var/lib/cobbler/snippets/)</span>
1140+{% else %}
1141+ <input type="hidden" name="snippet_name" value="{{ snippet_name }}" />
1142+{% endifnotequal %}
1143+ </li>
1144+ <li>
1145+ <pre><textarea name="snippetdata" id="snippetdata">{{ snippetdata }}</textarea></pre>
1146+ </li>
1147+{% if deleteable %}
1148+ <li>
1149+ <label class="delete" for="delete1">Delete</label>
1150+ <input type="checkbox" name="delete1" value="delete1" />
1151+ <label class="delete" for="delete2">Really?</label>
1152+ <input type="checkbox" name="delete2" value="delete2" />
1153+ <span class="context-tip">Check both buttons and click save to delete this object</span>
1154+ </li>
1155+{% else %}
1156+ {% ifequal editmode "edit" %}
1157+ <li>
1158+ <span class="warn">NOTE: This kickstart template is currently in-use.</span>
1159+ </li>
1160+ {% endifequal %}
1161+{% endif %}
1162+{% if editable %}
1163+ <li>
1164+ <input type="hidden" name="editmode" value="{{ editmode }}" />
1165+ <input class="button" type="submit" name="submit" value="Save" />
1166+ <input class="button" type="reset" name="reset" value="Reset" />
1167+ </li>
1168+{% endif %}
1169+ </ol>
1170+</form>
1171+{% endblock content %}
1172
1173=== added file '.pc/59_add_csrf_protection.patch/web/cobbler_web/views.py'
1174--- .pc/59_add_csrf_protection.patch/web/cobbler_web/views.py 1970-01-01 00:00:00 +0000
1175+++ .pc/59_add_csrf_protection.patch/web/cobbler_web/views.py 2011-11-11 16:30:32 +0000
1176@@ -0,0 +1,1162 @@
1177+from django.template.loader import get_template
1178+from django.template import Context
1179+from django.template import RequestContext
1180+from django.http import HttpResponse
1181+from django.http import HttpResponseRedirect
1182+from django.shortcuts import render_to_response
1183+
1184+import xmlrpclib
1185+import time
1186+import simplejson
1187+import string
1188+import distutils
1189+import exceptions
1190+import time
1191+
1192+import cobbler.item_distro as item_distro
1193+import cobbler.item_profile as item_profile
1194+import cobbler.item_system as item_system
1195+import cobbler.item_repo as item_repo
1196+import cobbler.item_image as item_image
1197+import cobbler.item_mgmtclass as item_mgmtclass
1198+import cobbler.item_package as item_package
1199+import cobbler.item_file as item_file
1200+import cobbler.field_info as field_info
1201+import cobbler.utils as utils
1202+
1203+url_cobbler_api = None
1204+remote = None
1205+username = None
1206+
1207+#==================================================================================
1208+
1209+def index(request):
1210+ """
1211+ This is the main greeting page for cobbler web.
1212+ """
1213+ if not test_user_authenticated(request): return login(request,next="/cobbler_web")
1214+
1215+ t = get_template('index.tmpl')
1216+ html = t.render(Context({
1217+ 'version' : remote.version(request.session['token']),
1218+ 'username': username,
1219+ }))
1220+ return HttpResponse(html)
1221+
1222+#========================================================================
1223+
1224+def task_created(request):
1225+ """
1226+ Let's the user know what to expect for event updates.
1227+ """
1228+ if not test_user_authenticated(request): return login(request)
1229+ t = get_template("task_created.tmpl")
1230+ html = t.render(Context({
1231+ 'version' : remote.version(request.session['token']),
1232+ 'username' : username
1233+ }))
1234+ return HttpResponse(html)
1235+
1236+#========================================================================
1237+
1238+def error_page(request,message):
1239+ """
1240+ This page is used to explain error messages to the user.
1241+ """
1242+ if not test_user_authenticated(request): return login(request)
1243+ # FIXME: test and make sure we use this rather than throwing lots of tracebacks for
1244+ # field errors
1245+ t = get_template('error_page.tmpl')
1246+ message = message.replace("<Fault 1: \"<class 'cobbler.cexceptions.CX'>:'","Remote exception: ")
1247+ message = message.replace("'\">","")
1248+ html = t.render(Context({
1249+ 'version' : remote.version(request.session['token']),
1250+ 'message' : message,
1251+ 'username': username
1252+ }))
1253+ return HttpResponse(html)
1254+
1255+#==================================================================================
1256+
1257+def get_fields(what, is_subobject, seed_item=None):
1258+
1259+ """
1260+ Helper function. Retrieves the field table from the cobbler objects
1261+ and formats it in a way to make it useful for Django templating.
1262+ The field structure indicates what fields to display and what the default
1263+ values are, etc.
1264+ """
1265+
1266+ if what == "distro":
1267+ field_data = item_distro.FIELDS
1268+ if what == "profile":
1269+ field_data = item_profile.FIELDS
1270+ if what == "system":
1271+ field_data = item_system.FIELDS
1272+ if what == "repo":
1273+ field_data = item_repo.FIELDS
1274+ if what == "image":
1275+ field_data = item_image.FIELDS
1276+ if what == "mgmtclass":
1277+ field_data = item_mgmtclass.FIELDS
1278+ if what == "package":
1279+ field_data = item_package.FIELDS
1280+ if what == "file":
1281+ field_data = item_file.FIELDS
1282+
1283+ settings = remote.get_settings()
1284+
1285+ fields = []
1286+ for row in field_data:
1287+
1288+ # if we are subprofile and see the field "distro", make it say parent
1289+ # with this really sneaky hack here
1290+ if is_subobject and row[0] == "distro":
1291+ row[0] = "parent"
1292+ row[3] = "Parent object"
1293+ row[5] = "Inherit settings from this profile"
1294+ row[6] = []
1295+
1296+ elem = {
1297+ "name" : row[0],
1298+ "dname" : row[0].replace("*",""),
1299+ "value" : "?",
1300+ "caption" : row[3],
1301+ "editable" : row[4],
1302+ "tooltip" : row[5],
1303+ "choices" : row[6],
1304+ "css_class" : "generic",
1305+ "html_element" : "generic",
1306+ }
1307+
1308+
1309+ if not elem["editable"]:
1310+ continue
1311+
1312+ if seed_item is not None:
1313+ if row[0].startswith("*"):
1314+ # system interfaces are loaded by javascript, not this
1315+ elem["value"] = ""
1316+ elem["name"] = row[0].replace("*","")
1317+ elif row[0].find("widget") == -1:
1318+ elem["value"] = seed_item[row[0]]
1319+ elif is_subobject:
1320+ elem["value"] = row[2]
1321+ else:
1322+ elem["value"] = row[1]
1323+
1324+ if elem["value"] is None:
1325+ elem["value"] = ""
1326+
1327+ # we'll process this for display but still need to present the original to some
1328+ # template logic
1329+ elem["value_raw"] = elem["value"]
1330+
1331+ if isinstance(elem["value"],basestring) and elem["value"].startswith("SETTINGS:"):
1332+ key = elem["value"].replace("SETTINGS:","",1)
1333+ elem["value"] = settings[key]
1334+
1335+ # flatten hashes of all types, they can only be edited as text
1336+ # as we have no HTML hash widget (yet)
1337+ if type(elem["value"]) == type({}):
1338+ if elem["name"] == "mgmt_parameters":
1339+ #Render dictionary as YAML for Management Parameters field
1340+ tokens = []
1341+ for (x,y) in elem["value"].items():
1342+ if y is not None:
1343+ tokens.append("%s: %s" % (x,y))
1344+ else:
1345+ tokens.append("%s: " % x)
1346+ elem["value"] = "{ %s }" % ", ".join(tokens)
1347+ else:
1348+ tokens = []
1349+ for (x,y) in elem["value"].items():
1350+ if y is not None:
1351+ tokens.append("%s=%s" % (x,y))
1352+ else:
1353+ tokens.append("%s" % x)
1354+ elem["value"] = " ".join(tokens)
1355+
1356+ name = row[0]
1357+ if name.find("_widget") != -1:
1358+ elem["html_element"] = "widget"
1359+ elif name in field_info.USES_SELECT:
1360+ elem["html_element"] = "select"
1361+ elif name in field_info.USES_MULTI_SELECT:
1362+ elem["html_element"] = "multiselect"
1363+ elif name in field_info.USES_RADIO:
1364+ elem["html_element"] = "radio"
1365+ elif name in field_info.USES_CHECKBOX:
1366+ elem["html_element"] = "checkbox"
1367+ elif name in field_info.USES_TEXTAREA:
1368+ elem["html_element"] = "textarea"
1369+ else:
1370+ elem["html_element"] = "text"
1371+
1372+ elem["block_section"] = field_info.BLOCK_MAPPINGS.get(name, "General")
1373+
1374+ # flatten lists for those that aren't using select boxes
1375+ if type(elem["value"]) == type([]):
1376+ if elem["html_element"] != "select":
1377+ elem["value"] = string.join(elem["value"], sep=" ")
1378+
1379+ # FIXME: need to handle interfaces special, they are prefixed with "*"
1380+
1381+ fields.append(elem)
1382+
1383+ return fields
1384+
1385+#==================================================================================
1386+
1387+def __tweak_field(fields,field_name,attribute,value):
1388+ """
1389+ Helper function to insert extra data into the field list.
1390+ """
1391+ # FIXME: eliminate this function.
1392+ for x in fields:
1393+ if x["name"] == field_name:
1394+ x[attribute] = value
1395+
1396+#==================================================================================
1397+
1398+
1399+def __format_columns(column_names,sort_field):
1400+ """
1401+ Format items retrieved from XMLRPC for rendering by the generic_edit template
1402+ """
1403+ dataset = []
1404+
1405+ # Default is sorting on name
1406+ if sort_field is not None:
1407+ sort_name = sort_field
1408+ else:
1409+ sort_name = "name"
1410+
1411+ if sort_name.startswith("!"):
1412+ sort_name = sort_name[1:]
1413+ sort_order = "desc"
1414+ else:
1415+ sort_order = "asc"
1416+
1417+ for fieldname in column_names:
1418+ fieldorder = "none"
1419+ if fieldname == sort_name:
1420+ fieldorder = sort_order
1421+ dataset.append([fieldname,fieldorder])
1422+ return dataset
1423+
1424+
1425+#==================================================================================
1426+
1427+def __format_items(items, column_names):
1428+ """
1429+ Format items retrieved from XMLRPC for rendering by the generic_edit template
1430+ """
1431+ dataset = []
1432+ for itemhash in items:
1433+ row = []
1434+ for fieldname in column_names:
1435+ if fieldname == "name":
1436+ html_element = "name"
1437+ elif fieldname in [ "system", "repo", "distro", "profile", "image", "mgmtclass", "package", "file" ]:
1438+ html_element = "editlink"
1439+ elif fieldname in field_info.USES_CHECKBOX:
1440+ html_element = "checkbox"
1441+ else:
1442+ html_element = "text"
1443+ row.append([fieldname,itemhash[fieldname],html_element])
1444+ dataset.append(row)
1445+ return dataset
1446+
1447+#==================================================================================
1448+
1449+def genlist(request, what, page=None):
1450+ """
1451+ Lists all object types, complete with links to actions
1452+ on those objects.
1453+ """
1454+ if not test_user_authenticated(request): return login(request)
1455+
1456+ # get details from the session
1457+ if page == None:
1458+ page = int(request.session.get("%s_page" % what, 1))
1459+ limit = int(request.session.get("%s_limit" % what, 50))
1460+ sort_field = request.session.get("%s_sort_field" % what, "name")
1461+ filters = simplejson.loads(request.session.get("%s_filters" % what, "{}"))
1462+ pageditems = remote.find_items_paged(what,utils.strip_none(filters),sort_field,page,limit)
1463+
1464+ # what columns to show for each page?
1465+ # we also setup the batch actions here since they're dependent
1466+ # on what we're looking at
1467+
1468+ # everythng gets batch delete
1469+ batchactions = [
1470+ ["Delete","delete","delete"],
1471+ ]
1472+
1473+ if what == "distro":
1474+ columns = [ "name" ]
1475+ if what == "profile":
1476+ columns = [ "name", "distro" ]
1477+ if what == "system":
1478+ # FIXME: also list network, once working
1479+ columns = [ "name", "profile", "netboot_enabled" ]
1480+ batchactions += [
1481+ ["Power on","power","on"],
1482+ ["Power off","power","off"],
1483+ ["Reboot","power","reboot"],
1484+ ["Change profile","profile",""],
1485+ ["Netboot enable","netboot","enable"],
1486+ ["Netboot disable","netboot","disable"],
1487+ ]
1488+ if what == "repo":
1489+ columns = [ "name", "mirror" ]
1490+ batchactions += [
1491+ ["Reposync","reposync","go"],
1492+ ]
1493+ if what == "image":
1494+ columns = [ "name", "file" ]
1495+ if what == "network":
1496+ columns = [ "name" ]
1497+ if what == "mgmtclass":
1498+ columns = [ "name" ]
1499+ if what == "package":
1500+ columns = [ "name", "installer" ]
1501+ if what == "file":
1502+ columns = [ "name" ]
1503+
1504+ # render the list
1505+ t = get_template('generic_list.tmpl')
1506+ html = t.render(RequestContext(request,{
1507+ 'what' : what,
1508+ 'columns' : __format_columns(columns,sort_field),
1509+ 'items' : __format_items(pageditems["items"],columns),
1510+ 'pageinfo' : pageditems["pageinfo"],
1511+ 'filters' : filters,
1512+ 'version' : remote.version(request.session['token']),
1513+ 'username' : username,
1514+ 'limit' : limit,
1515+ 'batchactions' : batchactions,
1516+ }))
1517+ return HttpResponse(html)
1518+
1519+
1520+def modify_list(request, what, pref, value=None):
1521+ """
1522+ This function is used in the generic list view
1523+ to modify the page/column sort/number of items
1524+ shown per page, and also modify the filters.
1525+
1526+ This function modifies the session object to
1527+ store these preferences persistently.
1528+ """
1529+ if not test_user_authenticated(request): return login(request)
1530+
1531+
1532+ # what preference are we tweaking?
1533+
1534+ if pref == "sort":
1535+
1536+ # FIXME: this isn't exposed in the UI.
1537+
1538+ # sorting list on columns
1539+ old_sort = request.session.get("%s_sort_field" % what,"name")
1540+ if old_sort.startswith("!"):
1541+ old_sort = old_sort[1:]
1542+ old_revsort = True
1543+ else:
1544+ old_revsort = False
1545+ # User clicked on the column already sorted on,
1546+ # so reverse the sorting list
1547+ if old_sort == value and not old_revsort:
1548+ value = "!" + value
1549+ request.session["%s_sort_field" % what] = value
1550+ request.session["%s_page" % what] = 1
1551+
1552+ elif pref == "limit":
1553+ # number of items to show per page
1554+ request.session["%s_limit" % what] = int(value)
1555+ request.session["%s_page" % what] = 1
1556+
1557+ elif pref == "page":
1558+ # what page are we currently on
1559+ request.session["%s_page" % what] = int(value)
1560+
1561+ elif pref in ("addfilter","removefilter"):
1562+ # filters limit what we show in the lists
1563+ # they are stored in json format for marshalling
1564+ filters = simplejson.loads(request.session.get("%s_filters" % what, "{}"))
1565+ if pref == "addfilter":
1566+ (field_name, field_value) = value.split(":", 1)
1567+ # add this filter
1568+ filters[field_name] = field_value
1569+ else:
1570+ # remove this filter, if it exists
1571+ if filters.has_key(value):
1572+ del filters[value]
1573+ # save session variable
1574+ request.session["%s_filters" % what] = simplejson.dumps(filters)
1575+ # since we changed what is viewed, reset the page
1576+ request.session["%s_page" % what] = 1
1577+
1578+ else:
1579+ return error_page(request, "Invalid preference change request")
1580+
1581+ # redirect to the list page
1582+ return HttpResponseRedirect("/cobbler_web/%s/list" % what)
1583+
1584+# ======================================================================
1585+
1586+def generic_rename(request, what, obj_name=None, obj_newname=None):
1587+
1588+ """
1589+ Renames an object.
1590+ """
1591+ if not test_user_authenticated(request): return login(request)
1592+
1593+ if obj_name == None:
1594+ return error_page(request,"You must specify a %s to rename" % what)
1595+ if not remote.has_item(what,obj_name):
1596+ return error_page(request,"Unknown %s specified" % what)
1597+ elif not remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name):
1598+ return error_page(request,"You do not have permission to rename this %s" % what)
1599+ else:
1600+ obj_id = remote.get_item_handle(what, obj_name, request.session['token'])
1601+ remote.rename_item(what, obj_id, obj_newname, request.session['token'])
1602+ return HttpResponseRedirect("/cobbler_web/%s/list" % what)
1603+
1604+# ======================================================================
1605+
1606+def generic_copy(request, what, obj_name=None, obj_newname=None):
1607+ """
1608+ Copies an object.
1609+ """
1610+ if not test_user_authenticated(request): return login(request)
1611+ # FIXME: shares all but one line with rename, merge it.
1612+ if obj_name == None:
1613+ return error_page(request,"You must specify a %s to rename" % what)
1614+ if not remote.has_item(what,obj_name):
1615+ return error_page(request,"Unknown %s specified" % what)
1616+ elif not remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name):
1617+ return error_page(request,"You do not have permission to copy this %s" % what)
1618+ else:
1619+ obj_id = remote.get_item_handle(what, obj_name, request.session['token'])
1620+ remote.copy_item(what, obj_id, obj_newname, request.session['token'])
1621+ return HttpResponseRedirect("/cobbler_web/%s/list" % what)
1622+
1623+# ======================================================================
1624+
1625+def generic_delete(request, what, obj_name=None):
1626+ """
1627+ Deletes an object.
1628+ """
1629+ if not test_user_authenticated(request): return login(request)
1630+ # FIXME: consolidate code with above functions.
1631+ if obj_name == None:
1632+ return error_page(request,"You must specify a %s to delete" % what)
1633+ if not remote.has_item(what,obj_name):
1634+ return error_page(request,"Unknown %s specified" % what)
1635+ elif not remote.check_access_no_fail(request.session['token'], "remove_%s" % what, obj_name):
1636+ return error_page(request,"You do not have permission to delete this %s" % what)
1637+ else:
1638+ remote.remove_item(what, obj_name, request.session['token'])
1639+ return HttpResponseRedirect("/cobbler_web/%s/list" % what)
1640+
1641+
1642+# ======================================================================
1643+
1644+def generic_domulti(request, what, multi_mode=None, multi_arg=None):
1645+
1646+ """
1647+ Process operations like profile reassignment, netboot toggling, and deletion
1648+ which occur on all items that are checked on the list page.
1649+ """
1650+ if not test_user_authenticated(request): return login(request)
1651+
1652+ # FIXME: cleanup
1653+ # FIXME: COMMENTS!!!11111???
1654+
1655+ names = request.POST.get('names', '').strip().split()
1656+ if names == "":
1657+ return error_page(request, "Need to select some systems first")
1658+
1659+ if multi_mode == "delete":
1660+ for obj_name in names:
1661+ remote.remove_item(what,obj_name, request.session['token'])
1662+ elif what == "system" and multi_mode == "netboot":
1663+ netboot_enabled = multi_arg # values: enable or disable
1664+ if netboot_enabled is None:
1665+ return error_page(request,"Cannot modify systems without specifying netboot_enabled")
1666+ if netboot_enabled == "enable":
1667+ netboot_enabled = True
1668+ elif netboot_enabled == "disable":
1669+ netboot_enabled = False
1670+ else:
1671+ return error_page(request,"Invalid netboot option, expect enable or disable")
1672+ for obj_name in names:
1673+ obj_id = remote.get_system_handle(obj_name, request.session['token'])
1674+ remote.modify_system(obj_id, "netboot_enabled", netboot_enabled, request.session['token'])
1675+ remote.save_system(obj_id, request.session['token'], "edit")
1676+ elif what == "system" and multi_mode == "profile":
1677+ profile = multi_arg
1678+ if profile is None:
1679+ return error_page(request,"Cannot modify systems without specifying profile")
1680+ for obj_name in names:
1681+ obj_id = remote.get_system_handle(obj_name, request.session['token'])
1682+ remote.modify_system(obj_id, "profile", profile, request.session['token'])
1683+ remote.save_system(obj_id, request.session['token'], "edit")
1684+ elif what == "system" and multi_mode == "power":
1685+ # FIXME: power should not loop, but send the list of all systems
1686+ # in one set.
1687+ power = multi_arg
1688+ if power is None:
1689+ return error_page(request,"Cannot modify systems without specifying power option")
1690+ options = { "systems" : names, "power" : power }
1691+ remote.background_power_system(options, request.session['token'])
1692+ elif what == "profile" and multi_mode == "reposync":
1693+ options = { "repos" : names, "tries" : 3 }
1694+ remote.background_reposync(options,request.session['token'])
1695+ else:
1696+ return error_page(request,"Unknown batch operation on %ss: %s" % (what,str(multi_mode)))
1697+
1698+ # FIXME: "operation complete" would make a lot more sense here than a redirect
1699+ return HttpResponseRedirect("/cobbler_web/%s/list"%what)
1700+
1701+# ======================================================================
1702+
1703+def import_prompt(request):
1704+ if not test_user_authenticated(request): return login(request)
1705+ t = get_template('import.tmpl')
1706+ html = t.render(Context({
1707+ 'version' : remote.version(request.session['token']),
1708+ 'username' : username,
1709+ }))
1710+ return HttpResponse(html)
1711+
1712+# ======================================================================
1713+
1714+def check(request):
1715+ """
1716+ Shows a page with the results of 'cobbler check'
1717+ """
1718+ if not test_user_authenticated(request): return login(request)
1719+ results = remote.check(request.session['token'])
1720+ t = get_template('check.tmpl')
1721+ html = t.render(Context({
1722+ 'version': remote.version(request.session['token']),
1723+ 'username' : username,
1724+ 'results' : results
1725+ }))
1726+ return HttpResponse(html)
1727+
1728+# ======================================================================
1729+
1730+def buildiso(request):
1731+ if not test_user_authenticated(request): return login(request)
1732+ remote.background_buildiso({},request.session['token'])
1733+ return HttpResponseRedirect('/cobbler_web/task_created')
1734+
1735+# ======================================================================
1736+
1737+def import_run(request):
1738+ if not test_user_authenticated(request): return login(request)
1739+ options = {
1740+ "name" : request.POST.get("name",""),
1741+ "path" : request.POST.get("path",""),
1742+ "breed" : request.POST.get("breed",""),
1743+ "arch" : request.POST.get("arch","")
1744+ }
1745+ remote.background_import(options,request.session['token'])
1746+ return HttpResponseRedirect('/cobbler_web/task_created')
1747+
1748+# ======================================================================
1749+
1750+def ksfile_list(request, page=None):
1751+ """
1752+ List all kickstart templates and link to their edit pages.
1753+ """
1754+ if not test_user_authenticated(request): return login(request)
1755+ ksfiles = remote.get_kickstart_templates(request.session['token'])
1756+
1757+ ksfile_list = []
1758+ for ksfile in ksfiles:
1759+ if ksfile.startswith("/var/lib/cobbler/kickstarts") or ksfile.startswith("/etc/cobbler"):
1760+ ksfile_list.append((ksfile,ksfile.replace('/var/lib/cobbler/kickstarts/',''),'editable'))
1761+ elif ksfile.startswith("http://") or ksfile.startswith("ftp://"):
1762+ ksfile_list.append((ksfile,ksfile,'','viewable'))
1763+ else:
1764+ ksfile_list.append((ksfile,ksfile,None))
1765+
1766+ t = get_template('ksfile_list.tmpl')
1767+ html = t.render(Context({
1768+ 'what':'ksfile',
1769+ 'ksfiles': ksfile_list,
1770+ 'version': remote.version(request.session['token']),
1771+ 'username': username,
1772+ 'item_count': len(ksfile_list[0]),
1773+ }))
1774+ return HttpResponse(html)
1775+
1776+# ======================================================================
1777+
1778+
1779+def ksfile_edit(request, ksfile_name=None, editmode='edit'):
1780+ """
1781+ This is the page where a kickstart file is edited.
1782+ """
1783+ if not test_user_authenticated(request): return login(request)
1784+ if editmode == 'edit':
1785+ editable = False
1786+ else:
1787+ editable = True
1788+ deleteable = False
1789+ ksdata = ""
1790+ if not ksfile_name is None:
1791+ editable = remote.check_access_no_fail(request.session['token'], "modify_kickstart", ksfile_name)
1792+ deleteable = not remote.is_kickstart_in_use(ksfile_name, request.session['token'])
1793+ ksdata = remote.read_or_write_kickstart_template(ksfile_name, True, "", request.session['token'])
1794+
1795+ t = get_template('ksfile_edit.tmpl')
1796+ html = t.render(Context({
1797+ 'ksfile_name' : ksfile_name,
1798+ 'deleteable' : deleteable,
1799+ 'ksdata' : ksdata,
1800+ 'editable' : editable,
1801+ 'editmode' : editmode,
1802+ 'version' : remote.version(request.session['token']),
1803+ 'username' : username
1804+ }))
1805+ return HttpResponse(html)
1806+
1807+# ======================================================================
1808+
1809+def ksfile_save(request):
1810+ """
1811+ This page processes and saves edits to a kickstart file.
1812+ """
1813+ if not test_user_authenticated(request): return login(request)
1814+ # FIXME: error checking
1815+
1816+ editmode = request.POST.get('editmode', 'edit')
1817+ ksfile_name = request.POST.get('ksfile_name', None)
1818+ ksdata = request.POST.get('ksdata', "").replace('\r\n','\n')
1819+
1820+ if ksfile_name == None:
1821+ return HttpResponse("NO KSFILE NAME SPECIFIED")
1822+ if editmode != 'edit':
1823+ ksfile_name = "/var/lib/cobbler/kickstarts/" + ksfile_name
1824+
1825+ delete1 = request.POST.get('delete1', None)
1826+ delete2 = request.POST.get('delete2', None)
1827+
1828+ if delete1 and delete2:
1829+ remote.read_or_write_kickstart_template(ksfile_name, False, -1, request.session['token'])
1830+ return HttpResponseRedirect('/cobbler_web/ksfile/list')
1831+ else:
1832+ remote.read_or_write_kickstart_template(ksfile_name,False,ksdata,request.session['token'])
1833+ return HttpResponseRedirect('/cobbler_web/ksfile/edit/file:%s' % ksfile_name)
1834+
1835+# ======================================================================
1836+
1837+def snippet_list(request, page=None):
1838+ """
1839+ This page lists all available snippets and has links to edit them.
1840+ """
1841+ if not test_user_authenticated(request): return login(request)
1842+ snippets = remote.get_snippets(request.session['token'])
1843+
1844+ snippet_list = []
1845+ for snippet in snippets:
1846+ if snippet.startswith("/var/lib/cobbler/snippets"):
1847+ snippet_list.append((snippet,snippet.replace("/var/lib/cobbler/snippets/",""),'editable'))
1848+ else:
1849+ snippet_list.append((snippet,snippet,None))
1850+
1851+ t = get_template('snippet_list.tmpl')
1852+ html = t.render(Context({
1853+ 'what' : 'snippet',
1854+ 'snippets' : snippet_list,
1855+ 'version' : remote.version(request.session['token']),
1856+ 'username' : username
1857+ }))
1858+ return HttpResponse(html)
1859+
1860+# ======================================================================
1861+
1862+def snippet_edit(request, snippet_name=None, editmode='edit'):
1863+ """
1864+ This page edits a specific snippet.
1865+ """
1866+ if not test_user_authenticated(request): return login(request)
1867+ if editmode == 'edit':
1868+ editable = False
1869+ else:
1870+ editable = True
1871+ deleteable = False
1872+ snippetdata = ""
1873+ if not snippet_name is None:
1874+ editable = remote.check_access_no_fail(request.session['token'], "modify_snippet", snippet_name)
1875+ deleteable = True
1876+ snippetdata = remote.read_or_write_snippet(snippet_name, True, "", request.session['token'])
1877+
1878+ t = get_template('snippet_edit.tmpl')
1879+ html = t.render(Context({
1880+ 'snippet_name' : snippet_name,
1881+ 'deleteable' : deleteable,
1882+ 'snippetdata' : snippetdata,
1883+ 'editable' : editable,
1884+ 'editmode' : editmode,
1885+ 'version' : remote.version(request.session['token']),
1886+ 'username' : username
1887+ }))
1888+ return HttpResponse(html)
1889+
1890+# ======================================================================
1891+
1892+def snippet_save(request):
1893+ """
1894+ This snippet saves a snippet once edited.
1895+ """
1896+ if not test_user_authenticated(request): return login(request)
1897+ # FIXME: error checking
1898+
1899+ editmode = request.POST.get('editmode', 'edit')
1900+ snippet_name = request.POST.get('snippet_name', None)
1901+ snippetdata = request.POST.get('snippetdata', "").replace('\r\n','\n')
1902+
1903+ if snippet_name == None:
1904+ return HttpResponse("NO SNIPPET NAME SPECIFIED")
1905+ if editmode != 'edit':
1906+ snippet_name = "/var/lib/cobbler/snippets/" + snippet_name
1907+
1908+ delete1 = request.POST.get('delete1', None)
1909+ delete2 = request.POST.get('delete2', None)
1910+
1911+ if delete1 and delete2:
1912+ remote.read_or_write_snippet(snippet_name, False, -1, request.session['token'])
1913+ return HttpResponseRedirect('/cobbler_web/snippet/list')
1914+ else:
1915+ remote.read_or_write_snippet(snippet_name,False,snippetdata,request.session['token'])
1916+ return HttpResponseRedirect('/cobbler_web/snippet/edit/file:%s' % snippet_name)
1917+
1918+# ======================================================================
1919+
1920+def settings(request):
1921+ """
1922+ This page presents a list of all the settings to the user. They are not editable.
1923+ """
1924+ if not test_user_authenticated(request): return login(request)
1925+ settings = remote.get_settings()
1926+ skeys = settings.keys()
1927+ skeys.sort()
1928+
1929+ results = []
1930+ for k in skeys:
1931+ results.append([k,settings[k]])
1932+
1933+ t = get_template('settings.tmpl')
1934+ html = t.render(Context({
1935+ 'settings' : results,
1936+ 'version' : remote.version(request.session['token']),
1937+ 'username' : username,
1938+ }))
1939+ return HttpResponse(html)
1940+
1941+# ======================================================================
1942+
1943+def events(request):
1944+ """
1945+ This page presents a list of all the events and links to the event log viewer.
1946+ """
1947+ if not test_user_authenticated(request): return login(request)
1948+ events = remote.get_events()
1949+
1950+ events2 = []
1951+ for id in events.keys():
1952+ (ttime, name, state, read_by) = events[id]
1953+ events2.append([id,time.asctime(time.gmtime(ttime)),name,state])
1954+
1955+ def sorter(a,b):
1956+ return cmp(a[2],b[2])
1957+ events2.sort(sorter)
1958+
1959+ t = get_template('events.tmpl')
1960+ html = t.render(Context({
1961+ 'results' : events2,
1962+ 'version' : remote.version(request.session['token']),
1963+ 'username' : username
1964+ }))
1965+ return HttpResponse(html)
1966+
1967+# ======================================================================
1968+
1969+def eventlog(request, event=0):
1970+ """
1971+ Shows the log for a given event.
1972+ """
1973+ if not test_user_authenticated(request): return login(request)
1974+ event_info = remote.get_events()
1975+ if not event_info.has_key(event):
1976+ return HttpResponse("event not found")
1977+
1978+ data = event_info[event]
1979+ eventname = data[0]
1980+ eventtime = data[1]
1981+ eventstate = data[2]
1982+ eventlog = remote.get_event_log(event)
1983+
1984+ t = get_template('eventlog.tmpl')
1985+ vars = {
1986+ 'eventlog' : eventlog,
1987+ 'eventname' : eventname,
1988+ 'eventstate' : eventstate,
1989+ 'eventid' : event,
1990+ 'eventtime' : eventtime,
1991+ 'version' : remote.version(request.session['token']),
1992+ 'username' : username
1993+ }
1994+ html = t.render(Context(vars))
1995+ return HttpResponse(html)
1996+
1997+# ======================================================================
1998+
1999+def random_mac(request, virttype="xenpv"):
2000+ """
2001+ Used in an ajax call to fill in a field with a mac address.
2002+ """
2003+ # FIXME: not exposed in UI currently
2004+ if not test_user_authenticated(request): return login(request)
2005+ random_mac = remote.get_random_mac(virttype, request.session['token'])
2006+ return HttpResponse(random_mac)
2007+
2008+# ======================================================================
2009+
2010+def sync(request):
2011+ """
2012+ Runs 'cobbler sync' from the API when the user presses the sync button.
2013+ """
2014+ if not test_user_authenticated(request): return login(request)
2015+ remote.background_sync({"verbose":"True"},request.session['token'])
2016+ return HttpResponseRedirect("/cobbler_web/task_created")
2017+
2018+# ======================================================================
2019+
2020+def reposync(request):
2021+ """
2022+ Syncs all repos that are configured to be synced.
2023+ """
2024+ if not test_user_authenticated(request): return login(request)
2025+ remote.background_reposync({ "names":"", "tries" : 3},request.session['token'])
2026+ return HttpResponseRedirect("/cobbler_web/task_created")
2027+
2028+# ======================================================================
2029+
2030+def hardlink(request):
2031+ """
2032+ Hardlinks files between repos and install trees to save space.
2033+ """
2034+ if not test_user_authenticated(request): return login(request)
2035+ remote.background_hardlink({},request.session['token'])
2036+ return HttpResponseRedirect("/cobbler_web/task_created")
2037+
2038+# ======================================================================
2039+
2040+def replicate(request):
2041+ """
2042+ Replicate configuration from the central cobbler server, configured
2043+ in /etc/cobbler/settings (note: this is uni-directional!)
2044+
2045+ FIXME: this is disabled because we really need a web page to provide options for
2046+ this command.
2047+
2048+ """
2049+ #settings = remote.get_settings()
2050+ #options = settings # just load settings from file until we decide to ask user (later?)
2051+ #remote.background_replicate(options, request.session['token'])
2052+ if not test_user_authenticated(request): return login(request)
2053+ return HttpResponseRedirect("/cobbler_web/task_created")
2054+
2055+# ======================================================================
2056+
2057+def __names_from_dicts(loh,optional=True):
2058+ """
2059+ Tiny helper function.
2060+ Get the names out of an array of hashes that the remote interface returns.
2061+ """
2062+ results = []
2063+ if optional:
2064+ results.append("<<None>>")
2065+ for x in loh:
2066+ results.append(x["name"])
2067+ results.sort()
2068+ return results
2069+
2070+# ======================================================================
2071+
2072+def generic_edit(request, what=None, obj_name=None, editmode="new"):
2073+
2074+ """
2075+ Presents an editor page for any type of object.
2076+ While this is generally standardized, systems are a little bit special.
2077+ """
2078+ if not test_user_authenticated(request): return login(request)
2079+
2080+ obj = None
2081+
2082+ settings = remote.get_settings()
2083+
2084+ child = False
2085+ if what == "subprofile":
2086+ what = "profile"
2087+ child = True
2088+
2089+
2090+ if not obj_name is None:
2091+ editable = remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name)
2092+ obj = remote.get_item(what, obj_name, True)
2093+ #
2094+ # if obj.has_key('ctime'):
2095+ # obj['ctime'] = time.ctime(obj['ctime'])
2096+ # if obj.has_key('mtime'):
2097+ # obj['mtime'] = time.ctime(obj['mtime'])
2098+
2099+ else:
2100+ editable = remote.check_access_no_fail(request.session['token'], "new_%s" % what, None)
2101+ obj = None
2102+
2103+
2104+ interfaces = {}
2105+ if what == "system":
2106+ if obj:
2107+ interfaces = obj.get("interfaces",{})
2108+ else:
2109+ interfaces = {}
2110+
2111+ fields = get_fields(what, child, obj)
2112+
2113+ # populate some select boxes
2114+ # FIXME: we really want to just populate with the names, right?
2115+ if what == "profile":
2116+ if (obj and obj["parent"] not in (None,"")) or child:
2117+ __tweak_field(fields, "parent", "choices", __names_from_dicts(remote.get_profiles()))
2118+ else:
2119+ __tweak_field(fields, "distro", "choices", __names_from_dicts(remote.get_distros()))
2120+ __tweak_field(fields, "repos", "choices", __names_from_dicts(remote.get_repos()))
2121+ elif what == "system":
2122+ __tweak_field(fields, "profile", "choices", __names_from_dicts(remote.get_profiles()))
2123+ __tweak_field(fields, "image", "choices", __names_from_dicts(remote.get_images(),optional=True))
2124+ elif what == "mgmtclass":
2125+ __tweak_field(fields, "packages", "choices", __names_from_dicts(remote.get_packages()))
2126+ __tweak_field(fields, "files", "choices", __names_from_dicts(remote.get_files()))
2127+
2128+ if what in ("distro","profile","system"):
2129+ __tweak_field(fields, "mgmt_classes", "choices", __names_from_dicts(remote.get_mgmtclasses(),optional=False))
2130+
2131+ # save the fields in the session for comparison later
2132+ request.session['%s_%s' % (what,obj_name)] = fields
2133+
2134+ t = get_template('generic_edit.tmpl')
2135+ inames = interfaces.keys()
2136+ inames.sort()
2137+ html = t.render(Context({
2138+ 'what' : what,
2139+ 'fields' : fields,
2140+ 'subobject' : child,
2141+ 'editmode' : editmode,
2142+ 'editable' : editable,
2143+ 'interfaces' : interfaces,
2144+ 'interface_names' : inames,
2145+ 'interface_length': len(inames),
2146+ 'version' : remote.version(request.session['token']),
2147+ 'username' : username,
2148+ 'name' : obj_name
2149+ }))
2150+
2151+ return HttpResponse(html)
2152+
2153+# ======================================================================
2154+
2155+def generic_save(request,what):
2156+
2157+ """
2158+ Saves an object back using the cobbler API after clearing any 'generic_edit' page.
2159+ """
2160+ if not test_user_authenticated(request): return login(request)
2161+
2162+ # load request fields and see if they are valid
2163+ editmode = request.POST.get('editmode', 'edit')
2164+ obj_name = request.POST.get('name', "")
2165+ subobject = request.POST.get('subobject', "False")
2166+
2167+ if subobject == "False":
2168+ subobject = False
2169+ else:
2170+ subobject = True
2171+
2172+ if obj_name == "":
2173+ return error_page(request,"Required field name is missing")
2174+
2175+ prev_fields = []
2176+ if request.session.has_key("%s_%s" % (what,obj_name)):
2177+ prev_fields = request.session["%s_%s" % (what,obj_name)]
2178+
2179+ # grab the remote object handle
2180+ # for edits, fail in the object cannot be found to be edited
2181+ # for new objects, fail if the object already exists
2182+ if editmode == "edit":
2183+ if not remote.has_item(what, obj_name):
2184+ return error_page(request,"Failure trying to access item %s, it may have been deleted." % (obj_name))
2185+ obj_id = remote.get_item_handle( what, obj_name, request.session['token'] )
2186+ else:
2187+ if remote.has_item(what, obj_name):
2188+ return error_page(request,"Could not create a new item %s, it already exists." % (obj_name))
2189+ obj_id = remote.new_item( what, request.session['token'] )
2190+
2191+ # walk through our fields list saving things we know how to save
2192+ fields = get_fields(what, subobject)
2193+
2194+ for field in fields:
2195+
2196+ if field['name'] == 'name' and editmode == 'edit':
2197+ # do not attempt renames here
2198+ continue
2199+ elif field['name'].startswith("*"):
2200+ # interface fields will be handled below
2201+ continue
2202+ else:
2203+ # check and see if the value exists in the fields stored in the session
2204+ prev_value = None
2205+ for prev_field in prev_fields:
2206+ if prev_field['name'] == field['name']:
2207+ prev_value = prev_field['value']
2208+ break
2209+
2210+ value = request.POST.get(field['name'],None)
2211+ # Checkboxes return the value of the field if checked, otherwise None
2212+ # convert to True/False
2213+ if field["html_element"] == "checkbox":
2214+ if value==field['name']:
2215+ value=True
2216+ else:
2217+ value=False
2218+
2219+ # Multiselect fields are handled differently
2220+ if field["html_element"] == "multiselect":
2221+ values=request.POST.getlist(field['name'])
2222+ value=[]
2223+ if '<<inherit>>' in values:
2224+ value='<<inherit>>'
2225+ else:
2226+ for single_value in values:
2227+ if single_value != "<<None>>":
2228+ value.insert(0,single_value)
2229+
2230+ if value != None:
2231+ if value == "<<None>>":
2232+ value = ""
2233+ if value is not None and (not subobject or field['name'] != 'distro') and value != prev_value:
2234+ try:
2235+ remote.modify_item(what,obj_id,field['name'],value,request.session['token'])
2236+ except Exception, e:
2237+ return error_page(request, str(e))
2238+
2239+ # special handling for system interface fields
2240+ # which are the only objects in cobbler that will ever work this way
2241+ if what == "system":
2242+ interface_field_list = []
2243+ for field in fields:
2244+ if field['name'].startswith("*"):
2245+ field = field['name'].replace("*","")
2246+ interface_field_list.append(field)
2247+ interfaces = request.POST.get('interface_list', "").split(",")
2248+ for interface in interfaces:
2249+ if interface == "":
2250+ continue
2251+ ifdata = {}
2252+ for item in interface_field_list:
2253+ ifdata["%s-%s" % (item,interface)] = request.POST.get("%s-%s" % (item,interface), "")
2254+ ifdata=utils.strip_none(ifdata)
2255+ # FIXME: I think this button is missing.
2256+ present = request.POST.get("present-%s" % interface, "")
2257+ original = request.POST.get("original-%s" % interface, "")
2258+ try:
2259+ if present == "0" and original == "1":
2260+ remote.modify_system(obj_id, 'delete_interface', interface, request.session['token'])
2261+ elif present == "1":
2262+ remote.modify_system(obj_id, 'modify_interface', ifdata, request.session['token'])
2263+ except Exception, e:
2264+ return error_page(request, str(e))
2265+
2266+ try:
2267+ remote.save_item(what, obj_id, request.session['token'], editmode)
2268+ except Exception, e:
2269+ return error_page(request, str(e))
2270+
2271+ return HttpResponseRedirect('/cobbler_web/%s/list' % what)
2272+
2273+
2274+# ======================================================================
2275+# Login/Logout views
2276+
2277+def test_user_authenticated(request):
2278+ global remote
2279+ global username
2280+ global url_cobbler_api
2281+
2282+ if url_cobbler_api is None:
2283+ url_cobbler_api = utils.local_get_cobbler_api_url()
2284+
2285+ remote = xmlrpclib.Server(url_cobbler_api, allow_none=True)
2286+
2287+ # if we have a token, get the associated username from
2288+ # the remote server via XMLRPC. We then compare that to
2289+ # the value stored in the session. If everything matches up,
2290+ # the user is considered successfully authenticated
2291+ if request.session.has_key('token') and request.session['token'] != '':
2292+ try:
2293+ if remote.token_check(request.session['token']):
2294+ token_user = remote.get_user_from_token(request.session['token'])
2295+ if request.session.has_key('username') and request.session['username'] == token_user:
2296+ username = request.session['username']
2297+ return True
2298+ except:
2299+ # just let it fall through to the 'return False' below
2300+ pass
2301+ return False
2302+
2303+def login(request, next=None):
2304+ return render_to_response('login.tmpl', {'next':next})
2305+
2306+def do_login(request):
2307+ global remote
2308+ global username
2309+ global url_cobbler_api
2310+
2311+ username = request.POST.get('username', '').strip()
2312+ password = request.POST.get('password', '')
2313+ nextsite = request.POST.get('next',None)
2314+
2315+ if url_cobbler_api is None:
2316+ url_cobbler_api = utils.local_get_cobbler_api_url()
2317+
2318+ remote = xmlrpclib.Server(url_cobbler_api, allow_none=True)
2319+
2320+ try:
2321+ token = remote.login(username, password)
2322+ except:
2323+ token = None
2324+
2325+ if token:
2326+ request.session['username'] = username
2327+ request.session['token'] = token
2328+ if nextsite:
2329+ return HttpResponseRedirect(nextsite)
2330+ else:
2331+ return HttpResponseRedirect("/cobbler_web")
2332+ else:
2333+ return login(request,nextsite)
2334+
2335+def do_logout(request):
2336+ request.session['username'] = ""
2337+ request.session['token'] = ""
2338+ return HttpResponseRedirect("/cobbler_web")
2339
2340=== added file '.pc/59_add_csrf_protection.patch/web/settings.py'
2341--- .pc/59_add_csrf_protection.patch/web/settings.py 1970-01-01 00:00:00 +0000
2342+++ .pc/59_add_csrf_protection.patch/web/settings.py 2011-11-11 16:30:32 +0000
2343@@ -0,0 +1,69 @@
2344+# Django settings for cobbler-web project.
2345+
2346+DEBUG = True
2347+TEMPLATE_DEBUG = DEBUG
2348+
2349+ADMINS = (
2350+ # ('Your Name', 'your_email@domain.com'),
2351+)
2352+
2353+MANAGERS = ADMINS
2354+
2355+DATABASE_ENGINE = '' # cobbler-web does not use a database
2356+DATABASE_NAME = ''
2357+DATABASE_USER = ''
2358+DATABASE_PASSWORD = ''
2359+DATABASE_HOST = ''
2360+DATABASE_PORT = ''
2361+
2362+# this is not used by cobbler-web
2363+TIME_ZONE = None
2364+
2365+# Language section
2366+# TBD.
2367+LANGUAGE_CODE = 'en-us'
2368+USE_I18N = False
2369+
2370+SITE_ID = 1
2371+
2372+# not used
2373+MEDIA_ROOT = ''
2374+MEDIA_URL = ''
2375+ADMIN_MEDIA_PREFIX = '/media/'
2376+
2377+# FIXME: ???
2378+SECRET_KEY = 'w&x*74x-b=ycigsdya03699o!9kt4(z4wyx-us9q=--&7clv4='
2379+
2380+# code config
2381+
2382+TEMPLATE_LOADERS = (
2383+ 'django.template.loaders.filesystem.load_template_source',
2384+ 'django.template.loaders.app_directories.load_template_source',
2385+)
2386+MIDDLEWARE_CLASSES = (
2387+ 'django.middleware.common.CommonMiddleware',
2388+ 'django.contrib.sessions.middleware.SessionMiddleware',
2389+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
2390+)
2391+ROOT_URLCONF = 'urls'
2392+
2393+TEMPLATE_DIRS = (
2394+ '/usr/share/cobbler/web/templates',
2395+)
2396+INSTALLED_APPS = (
2397+ 'django.contrib.auth',
2398+ 'django.contrib.contenttypes',
2399+ 'django.contrib.sessions',
2400+ 'django.contrib.sites',
2401+ 'cobbler_web',
2402+)
2403+
2404+from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
2405+
2406+TEMPLATE_CONTEXT_PROCESSORS += (
2407+ 'django.core.context_processors.request',
2408+)
2409+
2410+SESSION_ENGINE = 'django.contrib.sessions.backends.file'
2411+SESSION_FILE_PATH = '/var/lib/cobbler/webui_sessions'
2412+
2413
2414=== added directory '.pc/60_yaml_safe_load.patch'
2415=== added file '.pc/60_yaml_safe_load.patch/.timestamp'
2416=== added directory '.pc/60_yaml_safe_load.patch/cobbler'
2417=== added file '.pc/60_yaml_safe_load.patch/cobbler/api.py'
2418--- .pc/60_yaml_safe_load.patch/cobbler/api.py 1970-01-01 00:00:00 +0000
2419+++ .pc/60_yaml_safe_load.patch/cobbler/api.py 2011-11-11 16:30:32 +0000
2420@@ -0,0 +1,947 @@
2421+"""
2422+python API module for Cobbler
2423+see source for cobbler.py, or pydoc, for example usage.
2424+CLI apps and daemons should import api.py, and no other cobbler code.
2425+
2426+Copyright 2006-2009, Red Hat, Inc
2427+Michael DeHaan <mdehaan@redhat.com>
2428+
2429+This program is free software; you can redistribute it and/or modify
2430+it under the terms of the GNU General Public License as published by
2431+the Free Software Foundation; either version 2 of the License, or
2432+(at your option) any later version.
2433+
2434+This program is distributed in the hope that it will be useful,
2435+but WITHOUT ANY WARRANTY; without even the implied warranty of
2436+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2437+GNU General Public License for more details.
2438+
2439+You should have received a copy of the GNU General Public License
2440+along with this program; if not, write to the Free Software
2441+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
2442+02110-1301 USA
2443+"""
2444+
2445+import yaml
2446+import config
2447+import utils
2448+import action_sync
2449+import action_check
2450+import action_import
2451+import action_reposync
2452+import action_status
2453+import action_validate
2454+import action_buildiso
2455+import action_replicate
2456+import action_acl
2457+import action_report
2458+import action_power
2459+import action_log
2460+import action_hardlink
2461+import action_dlcontent
2462+from cexceptions import *
2463+try:
2464+ import subprocess as sub_process
2465+except:
2466+ import sub_process
2467+import module_loader
2468+import kickgen
2469+import yumgen
2470+import pxegen
2471+from utils import _
2472+
2473+import logging
2474+import time
2475+import random
2476+import os
2477+import xmlrpclib
2478+import traceback
2479+import exceptions
2480+import clogger
2481+
2482+import item_distro
2483+import item_profile
2484+import item_system
2485+import item_repo
2486+import item_image
2487+import item_mgmtclass
2488+import item_package
2489+import item_file
2490+
2491+ERROR = 100
2492+INFO = 10
2493+DEBUG = 5
2494+
2495+# notes on locking:
2496+# BootAPI is a singleton object
2497+# the XMLRPC variants allow 1 simultaneous request
2498+# therefore we flock on /etc/cobbler/settings for now
2499+# on a request by request basis.
2500+
2501+class BootAPI:
2502+
2503+ __shared_state = {}
2504+ __has_loaded = False
2505+
2506+ # ===========================================================
2507+
2508+ def __init__(self, is_cobblerd=False):
2509+ """
2510+ Constructor
2511+ """
2512+
2513+ # FIXME: this should be switchable through some simple system
2514+
2515+ self.__dict__ = BootAPI.__shared_state
2516+ self.perms_ok = False
2517+ if not BootAPI.__has_loaded:
2518+
2519+ if os.path.exists("/etc/cobbler/use.couch"):
2520+ self.use_couch = True
2521+ else:
2522+ self.use_couch = False
2523+
2524+ # NOTE: we do not log all API actions, because
2525+ # a simple CLI invocation may call adds and such
2526+ # to load the config, which would just fill up
2527+ # the logs, so we'll do that logging at CLI
2528+ # level (and remote.py web service level) instead.
2529+
2530+ random.seed()
2531+ self.is_cobblerd = is_cobblerd
2532+
2533+ try:
2534+ self.logger = clogger.Logger("/var/log/cobbler/cobbler.log")
2535+ except CX:
2536+ # return to CLI/other but perms are not valid
2537+ # perms_ok is False
2538+ return
2539+
2540+ # FIMXE: conslidate into 1 server instance
2541+
2542+ self.selinux_enabled = utils.is_selinux_enabled()
2543+ self.dist = utils.check_dist()
2544+ self.os_version = utils.os_release()
2545+
2546+ BootAPI.__has_loaded = True
2547+
2548+ module_loader.load_modules()
2549+
2550+ self._config = config.Config(self)
2551+ self.deserialize()
2552+
2553+ self.authn = self.get_module_from_file(
2554+ "authentication",
2555+ "module",
2556+ "authn_configfile"
2557+ )
2558+ self.authz = self.get_module_from_file(
2559+ "authorization",
2560+ "module",
2561+ "authz_allowall"
2562+ )
2563+
2564+ # FIXME: pass more loggers around, and also see that those
2565+ # using things via tasks construct their own kickgen/yumgen/
2566+ # pxegen versus reusing this one, which has the wrong logger
2567+ # (most likely) for background tasks.
2568+
2569+ self.kickgen = kickgen.KickGen(self._config)
2570+ self.yumgen = yumgen.YumGen(self._config)
2571+ self.pxegen = pxegen.PXEGen(self._config, logger=self.logger)
2572+ self.logger.debug("API handle initialized")
2573+ self.perms_ok = True
2574+
2575+ # ==========================================================
2576+
2577+ def is_selinux_enabled(self):
2578+ """
2579+ Returns whether selinux is enabled on the cobbler server.
2580+ We check this just once at cobbler API init time, because
2581+ a restart is required to change this; this does /not/ check
2582+ enforce/permissive, nor does it need to.
2583+ """
2584+ return self.selinux_enabled
2585+
2586+ def is_selinux_supported(self):
2587+ """
2588+ Returns whether or not the OS is sufficient enough
2589+ to run with SELinux enabled (currently EL 5 or later).
2590+ """
2591+ self.dist
2592+ if self.dist == "redhat" and self.os_version < 5:
2593+ # doesn't support public_content_t
2594+ return False
2595+ return True
2596+
2597+ # ==========================================================
2598+
2599+ def last_modified_time(self):
2600+ """
2601+ Returns the time of the last modification to cobbler, made by any
2602+ API instance, regardless of the serializer type.
2603+ """
2604+ if not os.path.exists("/var/lib/cobbler/.mtime"):
2605+ old = os.umask(0x777)
2606+ fd = open("/var/lib/cobbler/.mtime","w")
2607+ fd.write("0")
2608+ fd.close()
2609+ os.umask(old)
2610+ return 0
2611+ fd = open("/var/lib/cobbler/.mtime")
2612+ data = fd.read().strip()
2613+ return float(data)
2614+
2615+ # ==========================================================
2616+
2617+ def log(self,msg,args=None,debug=False):
2618+ if debug:
2619+ logger = self.logger.debug
2620+ else:
2621+ logger = self.logger.info
2622+ if args is None:
2623+ logger("%s" % msg)
2624+ else:
2625+ logger("%s; %s" % (msg, str(args)))
2626+
2627+ # ==========================================================
2628+
2629+ def version(self, extended=False):
2630+ """
2631+ What version is cobbler?
2632+
2633+ If extended == False, returns a float for backwards compatibility
2634+
2635+ If extended == True, returns a dict:
2636+
2637+ gitstamp -- the last git commit hash
2638+ gitdate -- the last git commit date on the builder machine
2639+ builddate -- the time of the build
2640+ version -- something like "1.3.2"
2641+ version_tuple -- something like [ 1, 3, 2 ]
2642+ """
2643+ fd = open("/etc/cobbler/version")
2644+ ydata = fd.read()
2645+ fd.close()
2646+ data = yaml.load(ydata)
2647+ if not extended:
2648+ # for backwards compatibility and use with koan's comparisons
2649+ elems = data["version_tuple"]
2650+ return int(elems[0]) + 0.1*int(elems[1]) + 0.001*int(elems[2])
2651+ else:
2652+ return data
2653+
2654+ # ==========================================================
2655+
2656+ def clear(self):
2657+ """
2658+ Forget about current list of profiles, distros, and systems
2659+ # FIXME: is this used anymore?
2660+ """
2661+ return self._config.clear()
2662+
2663+ def __cmp(self,a,b):
2664+ return cmp(a.name,b.name)
2665+ # ==========================================================
2666+
2667+ def get_item(self, what, name):
2668+ self.log("get_item",[what,name],debug=True)
2669+ return self._config.get_items(what).get(name)
2670+
2671+ # =============================================================
2672+
2673+ def get_items(self, what):
2674+ self.log("get_items",[what],debug=True)
2675+ return self._config.get_items(what)
2676+
2677+ def distros(self):
2678+ """
2679+ Return the current list of distributions
2680+ """
2681+ return self.get_items("distro")
2682+
2683+ def profiles(self):
2684+ """
2685+ Return the current list of profiles
2686+ """
2687+ return self.get_items("profile")
2688+
2689+ def systems(self):
2690+ """
2691+ Return the current list of systems
2692+ """
2693+ return self.get_items("system")
2694+
2695+ def repos(self):
2696+ """
2697+ Return the current list of repos
2698+ """
2699+ return self.get_items("repo")
2700+
2701+ def images(self):
2702+ """
2703+ Return the current list of images
2704+ """
2705+ return self.get_items("image")
2706+
2707+ def settings(self):
2708+ """
2709+ Return the application configuration
2710+ """
2711+ return self._config.settings()
2712+
2713+ def mgmtclasses(self):
2714+ """
2715+ Return the current list of mgmtclasses
2716+ """
2717+ return self.get_items("mgmtclass")
2718+
2719+ def packages(self):
2720+ """
2721+ Return the current list of packages
2722+ """
2723+ return self.get_items("package")
2724+
2725+ def files(self):
2726+ """
2727+ Return the current list of files
2728+ """
2729+ return self.get_items("file")
2730+
2731+ # =======================================================================
2732+
2733+ def update(self):
2734+ """
2735+ This can be called is no longer used by cobbler.
2736+ And is here to just avoid breaking older scripts.
2737+ """
2738+ return True
2739+
2740+ # ========================================================================
2741+
2742+ def copy_item(self, what, ref, newname, logger=None):
2743+ self.log("copy_item(%s)"%what,[ref.name, newname])
2744+ return self.get_items(what).copy(ref,newname,logger=logger)
2745+
2746+ def copy_distro(self, ref, newname):
2747+ return self.copy_item("distro", ref, newname, logger=None)
2748+
2749+ def copy_profile(self, ref, newname):
2750+ return self.copy_item("profile", ref, newname, logger=None)
2751+
2752+ def copy_system(self, ref, newname):
2753+ return self.copy_item("system", ref, newname, logger=None)
2754+
2755+ def copy_repo(self, ref, newname):
2756+ return self.copy_item("repo", ref, newname, logger=None)
2757+
2758+ def copy_image(self, ref, newname):
2759+ return self.copy_item("image", ref, newname, logger=None)
2760+
2761+ def copy_mgmtclass(self, ref, newname):
2762+ return self.copy_item("mgmtclass", ref, newname, logger=None)
2763+
2764+ def copy_package(self, ref, newname):
2765+ return self.copy_item("package", ref, newname, logger=None)
2766+
2767+ def copy_file(self, ref, newname):
2768+ return self.copy_item("file", ref, newname, logger=None)
2769+
2770+ # ==========================================================================
2771+
2772+ def remove_item(self, what, ref, recursive=False, delete=True, with_triggers=True, logger=None):
2773+ if isinstance(what, basestring):
2774+ if isinstance(ref, basestring):
2775+ ref = self.get_item(what, ref)
2776+ if ref is None:
2777+ return # nothing to remove
2778+ self.log("remove_item(%s)" % what, [ref.name])
2779+ return self.get_items(what).remove(ref.name, recursive=recursive, with_delete=delete, with_triggers=with_triggers, logger=logger)
2780+
2781+ def remove_distro(self, ref, recursive=False, delete=True, with_triggers=True, logger=None):
2782+ return self.remove_item("distro", ref, recursive=recursive, delete=delete, with_triggers=with_triggers, logger=logger)
2783+
2784+ def remove_profile(self,ref, recursive=False, delete=True, with_triggers=True, logger=None):
2785+ return self.remove_item("profile", ref, recursive=recursive, delete=delete, with_triggers=with_triggers, logger=logger)
2786+
2787+ def remove_system(self, ref, recursive=False, delete=True, with_triggers=True, logger=None):
2788+ return self.remove_item("system", ref, recursive=recursive, delete=delete, with_triggers=with_triggers, logger=logger)
2789+
2790+ def remove_repo(self, ref, recursive=False, delete=True, with_triggers=True, logger=None):
2791+ return self.remove_item("repo", ref, recursive=recursive, delete=delete, with_triggers=with_triggers, logger=logger)
2792+
2793+ def remove_image(self, ref, recursive=False, delete=True, with_triggers=True, logger=None):
2794+ return self.remove_item("image", ref, recursive=recursive, delete=delete, with_triggers=with_triggers, logger=logger)
2795+
2796+ def remove_mgmtclass(self, ref, recursive=False, delete=True, with_triggers=True, logger=None):
2797+ return self.remove_item("mgmtclass", ref, recursive=recursive, delete=delete, with_triggers=with_triggers, logger=logger)
2798+
2799+ def remove_package(self, ref, recursive=False, delete=True, with_triggers=True, logger=None):
2800+ return self.remove_item("package", ref, recursive=recursive, delete=delete, with_triggers=with_triggers, logger=logger)
2801+
2802+ def remove_file(self, ref, recursive=False, delete=True, with_triggers=True, logger=None):
2803+ return self.remove_item("file", ref, recursive=recursive, delete=delete, with_triggers=with_triggers, logger=logger)
2804+
2805+ # ==========================================================================
2806+
2807+ def rename_item(self, what, ref, newname, logger=None):
2808+ self.log("rename_item(%s)"%what,[ref.name,newname])
2809+ return self.get_items(what).rename(ref,newname,logger=logger)
2810+
2811+ def rename_distro(self, ref, newname, logger=None):
2812+ return self.rename_item("distro", ref, newname, logger=logger)
2813+
2814+ def rename_profile(self, ref, newname, logger=None):
2815+ return self.rename_item("profile", ref, newname, logger=logger)
2816+
2817+ def rename_system(self, ref, newname, logger=None):
2818+ return self.rename_item("system", ref, newname, logger=logger)
2819+
2820+ def rename_repo(self, ref, newname, logger=None):
2821+ return self.rename_item("repo", ref, newname, logger=logger)
2822+
2823+ def rename_image(self, ref, newname, logger=None):
2824+ return self.rename_item("image", ref, newname, logger=logger)
2825+
2826+ def rename_mgmtclass(self, ref, newname, logger=None):
2827+ return self.rename_item("mgmtclass", ref, newname, logger=logger)
2828+
2829+ def rename_package(self, ref, newname, logger=None):
2830+ return self.rename_item("package", ref, newname, logger=logger)
2831+
2832+ def rename_file(self, ref, newname, logger=None):
2833+ return self.rename_item("file", ref, newname, logger=logger)
2834+
2835+ # ==========================================================================
2836+
2837+ # FIXME: add a new_item method
2838+
2839+ def new_distro(self,is_subobject=False):
2840+ self.log("new_distro",[is_subobject])
2841+ return self._config.new_distro(is_subobject=is_subobject)
2842+
2843+ def new_profile(self,is_subobject=False):
2844+ self.log("new_profile",[is_subobject])
2845+ return self._config.new_profile(is_subobject=is_subobject)
2846+
2847+ def new_system(self,is_subobject=False):
2848+ self.log("new_system",[is_subobject])
2849+ return self._config.new_system(is_subobject=is_subobject)
2850+
2851+ def new_repo(self,is_subobject=False):
2852+ self.log("new_repo",[is_subobject])
2853+ return self._config.new_repo(is_subobject=is_subobject)
2854+
2855+ def new_image(self,is_subobject=False):
2856+ self.log("new_image",[is_subobject])
2857+ return self._config.new_image(is_subobject=is_subobject)
2858+
2859+ def new_mgmtclass(self,is_subobject=False):
2860+ self.log("new_mgmtclass",[is_subobject])
2861+ return self._config.new_mgmtclass(is_subobject=is_subobject)
2862+
2863+ def new_package(self,is_subobject=False):
2864+ self.log("new_package",[is_subobject])
2865+ return self._config.new_package(is_subobject=is_subobject)
2866+
2867+ def new_file(self,is_subobject=False):
2868+ self.log("new_file",[is_subobject])
2869+ return self._config.new_file(is_subobject=is_subobject)
2870+
2871+ # ==========================================================================
2872+
2873+ def add_item(self, what, ref, check_for_duplicate_names=False, save=True,logger=None):
2874+ self.log("add_item(%s)"%what,[ref.name])
2875+ return self.get_items(what).add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save,logger=logger)
2876+
2877+ def add_distro(self, ref, check_for_duplicate_names=False, save=True, logger=None):
2878+ return self.add_item("distro", ref, check_for_duplicate_names=check_for_duplicate_names, save=save,logger=logger)
2879+
2880+ def add_profile(self, ref, check_for_duplicate_names=False,save=True, logger=None):
2881+ return self.add_item("profile", ref, check_for_duplicate_names=check_for_duplicate_names, save=save,logger=logger)
2882+
2883+ def add_system(self, ref, check_for_duplicate_names=False, check_for_duplicate_netinfo=False, save=True, logger=None):
2884+ return self.add_item("system", ref, check_for_duplicate_names=check_for_duplicate_names, save=save,logger=logger)
2885+
2886+ def add_repo(self, ref, check_for_duplicate_names=False,save=True,logger=None):
2887+ return self.add_item("repo", ref, check_for_duplicate_names=check_for_duplicate_names, save=save,logger=logger)
2888+
2889+ def add_image(self, ref, check_for_duplicate_names=False,save=True, logger=None):
2890+ return self.add_item("image", ref, check_for_duplicate_names=check_for_duplicate_names, save=save,logger=logger)
2891+
2892+ def add_mgmtclass(self, ref, check_for_duplicate_names=False,save=True, logger=None):
2893+ return self.add_item("mgmtclass", ref, check_for_duplicate_names=check_for_duplicate_names, save=save,logger=logger)
2894+
2895+ def add_package(self, ref, check_for_duplicate_names=False,save=True, logger=None):
2896+ return self.add_item("package", ref, check_for_duplicate_names=check_for_duplicate_names, save=save,logger=logger)
2897+
2898+ def add_file(self, ref, check_for_duplicate_names=False,save=True, logger=None):
2899+ return self.add_item("file", ref, check_for_duplicate_names=check_for_duplicate_names, save=save,logger=logger)
2900+
2901+ # ==========================================================================
2902+
2903+ # FIXME: find_items should take all the arguments the other find
2904+ # methods do.
2905+
2906+ def find_items(self, what, criteria=None):
2907+ self.log("find_items",[what])
2908+ # defaults
2909+ if criteria is None:
2910+ criteria={}
2911+ items=self._config.get_items(what)
2912+ # empty criteria returns everything
2913+ if criteria == {}:
2914+ res=items
2915+ else:
2916+ res=items.find(return_list=True, no_errors=False, **criteria)
2917+ return res
2918+
2919+
2920+ def find_distro(self, name=None, return_list=False, no_errors=False, **kargs):
2921+ return self._config.distros().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
2922+
2923+ def find_profile(self, name=None, return_list=False, no_errors=False, **kargs):
2924+ return self._config.profiles().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
2925+
2926+ def find_system(self, name=None, return_list=False, no_errors=False, **kargs):
2927+ return self._config.systems().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
2928+
2929+ def find_repo(self, name=None, return_list=False, no_errors=False, **kargs):
2930+ return self._config.repos().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
2931+
2932+ def find_image(self, name=None, return_list=False, no_errors=False, **kargs):
2933+ return self._config.images().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
2934+
2935+ def find_mgmtclass(self, name=None, return_list=False, no_errors=False, **kargs):
2936+ return self._config.mgmtclasses().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
2937+
2938+ def find_package(self, name=None, return_list=False, no_errors=False, **kargs):
2939+ return self._config.packages().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
2940+
2941+ def find_file(self, name=None, return_list=False, no_errors=False, **kargs):
2942+ return self._config.files().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
2943+
2944+ # ==========================================================================
2945+
2946+ def __since(self,mtime,collector,collapse=False):
2947+ """
2948+ Called by get_*_since functions.
2949+ """
2950+ results1 = collector()
2951+ results2 = []
2952+ for x in results1:
2953+ if x.mtime == 0 or x.mtime >= mtime:
2954+ if not collapse:
2955+ results2.append(x)
2956+ else:
2957+ results2.append(x.to_datastruct())
2958+ return results2
2959+
2960+ def get_distros_since(self,mtime,collapse=False):
2961+ """
2962+ Returns distros modified since a certain time (in seconds since Epoch)
2963+ collapse=True specifies returning a hash instead of objects.
2964+ """
2965+ return self.__since(mtime,self.distros,collapse=collapse)
2966+
2967+ def get_profiles_since(self,mtime,collapse=False):
2968+ return self.__since(mtime,self.profiles,collapse=collapse)
2969+
2970+ def get_systems_since(self,mtime,collapse=False):
2971+ return self.__since(mtime,self.systems,collapse=collapse)
2972+
2973+ def get_repos_since(self,mtime,collapse=False):
2974+ return self.__since(mtime,self.repos,collapse=collapse)
2975+
2976+ def get_images_since(self,mtime,collapse=False):
2977+ return self.__since(mtime,self.images,collapse=collapse)
2978+
2979+ def get_mgmtclasses_since(self,mtime,collapse=False):
2980+ return self.__since(mtime,self.mgmtclasses,collapse=collapse)
2981+
2982+ def get_packages_since(self,mtime,collapse=False):
2983+ return self.__since(mtime,self.packages,collapse=collapse)
2984+
2985+ def get_files_since(self,mtime,collapse=False):
2986+ return self.__since(mtime,self.files,collapse=collapse)
2987+
2988+ # ==========================================================================
2989+
2990+ def dump_vars(self, obj, format=False):
2991+ return obj.dump_vars(format)
2992+
2993+ # ==========================================================================
2994+
2995+ def auto_add_repos(self):
2996+ """
2997+ Import any repos this server knows about and mirror them.
2998+ Credit: Seth Vidal.
2999+ """
3000+ self.log("auto_add_repos")
3001+ try:
3002+ import yum
3003+ except:
3004+ raise CX(_("yum is not installed"))
3005+
3006+ version = yum.__version__
3007+ (a,b,c) = version.split(".")
3008+ version = a* 1000 + b*100 + c
3009+ if version < 324:
3010+ raise CX(_("need yum > 3.2.4 to proceed"))
3011+
3012+ base = yum.YumBase()
3013+ base.doRepoSetup()
3014+ repos = base.repos.listEnabled()
3015+ if len(repos) == 0:
3016+ raise CX(_("no repos enabled/available -- giving up."))
3017+
3018+ for repo in repos:
3019+ url = repo.urls[0]
3020+ cobbler_repo = self.new_repo()
3021+ auto_name = repo.name.replace(" ","")
3022+ # FIXME: probably doesn't work for yum-rhn-plugin ATM
3023+ cobbler_repo.set_mirror(url)
3024+ cobbler_repo.set_name(auto_name)
3025+ print "auto adding: %s (%s)" % (auto_name, url)
3026+ self._config.repos().add(cobbler_repo,save=True)
3027+
3028+ # run cobbler reposync to apply changes
3029+ return True
3030+
3031+ # ==========================================================================
3032+
3033+ def get_repo_config_for_profile(self,obj):
3034+ return self.yumgen.get_yum_config(obj,True)
3035+
3036+ def get_repo_config_for_system(self,obj):
3037+ return self.yumgen.get_yum_config(obj,False)
3038+
3039+ # ==========================================================================
3040+
3041+ def get_template_file_for_profile(self,obj,path):
3042+ template_results = self.pxegen.write_templates(obj,False,path)
3043+ if template_results.has_key(path):
3044+ return template_results[path]
3045+ else:
3046+ return "# template path not found for specified profile"
3047+
3048+ def get_template_file_for_system(self,obj,path):
3049+ template_results = self.pxegen.write_templates(obj,False,path)
3050+ if template_results.has_key(path):
3051+ return template_results[path]
3052+ else:
3053+ return "# template path not found for specified system"
3054+
3055+ # ==========================================================================
3056+
3057+ def generate_kickstart(self,profile,system):
3058+ self.log("generate_kickstart")
3059+ if system:
3060+ return self.kickgen.generate_kickstart_for_system(system)
3061+ else:
3062+ return self.kickgen.generate_kickstart_for_profile(profile)
3063+
3064+ # ==========================================================================
3065+
3066+ def check(self, logger=None):
3067+ """
3068+ See if all preqs for network booting are valid. This returns
3069+ a list of strings containing instructions on things to correct.
3070+ An empty list means there is nothing to correct, but that still
3071+ doesn't mean there are configuration errors. This is mainly useful
3072+ for human admins, who may, for instance, forget to properly set up
3073+ their TFTP servers for PXE, etc.
3074+ """
3075+ self.log("check")
3076+ check = action_check.BootCheck(self._config, logger=logger)
3077+ return check.run()
3078+
3079+ # ==========================================================================
3080+
3081+ def dlcontent(self,force=False,logger=None):
3082+ """
3083+ Downloads bootloader content that may not be avialable in packages
3084+ for the given arch, ex: if installing on PPC, get syslinux. If installing
3085+ on x86_64, get elilo, etc.
3086+ """
3087+ # FIXME: teach code that copies it to grab from the right place
3088+ self.log("dlcontent")
3089+ grabber = action_dlcontent.ContentDownloader(self._config, logger=logger)
3090+ return grabber.run(force)
3091+
3092+ # ==========================================================================
3093+
3094+ def validateks(self, logger=None):
3095+ """
3096+ Use ksvalidator (from pykickstart, if available) to determine
3097+ whether the cobbler kickstarts are going to be (likely) well
3098+ accepted by Anaconda. Presence of an error does not indicate
3099+ the kickstart is bad, only that the possibility exists. ksvalidator
3100+ is not available on all platforms and can not detect "future"
3101+ kickstart format correctness.
3102+ """
3103+ self.log("validateks")
3104+ validator = action_validate.Validate(self._config, logger=logger)
3105+ return validator.run()
3106+
3107+ # ==========================================================================
3108+
3109+ def sync(self,verbose=False, logger=None):
3110+ """
3111+ Take the values currently written to the configuration files in
3112+ /etc, and /var, and build out the information tree found in
3113+ /tftpboot. Any operations done in the API that have not been
3114+ saved with serialize() will NOT be synchronized with this command.
3115+ """
3116+ self.log("sync")
3117+ sync = self.get_sync(verbose=verbose, logger=logger)
3118+ return sync.run()
3119+
3120+ # ==========================================================================
3121+
3122+ def get_sync(self,verbose=False,logger=None):
3123+ self.dhcp = self.get_module_from_file(
3124+ "dhcp",
3125+ "module",
3126+ "manage_isc"
3127+ ).get_manager(self._config,logger)
3128+ self.dns = self.get_module_from_file(
3129+ "dns",
3130+ "module",
3131+ "manage_bind"
3132+ ).get_manager(self._config,logger)
3133+ self.tftpd = self.get_module_from_file(
3134+ "tftpd",
3135+ "module",
3136+ "in_tftpd",
3137+ ).get_manager(self._config,logger)
3138+
3139+ return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns,tftpd=self.tftpd,verbose=verbose,logger=logger)
3140+
3141+ # ==========================================================================
3142+
3143+ def reposync(self, name=None, tries=1, nofail=False, logger=None):
3144+ """
3145+ Take the contents of /var/lib/cobbler/repos and update them --
3146+ or create the initial copy if no contents exist yet.
3147+ """
3148+ self.log("reposync",[name])
3149+ reposync = action_reposync.RepoSync(self._config, tries=tries, nofail=nofail, logger=logger)
3150+ return reposync.run(name)
3151+
3152+ # ==========================================================================
3153+
3154+ def status(self,mode,logger=None):
3155+ statusifier = action_status.BootStatusReport(self._config,mode,logger=logger)
3156+ return statusifier.run()
3157+
3158+ # ==========================================================================
3159+
3160+ def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None,breed=None,os_version=None,logger=None):
3161+ """
3162+ Automatically import a directory tree full of distribution files.
3163+ mirror_url can be a string that represents a path, a user@host
3164+ syntax for SSH, or an rsync:// address. If mirror_url is a
3165+ filesystem path and mirroring is not desired, set network_root
3166+ to something like "nfs://path/to/mirror_url/root"
3167+ """
3168+ self.log("import_tree",[mirror_url, mirror_name, network_root, kickstart_file, rsync_flags])
3169+ importer_modules = self.get_modules_in_category("manage/import")
3170+ for importer_module in importer_modules:
3171+ manager = importer_module.get_import_manager(self._config,logger)
3172+ if 1:#try:
3173+ (found,pkgdir) = manager.check_for_signature(mirror_url,breed)
3174+ if found:
3175+ self.log("running import manager: %s" % manager.what())
3176+ return manager.run(pkgdir,mirror_url,mirror_name,network_root,kickstart_file,rsync_flags,arch,breed,os_version)
3177+ #except:
3178+ # self.log("an error occured while running the import manager")
3179+ # continue
3180+ self.log("No import managers found a valid signature at the location specified")
3181+ return False
3182+
3183+ # ==========================================================================
3184+
3185+ def acl_config(self,adduser=None,addgroup=None,removeuser=None,removegroup=None, logger=None):
3186+ """
3187+ Configures users/groups to run the cobbler CLI as non-root.
3188+ Pass in only one option at a time. Powers "cobbler aclconfig"
3189+ """
3190+ acl = action_acl.AclConfig(self._config, logger)
3191+ return acl.run(
3192+ adduser=adduser,
3193+ addgroup=addgroup,
3194+ removeuser=removeuser,
3195+ removegroup=removegroup
3196+ )
3197+
3198+ # ==========================================================================
3199+
3200+ def serialize(self):
3201+ """
3202+ Save the config file(s) to disk.
3203+ Cobbler internal use only.
3204+ """
3205+ return self._config.serialize()
3206+
3207+ def deserialize(self):
3208+ """
3209+ Load the current configuration from config file(s)
3210+ Cobbler internal use only.
3211+ """
3212+ return self._config.deserialize()
3213+
3214+ def deserialize_raw(self,collection_name):
3215+ """
3216+ Get the collection back just as raw data.
3217+ Cobbler internal use only.
3218+ """
3219+ return self._config.deserialize_raw(collection_name)
3220+
3221+ def deserialize_item_raw(self,collection_name,obj_name):
3222+ """
3223+ Get an object back as raw data.
3224+ Can be very fast for shelve or catalog serializers
3225+ Cobbler internal use only.
3226+ """
3227+ return self._config.deserialize_item_raw(collection_name,obj_name)
3228+
3229+ # ==========================================================================
3230+
3231+ def get_module_by_name(self,module_name):
3232+ """
3233+ Returns a loaded cobbler module named 'name', if one exists, else None.
3234+ Cobbler internal use only.
3235+ """
3236+ return module_loader.get_module_by_name(module_name)
3237+
3238+ def get_module_from_file(self,section,name,fallback=None):
3239+ """
3240+ Looks in /etc/cobbler/modules.conf for a section called 'section'
3241+ and a key called 'name', and then returns the module that corresponds
3242+ to the value of that key.
3243+ Cobbler internal use only.
3244+ """
3245+ return module_loader.get_module_from_file(section,name,fallback)
3246+
3247+ def get_modules_in_category(self,category):
3248+ """
3249+ Returns all modules in a given category, for instance "serializer", or "cli".
3250+ Cobbler internal use only.
3251+ """
3252+ return module_loader.get_modules_in_category(category)
3253+
3254+ # ==========================================================================
3255+
3256+ def authenticate(self,user,password):
3257+ """
3258+ (Remote) access control.
3259+ Cobbler internal use only.
3260+ """
3261+ rc = self.authn.authenticate(self,user,password)
3262+ self.log("authenticate",[user,rc])
3263+ return rc
3264+
3265+ def authorize(self,user,resource,arg1=None,arg2=None):
3266+ """
3267+ (Remote) access control.
3268+ Cobbler internal use only.
3269+ """
3270+ rc = self.authz.authorize(self,user,resource,arg1,arg2)
3271+ self.log("authorize",[user,resource,arg1,arg2,rc],debug=True)
3272+ return rc
3273+
3274+ # ==========================================================================
3275+
3276+ def build_iso(self,iso=None,profiles=None,systems=None,buildisodir=None,distro=None,standalone=None,source=None, exclude_dns=None, logger=None):
3277+ builder = action_buildiso.BuildIso(self._config, logger=logger)
3278+ return builder.run(
3279+ iso=iso, profiles=profiles, systems=systems, buildisodir=buildisodir, distro=distro, standalone=standalone, source=source, exclude_dns=exclude_dns
3280+ )
3281+
3282+ # ==========================================================================
3283+
3284+ def hardlink(self, logger=None):
3285+ linker = action_hardlink.HardLinker(self._config, logger=logger)
3286+ return linker.run()
3287+
3288+ # ==========================================================================
3289+
3290+ def replicate(self, cobbler_master = None, distro_patterns="", profile_patterns="", system_patterns="", repo_patterns="", image_patterns="",
3291+ mgmtclass_patterns=None, package_patterns=None, file_patterns=None, prune=False, omit_data=False, sync_all=False, logger=None):
3292+ """
3293+ Pull down data/configs from a remote cobbler server that is a master to this server.
3294+ """
3295+ replicator = action_replicate.Replicate(self._config, logger=logger)
3296+ return replicator.run(
3297+ cobbler_master = cobbler_master,
3298+ distro_patterns = distro_patterns,
3299+ profile_patterns = profile_patterns,
3300+ system_patterns = system_patterns,
3301+ repo_patterns = repo_patterns,
3302+ image_patterns = image_patterns,
3303+ mgmtclass_patterns = mgmtclass_patterns,
3304+ package_patterns = package_patterns,
3305+ file_patterns = file_patterns,
3306+ prune = prune,
3307+ omit_data = omit_data,
3308+ sync_all = sync_all
3309+ )
3310+
3311+ # ==========================================================================
3312+
3313+ def report(self, report_what = None, report_name = None, report_type = None, report_fields = None, report_noheaders = None):
3314+ """
3315+ Report functionality for cobbler
3316+ """
3317+ reporter = action_report.Report(self._config)
3318+ return reporter.run(report_what = report_what, report_name = report_name,\
3319+ report_type = report_type, report_fields = report_fields,\
3320+ report_noheaders = report_noheaders)
3321+
3322+ # ==========================================================================
3323+
3324+ def get_kickstart_templates(self):
3325+ return utils.get_kickstar_templates(self)
3326+
3327+ # ==========================================================================
3328+
3329+ def power_on(self, system, user=None, password=None, logger=None):
3330+ """
3331+ Powers up a system that has power management configured.
3332+ """
3333+ return action_power.PowerTool(self._config,system,self,user,password,logger=logger).power("on")
3334+
3335+ def power_off(self, system, user=None, password=None, logger=None):
3336+ """
3337+ Powers down a system that has power management configured.
3338+ """
3339+ return action_power.PowerTool(self._config,system,self,user,password,logger=logger).power("off")
3340+
3341+ def reboot(self,system, user=None, password=None, logger=None):
3342+ """
3343+ Cycles power on a system that has power management configured.
3344+ """
3345+ self.power_off(system, user, password, logger=logger)
3346+ time.sleep(5)
3347+ return self.power_on(system, user, password, logger=logger)
3348+
3349+ def power_status(self, system, user=None, password=None, logger=None):
3350+ """
3351+ Returns the power status for a system that has power management configured.
3352+
3353+ @return: 0 the system is powered on, False if it's not or None on error
3354+ """
3355+ return action_power.PowerTool(self._config, system, self, user, password, logger = logger).power("status")
3356+
3357+
3358+ # ==========================================================================
3359+
3360+ def clear_logs(self, system, logger=None):
3361+ """
3362+ Clears console and anamon logs for system
3363+ """
3364+ return action_log.LogTool(self._config,system,self, logger=logger).clear()
3365+
3366+ def get_os_details(self):
3367+ return (self.dist, self.os_version)
3368
3369=== added file '.pc/60_yaml_safe_load.patch/cobbler/item.py'
3370--- .pc/60_yaml_safe_load.patch/cobbler/item.py 1970-01-01 00:00:00 +0000
3371+++ .pc/60_yaml_safe_load.patch/cobbler/item.py 2011-11-11 16:30:32 +0000
3372@@ -0,0 +1,427 @@
3373+"""
3374+An Item is a serializable thing that can appear in a Collection
3375+
3376+Copyright 2006-2009, Red Hat, Inc
3377+Michael DeHaan <mdehaan@redhat.com>
3378+
3379+This software may be freely redistributed under the terms of the GNU
3380+general public license.
3381+
3382+You should have received a copy of the GNU General Public License
3383+along with this program; if not, write to the Free Software
3384+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
3385+"""
3386+
3387+import exceptions
3388+import utils
3389+from cexceptions import *
3390+from utils import _
3391+import pprint
3392+import fnmatch
3393+
3394+class Item:
3395+
3396+ TYPE_NAME = "generic"
3397+
3398+ def __init__(self,config,is_subobject=False):
3399+ """
3400+ Constructor. Requires a back reference to the Config management object.
3401+
3402+ NOTE: is_subobject is used for objects that allow inheritance in their trees. This
3403+ inheritance refers to conceptual inheritance, not Python inheritance. Objects created
3404+ with is_subobject need to call their set_parent() method immediately after creation
3405+ and pass in a value of an object of the same type. Currently this is only supported
3406+ for profiles. Subobjects blend their data with their parent objects and only require
3407+ a valid parent name and a name for themselves, so other required options can be
3408+ gathered from items further up the cobbler tree.
3409+
3410+ Old cobbler: New cobbler:
3411+ distro distro
3412+ profile profile
3413+ system profile <-- created with is_subobject=True
3414+ system <-- created as normal
3415+
3416+ For consistancy, there is some code supporting this in all object types, though it is only usable
3417+ (and only should be used) for profiles at this time. Objects that are children of
3418+ objects of the same type (i.e. subprofiles) need to pass this in as True. Otherwise, just
3419+ use False for is_subobject and the parent object will (therefore) have a different type.
3420+
3421+ """
3422+
3423+ self.config = config
3424+ self.settings = self.config._settings
3425+ self.clear(is_subobject) # reset behavior differs for inheritance cases
3426+ self.parent = '' # all objects by default are not subobjects
3427+ self.children = {} # caching for performance reasons, not serialized
3428+ self.log_func = self.config.api.log
3429+ self.ctime = 0 # to be filled in by collection class
3430+ self.mtime = 0 # to be filled in by collection class
3431+ self.uid = "" # to be filled in by collection class
3432+
3433+ self.last_cached_mtime = 0
3434+ self.cached_datastruct = ""
3435+
3436+ def clear(self,is_subobject=False):
3437+ """
3438+ Reset this object.
3439+ """
3440+ utils.clear_from_fields(self,self.get_fields(),is_subobject=is_subobject)
3441+
3442+ def make_clone(self):
3443+ raise exceptions.NotImplementedError
3444+
3445+ def from_datastruct(self,seed_data):
3446+ """
3447+ Modify this object to take on values in seed_data
3448+ """
3449+ return utils.from_datastruct_from_fields(self,seed_data,self.get_fields())
3450+
3451+ def to_datastruct(self):
3452+ return utils.to_datastruct_from_fields(self, self.get_fields())
3453+
3454+ def printable(self):
3455+ return utils.printable_from_fields(self,self.get_fields())
3456+
3457+ def remote_methods(self):
3458+ return utils.get_remote_methods_from_fields(self,self.get_fields())
3459+
3460+ def set_uid(self,uid):
3461+ self.uid = uid
3462+
3463+ def get_children(self,sorted=True):
3464+ """
3465+ Get direct children of this object.
3466+ """
3467+ keys = self.children.keys()
3468+ if sorted:
3469+ keys.sort()
3470+ results = []
3471+ for k in keys:
3472+ results.append(self.children[k])
3473+ return results
3474+
3475+ def get_descendants(self):
3476+ """
3477+ Get objects that depend on this object, i.e. those that
3478+ would be affected by a cascading delete, etc.
3479+ """
3480+ results = []
3481+ kids = self.get_children(sorted=False)
3482+ results.extend(kids)
3483+ for kid in kids:
3484+ grandkids = kid.get_descendants()
3485+ results.extend(grandkids)
3486+ return results
3487+
3488+ def get_parent(self):
3489+ """
3490+ For objects with a tree relationship, what's the parent object?
3491+ """
3492+ return None
3493+
3494+ def get_conceptual_parent(self):
3495+ """
3496+ The parent may just be a superclass for something like a
3497+ subprofile. Get the first parent of a different type.
3498+ """
3499+
3500+ # FIXME: this is a workaround to get the type of an instance var
3501+ # what's a more clean way to do this that's python 2.3 friendly?
3502+ # this returns something like: cobbler.item_system.System
3503+ mtype = str(self).split(" ")[0][1:]
3504+ parent = self.get_parent()
3505+ while parent is not None:
3506+ ptype = str(parent).split(" ")[0][1:]
3507+ if mtype != ptype:
3508+ self.conceptual_parent = parent
3509+ return parent
3510+ parent = parent.get_parent()
3511+ return None
3512+
3513+ def set_name(self,name):
3514+ """
3515+ All objects have names, and with the exception of System
3516+ they aren't picky about it.
3517+ """
3518+ if self.name not in ["",None] and self.parent not in ["",None] and self.name == self.parent:
3519+ raise CX(_("self parentage is weird"))
3520+ if not isinstance(name, basestring):
3521+ raise CX(_("name must be a string"))
3522+ for x in name:
3523+ if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] :
3524+ raise CX(_("invalid characters in name: '%s'" % name))
3525+ self.name = name
3526+ return True
3527+
3528+ def set_comment(self, comment):
3529+ if comment is None:
3530+ comment = ""
3531+ self.comment = comment
3532+ return True
3533+
3534+ def set_owners(self,data):
3535+ """
3536+ The owners field is a comment unless using an authz module that pays attention to it,
3537+ like authz_ownership, which ships with Cobbler but is off by default. Consult the Wiki
3538+ docs for more info on CustomizableAuthorization.
3539+ """
3540+ owners = utils.input_string_or_list(data)
3541+ self.owners = owners
3542+ return True
3543+
3544+ def set_kernel_options(self,options,inplace=False):
3545+ """
3546+ Kernel options are a space delimited list,
3547+ like 'a=b c=d e=f g h i=j' or a hash.
3548+ """
3549+ (success, value) = utils.input_string_or_hash(options)
3550+ if not success:
3551+ raise CX(_("invalid kernel options"))
3552+ else:
3553+ if inplace:
3554+ for key in value.keys():
3555+ if key.startswith("~"):
3556+ del self.kernel_options[key[1:]]
3557+ else:
3558+ self.kernel_options[key] = value[key]
3559+ else:
3560+ self.kernel_options = value
3561+ return True
3562+
3563+ def set_kernel_options_post(self,options,inplace=False):
3564+ """
3565+ Post kernel options are a space delimited list,
3566+ like 'a=b c=d e=f g h i=j' or a hash.
3567+ """
3568+ (success, value) = utils.input_string_or_hash(options)
3569+ if not success:
3570+ raise CX(_("invalid post kernel options"))
3571+ else:
3572+ if inplace:
3573+ for key in value.keys():
3574+ if key.startswith("~"):
3575+ del self.self.kernel_options_post[key[1:]]
3576+ else:
3577+ self.kernel_options_post[key] = value[key]
3578+ else:
3579+ self.kernel_options_post = value
3580+ return True
3581+
3582+ def set_ks_meta(self,options,inplace=False):
3583+ """
3584+ A comma delimited list of key value pairs, like 'a=b,c=d,e=f' or a hash.
3585+ The meta tags are used as input to the templating system
3586+ to preprocess kickstart files
3587+ """
3588+ (success, value) = utils.input_string_or_hash(options,allow_multiples=False)
3589+ if not success:
3590+ return False
3591+ else:
3592+ if inplace:
3593+ for key in value.keys():
3594+ if key.startswith("~"):
3595+ del self.ks_meta[key[1:]]
3596+ else:
3597+ self.ks_meta[key] = value[key]
3598+ else:
3599+ self.ks_meta = value
3600+ return True
3601+
3602+ def set_mgmt_classes(self,mgmt_classes):
3603+ """
3604+ Assigns a list of configuration management classes that can be assigned
3605+ to any object, such as those used by Puppet's external_nodes feature.
3606+ """
3607+ mgmt_classes_split = utils.input_string_or_list(mgmt_classes)
3608+ self.mgmt_classes = utils.input_string_or_list(mgmt_classes_split)
3609+ return True
3610+
3611+ def set_mgmt_parameters(self,mgmt_parameters):
3612+ """
3613+ A YAML string which can be assigned to any object, this is used by
3614+ Puppet's external_nodes feature.
3615+ """
3616+ if mgmt_parameters == "<<inherit>>":
3617+ self.mgmt_parameters = mgmt_parameters
3618+ else:
3619+ import yaml
3620+ data = yaml.load(mgmt_parameters)
3621+ if type(data) is not dict:
3622+ raise CX(_("Input YAML in Puppet Parameter field must evaluate to a dictionary."))
3623+ self.mgmt_parameters = data
3624+ return True
3625+
3626+
3627+ def set_template_files(self,template_files,inplace=False):
3628+ """
3629+ A comma seperated list of source=destination templates
3630+ that should be generated during a sync.
3631+ """
3632+ (success, value) = utils.input_string_or_hash(template_files,allow_multiples=False)
3633+ if not success:
3634+ return False
3635+ else:
3636+ if inplace:
3637+ for key in value.keys():
3638+ if key.startswith("~"):
3639+ del self.template_files[key[1:]]
3640+ else:
3641+ self.template_files[key] = value[key]
3642+ else:
3643+ self.template_files = value
3644+ return True
3645+
3646+ def set_boot_files(self,boot_files,inplace=False):
3647+ """
3648+ A comma seperated list of req_name=source_file_path
3649+ that should be fetchable via tftp
3650+ """
3651+ (success, value) = utils.input_string_or_hash(boot_files,allow_multiples=False)
3652+ if not success:
3653+ return False
3654+ else:
3655+ if inplace:
3656+ for key in value.keys():
3657+ if key.startswith("~"):
3658+ del self.boot_files[key[1:]]
3659+ else:
3660+ self.boot_files[key] = value[key]
3661+ else:
3662+ self.boot_files= value
3663+ return True
3664+
3665+
3666+ def set_fetchable_files(self,fetchable_files,inplace=False):
3667+ """
3668+ A comma seperated list of virt_name=path_to_template
3669+ that should be fetchable via tftp or a webserver
3670+ """
3671+ (success, value) = utils.input_string_or_hash(fetchable_files,allow_multiples=False)
3672+ if not success:
3673+ return False
3674+ else:
3675+ if inplace:
3676+ for key in value.keys():
3677+ if key.startswith("~"):
3678+ del self.fetchable_files[key[1:]]
3679+ else:
3680+ self.fetchable_files[key] = value[key]
3681+ else:
3682+ self.fetchable_files= value
3683+ return True
3684+
3685+
3686+ def sort_key(self,sort_fields=[]):
3687+ data = self.to_datastruct()
3688+ return [data.get(x,"") for x in sort_fields]
3689+
3690+ def find_match(self,kwargs,no_errors=False):
3691+ # used by find() method in collection.py
3692+ data = self.to_datastruct()
3693+ for (key, value) in kwargs.iteritems():
3694+ # Allow ~ to negate the compare
3695+ if value is not None and value.startswith("~"):
3696+ res=not self.find_match_single_key(data,key,value[1:],no_errors)
3697+ else:
3698+ res=self.find_match_single_key(data,key,value,no_errors)
3699+ if not res:
3700+ return False
3701+
3702+ return True
3703+
3704+
3705+ def find_match_single_key(self,data,key,value,no_errors=False):
3706+ # special case for systems
3707+ key_found_already = False
3708+ if data.has_key("interfaces"):
3709+ if key in [ "mac_address", "ip_address", "subnet", "virt_bridge", "dhcp_tag", "dns_name", "static_routes", "bonding", "bonding_opts", "bonding_master" ]:
3710+ key_found_already = True
3711+ for (name, interface) in data["interfaces"].iteritems():
3712+ if value is not None:
3713+ if self.__find_compare(interface[key], value):
3714+ return True
3715+
3716+ if not data.has_key(key):
3717+ if not key_found_already:
3718+ if not no_errors:
3719+ # FIXME: removed for 2.0 code, shouldn't cause any problems to not have an exception here?
3720+ # raise CX(_("searching for field that does not exist: %s" % key))
3721+ return False
3722+ else:
3723+ if value is not None: # FIXME: new?
3724+ return False
3725+
3726+ if value is None:
3727+ return True
3728+ else:
3729+ return self.__find_compare(value, data[key])
3730+
3731+
3732+ def __find_compare(self, from_search, from_obj):
3733+
3734+ if isinstance(from_obj, basestring):
3735+ # FIXME: fnmatch is only used for string to string comparisions
3736+ # which should cover most major usage, if not, this deserves fixing
3737+ if fnmatch.fnmatch(from_obj.lower(), from_search.lower()):
3738+ return True
3739+ else:
3740+ return False
3741+
3742+ else:
3743+ if isinstance(from_search, basestring):
3744+ if type(from_obj) == type([]):
3745+ from_search = utils.input_string_or_list(from_search)
3746+ for x in from_search:
3747+ if x not in from_obj:
3748+ return False
3749+ return True
3750+
3751+ if type(from_obj) == type({}):
3752+ (junk, from_search) = utils.input_string_or_hash(from_search,allow_multiples=True)
3753+ for x in from_search.keys():
3754+ y = from_search[x]
3755+ if not from_obj.has_key(x):
3756+ return False
3757+ if not (y == from_obj[x]):
3758+ return False
3759+ return True
3760+
3761+ if type(from_obj) == type(True):
3762+ if from_search.lower() in [ "true", "1", "y", "yes" ]:
3763+ inp = True
3764+ else:
3765+ inp = False
3766+ if inp == from_obj:
3767+ return True
3768+ return False
3769+
3770+ raise CX(_("find cannot compare type: %s") % type(from_obj))
3771+
3772+ def dump_vars(self,data,format=True):
3773+ raw = utils.blender(self.config.api, False, self)
3774+ if format:
3775+ return pprint.pformat(raw)
3776+ else:
3777+ return raw
3778+
3779+ def set_depth(self,depth):
3780+ self.depth = depth
3781+
3782+ def set_ctime(self,ctime):
3783+ self.ctime = ctime
3784+
3785+ def set_mtime(self,mtime):
3786+ self.mtime = mtime
3787+
3788+ def set_parent(self,parent):
3789+ self.parent = parent
3790+
3791+ def check_if_valid(self):
3792+ """
3793+ Raise exceptions if the object state is inconsistent
3794+ """
3795+ if self.name is None or self.name == "":
3796+ raise CX("Name is required")
3797+
3798+
3799+
3800
3801=== added directory '.pc/60_yaml_safe_load.patch/cobbler/modules'
3802=== added file '.pc/60_yaml_safe_load.patch/cobbler/modules/serializer_catalog.py'
3803--- .pc/60_yaml_safe_load.patch/cobbler/modules/serializer_catalog.py 1970-01-01 00:00:00 +0000
3804+++ .pc/60_yaml_safe_load.patch/cobbler/modules/serializer_catalog.py 2011-11-11 16:30:32 +0000
3805@@ -0,0 +1,241 @@
3806+"""
3807+Serializer code for cobbler.
3808+As of 8/2009, this is the "best" serializer option.
3809+It uses multiple files in /var/lib/cobbler/config/distros.d, profiles.d, etc
3810+And JSON, when possible, and YAML, when not.
3811+It is particularly fast, especially when using JSON. YAML, not so much.
3812+It also knows how to upgrade the old "single file" configs to .d versions.
3813+
3814+Copyright 2006-2009, Red Hat, Inc
3815+Michael DeHaan <mdehaan@redhat.com>
3816+
3817+This program is free software; you can redistribute it and/or modify
3818+it under the terms of the GNU General Public License as published by
3819+the Free Software Foundation; either version 2 of the License, or
3820+(at your option) any later version.
3821+
3822+This program is distributed in the hope that it will be useful,
3823+but WITHOUT ANY WARRANTY; without even the implied warranty of
3824+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3825+GNU General Public License for more details.
3826+
3827+You should have received a copy of the GNU General Public License
3828+along with this program; if not, write to the Free Software
3829+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
3830+02110-1301 USA
3831+"""
3832+
3833+import distutils.sysconfig
3834+import os
3835+import sys
3836+import glob
3837+import traceback
3838+import yaml # PyYAML
3839+import simplejson
3840+import exceptions
3841+
3842+plib = distutils.sysconfig.get_python_lib()
3843+mod_path="%s/cobbler" % plib
3844+sys.path.insert(0, mod_path)
3845+
3846+from utils import _
3847+import utils
3848+from cexceptions import *
3849+import os
3850+
3851+def can_use_json():
3852+ version = sys.version[:3]
3853+ version = float(version)
3854+ return (version > 2.3)
3855+
3856+def register():
3857+ """
3858+ The mandatory cobbler module registration hook.
3859+ """
3860+ return "serializer"
3861+
3862+def serialize_item(obj, item):
3863+
3864+ if item.name is None or item.name == "":
3865+ raise exceptions.RuntimeError("name unset for object!")
3866+
3867+ # FIXME: Need a better way to support collections/items
3868+ # appending an 's' does not work in all cases
3869+ if obj.collection_type() in [ 'mgmtclass' ]:
3870+ filename = "/var/lib/cobbler/config/%ses.d/%s" % (obj.collection_type(),item.name)
3871+ else:
3872+ filename = "/var/lib/cobbler/config/%ss.d/%s" % (obj.collection_type(),item.name)
3873+
3874+ datastruct = item.to_datastruct()
3875+
3876+ jsonable = can_use_json()
3877+
3878+ if jsonable:
3879+
3880+ # avoid using JSON on python 2.3 where we can encounter
3881+ # unicode problems with simplejson pre 2.0
3882+
3883+ if os.path.exists(filename):
3884+ print "upgrading yaml file to json: %s" % filename
3885+ os.remove(filename)
3886+ filename = filename + ".json"
3887+ datastruct = item.to_datastruct()
3888+ fd = open(filename,"w+")
3889+ data = simplejson.dumps(datastruct, encoding="utf-8")
3890+ #data = data.encode('utf-8')
3891+ fd.write(data)
3892+
3893+ else:
3894+
3895+ if os.path.exists(filename + ".json"):
3896+ print "downgrading json file back to yaml: %s" % filename
3897+ os.remove(filename + ".json")
3898+ datastruct = item.to_datastruct()
3899+ fd = open(filename,"w+")
3900+ data = yaml.dump(datastruct)
3901+ fd.write(data)
3902+
3903+ fd.close()
3904+ return True
3905+
3906+def serialize_delete(obj, item):
3907+ # FIXME: Need a better way to support collections/items
3908+ # appending an 's' does not work in all cases
3909+ if obj.collection_type() in [ 'mgmtclass', ]:
3910+ filename = "/var/lib/cobbler/config/%ses.d/%s" % (obj.collection_type(),item.name)
3911+ else:
3912+ filename = "/var/lib/cobbler/config/%ss.d/%s" % (obj.collection_type(),item.name)
3913+
3914+ filename2 = filename + ".json"
3915+ if os.path.exists(filename):
3916+ os.remove(filename)
3917+ if os.path.exists(filename2):
3918+ os.remove(filename2)
3919+ return True
3920+
3921+def deserialize_item_raw(collection_type, item_name):
3922+ # this new fn is not really implemented performantly in this module.
3923+ # yet.
3924+
3925+ # FIXME: Need a better way to support collections/items
3926+ # appending an 's' does not work in all cases
3927+ if item_name in [ 'mgmtclass' ]:
3928+ filename = "/var/lib/cobbler/config/%ses.d/%s" % (collection_type(),item_name)
3929+ else:
3930+ filename = "/var/lib/cobbler/config/%ss.d/%s" % (collection_type,item_name)
3931+
3932+ filename2 = filename + ".json"
3933+ if os.path.exists(filename):
3934+ fd = open(filename)
3935+ data = fd.read()
3936+ return yaml.load(data)
3937+ elif os.path.exists(filename2):
3938+ fd = open(filename2)
3939+ data = fd.read()
3940+ return simplejson.loads(data, encoding="utf-8")
3941+ else:
3942+ return None
3943+
3944+
3945+def serialize(obj):
3946+ """
3947+ Save an object to disk. Object must "implement" Serializable.
3948+ FIXME: Return False on access/permission errors.
3949+ This should NOT be used by API if serialize_item is available.
3950+ """
3951+ ctype = obj.collection_type()
3952+ if ctype == "settings":
3953+ return True
3954+ for x in obj:
3955+ serialize_item(obj,x)
3956+ return True
3957+
3958+def deserialize_raw(collection_type):
3959+ # FIXME: Need a better way to support collections/items
3960+ # appending an 's' does not work in all cases
3961+ if collection_type in [ 'mgmtclass' ]:
3962+ old_filename = "/var/lib/cobbler/%ses" % collection_type
3963+ else:
3964+ old_filename = "/var/lib/cobbler/%ss" % collection_type
3965+
3966+ if collection_type == "settings":
3967+ fd = open("/etc/cobbler/settings")
3968+ datastruct = yaml.load(fd.read())
3969+ fd.close()
3970+ return datastruct
3971+ elif os.path.exists(old_filename):
3972+ # for use in migration from serializer_yaml to serializer_catalog (yaml/json)
3973+ fd = open(old_filename)
3974+ datastruct = yaml.load(fd.read())
3975+ fd.close()
3976+ return datastruct
3977+ else:
3978+ results = []
3979+ # FIXME: Need a better way to support collections/items
3980+ # appending an 's' does not work in all cases
3981+ if collection_type in [ 'mgmtclass' ]:
3982+ all_files = glob.glob("/var/lib/cobbler/config/%ses.d/*" % collection_type)
3983+ else:
3984+ all_files = glob.glob("/var/lib/cobbler/config/%ss.d/*" % collection_type)
3985+
3986+ all_files = filter_upgrade_duplicates(all_files)
3987+ for f in all_files:
3988+ fd = open(f)
3989+ ydata = fd.read()
3990+ # ydata = ydata.decode()
3991+ if f.endswith(".json"):
3992+ datastruct = simplejson.loads(ydata, encoding='utf-8')
3993+ else:
3994+ datastruct = yaml.load(ydata)
3995+ results.append(datastruct)
3996+ fd.close()
3997+ return results
3998+
3999+def filter_upgrade_duplicates(file_list):
4000+ """
4001+ In a set of files, some ending with .json, some not, return
4002+ the list of files with the .json ones taking priority over
4003+ the ones that are not.
4004+ """
4005+ bases = {}
4006+ for f in file_list:
4007+ basekey = f.replace(".json","")
4008+ if f.endswith(".json"):
4009+ bases[basekey] = f
4010+ else:
4011+ lookup = bases.get(basekey,"")
4012+ if not lookup.endswith(".json"):
4013+ bases[basekey] = f
4014+ return bases.values()
4015+
4016+def deserialize(obj,topological=True):
4017+ """
4018+ Populate an existing object with the contents of datastruct.
4019+ Object must "implement" Serializable.
4020+ """
4021+ # FIXME: Need a better way to support collections/items
4022+ # appending an 's' does not work in all cases
4023+ if obj.collection_type() in [ 'mgmtclass' ]:
4024+ old_filename = "/var/lib/cobbler/%ses" % obj.collection_type()
4025+ else:
4026+ old_filename = "/var/lib/cobbler/%ss" % obj.collection_type()
4027+
4028+ datastruct = deserialize_raw(obj.collection_type())
4029+ if topological and type(datastruct) == list:
4030+ datastruct.sort(__depth_cmp)
4031+ obj.from_datastruct(datastruct)
4032+ if os.path.exists(old_filename):
4033+ # we loaded it in from the old filename, so now migrate to new fmt
4034+ sys.stderr.write("auto-removing old config format: %s\n" % old_filename)
4035+ serialize(obj)
4036+ os.remove(old_filename)
4037+ return True
4038+
4039+def __depth_cmp(item1, item2):
4040+ d1 = item1.get("depth",1)
4041+ d2 = item2.get("depth",1)
4042+ return cmp(d1,d2)
4043+
4044+if __name__ == "__main__":
4045+ print deserialize_item_raw("distro","D1")
4046+
4047
4048=== added file '.pc/60_yaml_safe_load.patch/cobbler/modules/serializer_couch.py'
4049--- .pc/60_yaml_safe_load.patch/cobbler/modules/serializer_couch.py 1970-01-01 00:00:00 +0000
4050+++ .pc/60_yaml_safe_load.patch/cobbler/modules/serializer_couch.py 2011-11-11 16:30:32 +0000
4051@@ -0,0 +1,136 @@
4052+"""
4053+Serializer code for cobbler.
4054+Experimental: couchdb version
4055+
4056+Copyright 2006-2009, Red Hat, Inc
4057+Michael DeHaan <mdehaan@redhat.com>
4058+
4059+This program is free software; you can redistribute it and/or modify
4060+it under the terms of the GNU General Public License as published by
4061+the Free Software Foundation; either version 2 of the License, or
4062+(at your option) any later version.
4063+
4064+This program is distributed in the hope that it will be useful,
4065+but WITHOUT ANY WARRANTY; without even the implied warranty of
4066+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4067+GNU General Public License for more details.
4068+
4069+You should have received a copy of the GNU General Public License
4070+along with this program; if not, write to the Free Software
4071+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
4072+02110-1301 USA
4073+"""
4074+
4075+import distutils.sysconfig
4076+import os
4077+import sys
4078+import glob
4079+import traceback
4080+import yaml # PyYAML
4081+import simplejson
4082+import exceptions
4083+
4084+plib = distutils.sysconfig.get_python_lib()
4085+mod_path="%s/cobbler" % plib
4086+sys.path.insert(0, mod_path)
4087+
4088+from utils import _
4089+import utils
4090+from cexceptions import *
4091+import os
4092+import couch
4093+
4094+typez = [ "distro", "profile", "system", "image", "repo" ]
4095+couchdb = couch.Couch('127.0.0.1')
4096+
4097+def __connect():
4098+ couchdb.connect()
4099+ for x in typez:
4100+ couchdb.createDb(x)
4101+
4102+def register():
4103+ """
4104+ The mandatory cobbler module registration hook.
4105+ """
4106+ # FIXME: only run this if enabled.
4107+ return "serializer"
4108+
4109+def serialize_item(obj, item):
4110+ __connect()
4111+ datastruct = item.to_datastruct()
4112+ # blindly prevent conflict resolution
4113+ couchdb.openDoc(obj.collection_type(), item.name)
4114+ data = couchdb.saveDoc(obj.collection_type(),
4115+ simplejson.dumps(datastruct, encoding="utf-8"),
4116+ item.name)
4117+ data = simplejson.loads(data)
4118+ return True
4119+
4120+def serialize_delete(obj, item):
4121+ __connect()
4122+ couchdb.deleteDoc(obj.collection_type(),
4123+ item.name)
4124+ return True
4125+
4126+def deserialize_item_raw(collection_type, item_name):
4127+ __connect()
4128+ data = couchdb.openDoc(collection_type, item_name)
4129+ return simplejson.loads(data, encoding="utf-8")
4130+
4131+def serialize(obj):
4132+ """
4133+ Save an object to disk. Object must "implement" Serializable.
4134+ FIXME: Return False on access/permission errors.
4135+ This should NOT be used by API if serialize_item is available.
4136+ """
4137+ __connect()
4138+ ctype = obj.collection_type()
4139+ if ctype == "settings":
4140+ return True
4141+ for x in obj:
4142+ serialize_item(obj,x)
4143+ return True
4144+
4145+def deserialize_raw(collection_type):
4146+ __connect()
4147+ contents = simplejson.loads(couchdb.listDoc(collection_type))
4148+ items = []
4149+ if contents.has_key("error") and contents.get("reason","").find("Missing") != -1:
4150+ # no items in the DB yet
4151+ return []
4152+ for x in contents["rows"]:
4153+ items.append(x["key"])
4154+
4155+ if collection_type == "settings":
4156+ fd = open("/etc/cobbler/settings")
4157+ datastruct = yaml.load(fd.read())
4158+ fd.close()
4159+ return datastruct
4160+ else:
4161+ results = []
4162+ for f in items:
4163+ data = couchdb.openDoc(collection_type, f)
4164+ datastruct = simplejson.loads(data, encoding='utf-8')
4165+ results.append(datastruct)
4166+ return results
4167+
4168+def deserialize(obj,topological=True):
4169+ """
4170+ Populate an existing object with the contents of datastruct.
4171+ Object must "implement" Serializable.
4172+ """
4173+ __connect()
4174+ datastruct = deserialize_raw(obj.collection_type())
4175+ if topological and type(datastruct) == list:
4176+ datastruct.sort(__depth_cmp)
4177+ obj.from_datastruct(datastruct)
4178+ return True
4179+
4180+def __depth_cmp(item1, item2):
4181+ d1 = item1.get("depth",1)
4182+ d2 = item2.get("depth",1)
4183+ return cmp(d1,d2)
4184+
4185+if __name__ == "__main__":
4186+ print deserialize_item_raw("distro","D1")
4187+
4188
4189=== added file '.pc/60_yaml_safe_load.patch/cobbler/remote.py'
4190--- .pc/60_yaml_safe_load.patch/cobbler/remote.py 1970-01-01 00:00:00 +0000
4191+++ .pc/60_yaml_safe_load.patch/cobbler/remote.py 2011-11-11 16:30:32 +0000
4192@@ -0,0 +1,2547 @@
4193+"""
4194+Code for Cobbler's XMLRPC API
4195+
4196+Copyright 2007-2009, Red Hat, Inc
4197+Michael DeHaan <mdehaan@redhat.com>
4198+
4199+This program is free software; you can redistribute it and/or modify
4200+it under the terms of the GNU General Public License as published by
4201+the Free Software Foundation; either version 2 of the License, or
4202+(at your option) any later version.
4203+
4204+This program is distributed in the hope that it will be useful,
4205+but WITHOUT ANY WARRANTY; without even the implied warranty of
4206+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4207+GNU General Public License for more details.
4208+
4209+You should have received a copy of the GNU General Public License
4210+along with this program; if not, write to the Free Software
4211+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
4212+02110-1301 USA
4213+"""
4214+
4215+import sys, socket, time, os, errno, re, random, stat, string
4216+import base64
4217+import SimpleXMLRPCServer
4218+from SocketServer import ThreadingMixIn
4219+import xmlrpclib
4220+import base64
4221+import fcntl
4222+import traceback
4223+import glob
4224+try:
4225+ import subprocess
4226+except:
4227+ import sub_process as subprocess
4228+from threading import Thread
4229+
4230+import api as cobbler_api
4231+import utils
4232+from cexceptions import *
4233+import item_distro
4234+import item_profile
4235+import item_system
4236+import item_repo
4237+import item_image
4238+import item_mgmtclass
4239+import item_package
4240+import item_file
4241+import clogger
4242+import pxegen
4243+import utils
4244+#from utils import * # BAD!
4245+from utils import _
4246+import configgen
4247+
4248+# FIXME: make configurable?
4249+TOKEN_TIMEOUT = 60*60 # 60 minutes
4250+EVENT_TIMEOUT = 7*24*60*60 # 1 week
4251+CACHE_TIMEOUT = 10*60 # 10 minutes
4252+
4253+# task codes
4254+EVENT_RUNNING = "running"
4255+EVENT_COMPLETE = "complete"
4256+EVENT_FAILED = "failed"
4257+# normal events
4258+EVENT_INFO = "notification"
4259+
4260+# for backwards compatibility with 1.6 and prev XMLRPC
4261+# do not remove!
4262+REMAP_COMPAT = {
4263+ "ksmeta" : "ks_meta",
4264+ "kopts" : "kernel_options",
4265+ "kopts_post" : "kernel_options_post",
4266+ "netboot-enabled" : "netboot_enabled"
4267+}
4268+
4269+class CobblerThread(Thread):
4270+ def __init__(self,event_id,remote,logatron,options):
4271+ Thread.__init__(self)
4272+ self.event_id = event_id
4273+ self.remote = remote
4274+ self.logger = logatron
4275+ if options is None:
4276+ options = {}
4277+ self.options = options
4278+
4279+ def on_done(self):
4280+ pass
4281+
4282+ def run(self):
4283+ time.sleep(1)
4284+ try:
4285+ rc = self._run(self)
4286+ self.remote._set_task_state(self,self.event_id,EVENT_COMPLETE)
4287+ self.on_done()
4288+ return rc
4289+ except:
4290+ utils.log_exc(self.logger)
4291+ self.remote._set_task_state(self,self.event_id,EVENT_FAILED)
4292+ return False
4293+
4294+# *********************************************************************
4295+# *********************************************************************
4296+
4297+class CobblerXMLRPCInterface:
4298+ """
4299+ This is the interface used for all XMLRPC methods, for instance,
4300+ as used by koan or CobblerWeb.
4301+
4302+ Most read-write operations require a token returned from "login".
4303+ Read operations do not.
4304+ """
4305+
4306+ def __init__(self,api):
4307+ """
4308+ Constructor. Requires a Cobbler API handle.
4309+ """
4310+ self.api = api
4311+ self.logger = self.api.logger
4312+ self.token_cache = {}
4313+ self.object_cache = {}
4314+ self.timestamp = self.api.last_modified_time()
4315+ self.events = {}
4316+ self.shared_secret = utils.get_shared_secret()
4317+ random.seed(time.time())
4318+ self.translator = utils.Translator(keep=string.printable)
4319+ self.pxegen = pxegen.PXEGen(api._config,self.logger)
4320+
4321+ def check(self, token):
4322+ """
4323+ Returns a list of all the messages/warnings that are things
4324+ that admin may want to correct about the configuration of
4325+ the cobbler server. This has nothing to do with "check_access"
4326+ which is an auth/authz function in the XMLRPC API.
4327+ """
4328+ self.check_access(token, "check")
4329+ return self.api.check(logger=self.logger)
4330+
4331+ def background_buildiso(self, options, token):
4332+ """
4333+ Generates an ISO in /var/www/cobbler/pub that can be used to install
4334+ profiles without using PXE.
4335+ """
4336+ def runner(self):
4337+ return self.remote.api.build_iso(
4338+ self.options.get("iso","/var/www/cobbler/pub/generated.iso"),
4339+ self.options.get("profiles",None),
4340+ self.options.get("systems",None),
4341+ self.options.get("buildisodir",None),
4342+ self.options.get("distro",None),
4343+ self.options.get("standalone",False),
4344+ self.options.get("source",None),
4345+ self.options.get("exclude_dns",False),
4346+ self.logger
4347+ )
4348+ def on_done(self):
4349+ if self.options.get("iso","") == "/var/www/cobbler/pub/generated.iso":
4350+ msg = "ISO now available for <A HREF=\"/cobbler/pub/generated.iso\">download</A>"
4351+ self.remote._new_event(msg)
4352+ return self.__start_task(runner, token, "buildiso", "Build Iso", options, on_done)
4353+
4354+ def background_aclsetup(self, options, token):
4355+ def runner(self):
4356+ return self.remote.api.acl_config(
4357+ self.options.get("adduser",None),
4358+ self.options.get("addgroup",None),
4359+ self.options.get("removeuser",None),
4360+ self.options.get("removegroup",None),
4361+ self.logger
4362+ )
4363+ return self.__start_task(runner, token, "aclsetup", "(CLI) ACL Configuration", options)
4364+
4365+ def background_dlcontent(self, options, token):
4366+ """
4367+ Download bootloaders and other support files.
4368+ """
4369+ def runner(self):
4370+ return self.remote.api.dlcontent(self.options.get("force",False), self.logger)
4371+ return self.__start_task(runner, token, "get_loaders", "Download Bootloader Content", options)
4372+
4373+ def background_sync(self, options, token):
4374+ def runner(self):
4375+ return self.remote.api.sync(self.options.get("verbose",False),logger=self.logger)
4376+ return self.__start_task(runner, token, "sync", "Sync", options)
4377+
4378+ def background_hardlink(self, options, token):
4379+ def runner(self):
4380+ return self.remote.api.hardlink(logger=self.logger)
4381+ return self.__start_task(runner, token, "hardlink", "Hardlink", options)
4382+
4383+ def background_validateks(self, options, token):
4384+ def runner(self):
4385+ return self.remote.api.validateks(logger=self.logger)
4386+ return self.__start_task(runner, token, "validateks", "Kickstart Validation", options)
4387+
4388+ def background_replicate(self, options, token):
4389+ def runner(self):
4390+ # FIXME: defaults from settings here should come from views, fix in views.py
4391+ return self.remote.api.replicate(
4392+ self.options.get("master", None),
4393+ self.options.get("distro_patterns", ""),
4394+ self.options.get("profile_patterns", ""),
4395+ self.options.get("system_patterns", ""),
4396+ self.options.get("repo_patterns", ""),
4397+ self.options.get("image_patterns", ""),
4398+ self.options.get("mgmtclass_patterns", ""),
4399+ self.options.get("package_patterns", ""),
4400+ self.options.get("file_patterns", ""),
4401+ self.options.get("prune", False),
4402+ self.options.get("omit_data", False),
4403+ self.options.get("sync_all", False),
4404+ self.logger
4405+ )
4406+ return self.__start_task(runner, token, "replicate", "Replicate", options)
4407+
4408+ def background_import(self, options, token):
4409+ def runner(self):
4410+ return self.remote.api.import_tree(
4411+ self.options.get("path", None),
4412+ self.options.get("name", None),
4413+ self.options.get("available_as", None),
4414+ self.options.get("kickstart_file", None),
4415+ self.options.get("rsync_flags",None),
4416+ self.options.get("arch",None),
4417+ self.options.get("breed", None),
4418+ self.options.get("os_version", None),
4419+ self.logger
4420+ )
4421+ return self.__start_task(runner, token, "import", "Media import", options)
4422+
4423+ def background_reposync(self, options, token):
4424+ def runner(self):
4425+ # NOTE: WebUI passes in repos here, CLI passes only:
4426+ repos = options.get("repos", [])
4427+ only = options.get("only", None)
4428+ if only is not None:
4429+ repos = [ only ]
4430+ nofail = options.get("nofail", len(repos) > 0)
4431+
4432+ if len(repos) > 0:
4433+ for name in repos:
4434+ self.remote.api.reposync(tries=self.options.get("tries",
4435+ 3), name=name, nofail=nofail, logger=self.logger)
4436+ else:
4437+ self.remote.api.reposync(tries=self.options.get("tries",3),
4438+ name=None, nofail=nofail, logger=self.logger)
4439+ return True
4440+ return self.__start_task(runner, token, "reposync", "Reposync", options)
4441+
4442+ def background_power_system(self, options, token):
4443+ def runner(self):
4444+ for x in self.options.get("systems",[]):
4445+ object_id = self.remote.get_system_handle(x,token)
4446+ self.remote.power_system(object_id,self.options.get("power",""),token,logger=self.logger)
4447+ return True
4448+ self.check_access(token, "power")
4449+ return self.__start_task(runner, token, "power", "Power management (%s)" % options.get("power",""), options)
4450+
4451+
4452+ def get_events(self, for_user=""):
4453+ """
4454+ Returns a hash(key=event id) = [ statetime, name, state, [read_by_who] ]
4455+ If for_user is set to a string, it will only return events the user
4456+ has not seen yet. If left unset, it will return /all/ events.
4457+ """
4458+
4459+ # return only the events the user has not seen
4460+ self.events_filtered = {}
4461+ for (k,x) in self.events.iteritems():
4462+ if for_user in x[3]:
4463+ pass
4464+ else:
4465+ self.events_filtered[k] = x
4466+
4467+ # mark as read so user will not get events again
4468+ if for_user is not None and for_user != "":
4469+ for (k,x) in self.events.iteritems():
4470+ if for_user in x[3]:
4471+ pass
4472+ else:
4473+ self.events[k][3].append(for_user)
4474+
4475+ return self.events_filtered
4476+
4477+ def get_event_log(self,event_id):
4478+ """
4479+ Returns the contents of a task log.
4480+ Events that are not task-based do not have logs.
4481+ """
4482+ event_id = str(event_id).replace("..","").replace("/","")
4483+ path = "/var/log/cobbler/tasks/%s.log" % event_id
4484+ self._log("getting log for %s" % event_id)
4485+ if os.path.exists(path):
4486+ fh = open(path, "r")
4487+ data = str(fh.read())
4488+ data = self.translator(data)
4489+ fh.close()
4490+ return data
4491+ else:
4492+ return "?"
4493+
4494+ def __generate_event_id(self,optype):
4495+ t = time.time()
4496+ (year, month, day, hour, minute, second, weekday, julian, dst) = time.localtime()
4497+ return "%04d-%02d-%02d_%02d%02d%02d_%s" % (year,month,day,hour,minute,second,optype)
4498+
4499+ def _new_event(self, name):
4500+ event_id = self.__generate_event_id("event")
4501+ event_id = str(event_id)
4502+ self.events[event_id] = [ float(time.time()), str(name), EVENT_INFO, [] ]
4503+
4504+ def __start_task(self, thr_obj_fn, token, role_name, name, args, on_done=None):
4505+ """
4506+ Starts a new background task.
4507+ token -- token from login() call, all tasks require tokens
4508+ role_name -- used to check token against authn/authz layers
4509+ thr_obj_fn -- function handle to run in a background thread
4510+ name -- display name to show in logs/events
4511+ args -- usually this is a single hash, containing options
4512+ on_done -- an optional second function handle to run after success (and only success)
4513+ Returns a task id.
4514+ """
4515+ self.check_access(token, role_name)
4516+ event_id = self.__generate_event_id(role_name) # use short form for logfile suffix
4517+ event_id = str(event_id)
4518+ self.events[event_id] = [ float(time.time()), str(name), EVENT_RUNNING, [] ]
4519+
4520+ self._log("start_task(%s); event_id(%s)"%(name,event_id))
4521+ logatron = clogger.Logger("/var/log/cobbler/tasks/%s.log" % event_id)
4522+
4523+ thr_obj = CobblerThread(event_id,self,logatron,args)
4524+ on_done_type = type(thr_obj.on_done)
4525+
4526+ thr_obj._run = thr_obj_fn
4527+ if on_done is not None:
4528+ thr_obj.on_done = on_done_type(on_done, thr_obj, CobblerThread)
4529+ thr_obj.start()
4530+ return event_id
4531+
4532+ def _set_task_state(self,thread_obj,event_id,new_state):
4533+ event_id = str(event_id)
4534+ if self.events.has_key(event_id):
4535+ self.events[event_id][2] = new_state
4536+ self.events[event_id][3] = [] # clear the list of who has read it
4537+ if thread_obj is not None:
4538+ if new_state == EVENT_COMPLETE:
4539+ thread_obj.logger.info("### TASK COMPLETE ###")
4540+ if new_state == EVENT_FAILED:
4541+ thread_obj.logger.error("### TASK FAILED ###")
4542+
4543+ def get_task_status(self, event_id):
4544+ event_id = str(event_id)
4545+ if self.events.has_key(event_id):
4546+ return self.events[event_id]
4547+ else:
4548+ raise CX("no event with that id")
4549+
4550+ def __sorter(self,a,b):
4551+ """
4552+ Helper function to sort two datastructure representations of
4553+ cobbler objects by name.
4554+ """
4555+ return cmp(a["name"],b["name"])
4556+
4557+ def last_modified_time(self, token=None):
4558+ """
4559+ Return the time of the last modification to any object.
4560+ Used to verify from a calling application that no cobbler
4561+ objects have changed since last check.
4562+ """
4563+ return self.api.last_modified_time()
4564+
4565+ def update(self, token=None):
4566+ """
4567+ Deprecated method. Now does nothing.
4568+ """
4569+ return True
4570+
4571+ def ping(self):
4572+ """
4573+ Deprecated method. Now does nothing.
4574+ """
4575+ return True
4576+
4577+ def get_user_from_token(self,token):
4578+ """
4579+ Given a token returned from login, return the username
4580+ that logged in with it.
4581+ """
4582+ if not self.token_cache.has_key(token):
4583+ raise CX("invalid token: %s" % token)
4584+ else:
4585+ return self.token_cache[token][1]
4586+
4587+ def _log(self,msg,user=None,token=None,name=None,object_id=None,attribute=None,debug=False,error=False):
4588+ """
4589+ Helper function to write data to the log file from the XMLRPC remote implementation.
4590+ Takes various optional parameters that should be supplied when known.
4591+ """
4592+
4593+ # add the user editing the object, if supplied
4594+ m_user = "?"
4595+ if user is not None:
4596+ m_user = user
4597+ if token is not None:
4598+ try:
4599+ m_user = self.get_user_from_token(token)
4600+ except:
4601+ # invalid or expired token?
4602+ m_user = "???"
4603+ msg = "REMOTE %s; user(%s)" % (msg, m_user)
4604+
4605+ if name is not None:
4606+ msg = "%s; name(%s)" % (msg, name)
4607+
4608+ if object_id is not None:
4609+ msg = "%s; object_id(%s)" % (msg, object_id)
4610+
4611+ # add any attributes being modified, if any
4612+ if attribute:
4613+ msg = "%s; attribute(%s)" % (msg, attribute)
4614+
4615+ # log to the correct logger
4616+ if error:
4617+ logger = self.logger.error
4618+ elif debug:
4619+ logger = self.logger.debug
4620+ else:
4621+ logger = self.logger.info
4622+ logger(msg)
4623+
4624+ def __sort(self,data,sort_field=None):
4625+ """
4626+ Helper function used by the various find/search functions to return
4627+ object representations in order.
4628+ """
4629+ sort_fields=["name"]
4630+ sort_rev=False
4631+ if sort_field is not None:
4632+ if sort_field.startswith("!"):
4633+ sort_field=sort_field[1:]
4634+ sort_rev=True
4635+ sort_fields.insert(0,sort_field)
4636+ sortdata=[(x.sort_key(sort_fields),x) for x in data]
4637+ if sort_rev:
4638+ sortdata.sort(lambda a,b:cmp(b,a))
4639+ else:
4640+ sortdata.sort()
4641+ return [x for (key, x) in sortdata]
4642+
4643+ def __paginate(self,data,page=None,items_per_page=None,token=None):
4644+ """
4645+ Helper function to support returning parts of a selection, for
4646+ example, for use in a web app where only a part of the results
4647+ are to be presented on each screen.
4648+ """
4649+ default_page = 1
4650+ default_items_per_page = 25
4651+
4652+ try:
4653+ page = int(page)
4654+ if page < 1:
4655+ page = default_page
4656+ except:
4657+ page = default_page
4658+ try:
4659+ items_per_page = int(items_per_page)
4660+ if items_per_page <= 0:
4661+ items_per_page = default_items_per_page
4662+ except:
4663+ items_per_page = default_items_per_page
4664+
4665+ num_items = len(data)
4666+ num_pages = ((num_items-1)/items_per_page)+1
4667+ if num_pages==0:
4668+ num_pages=1
4669+ if page>num_pages:
4670+ page=num_pages
4671+ start_item = (items_per_page * (page-1))
4672+ end_item = start_item + items_per_page
4673+ if start_item > num_items:
4674+ start_item = num_items - 1
4675+ if end_item > num_items:
4676+ end_item = num_items
4677+ data = data[start_item:end_item]
4678+
4679+ if page > 1:
4680+ prev_page = page - 1
4681+ else:
4682+ prev_page = None
4683+ if page < num_pages:
4684+ next_page = page + 1
4685+ else:
4686+ next_page = None
4687+
4688+ return (data,{
4689+ 'page' : page,
4690+ 'prev_page' : prev_page,
4691+ 'next_page' : next_page,
4692+ 'pages' : range(1,num_pages+1),
4693+ 'num_pages' : num_pages,
4694+ 'num_items' : num_items,
4695+ 'start_item' : start_item,
4696+ 'end_item' : end_item,
4697+ 'items_per_page' : items_per_page,
4698+ 'items_per_page_list' : [10,20,50,100,200,500],
4699+ })
4700+
4701+ def __get_object(self, object_id):
4702+ """
4703+ Helper function. Given an object id, return the actual object.
4704+ """
4705+ if object_id.startswith("___NEW___"):
4706+ return self.object_cache[object_id][1]
4707+ (otype, oname) = object_id.split("::",1)
4708+ return self.api.get_item(otype,oname)
4709+
4710+ def get_item(self, what, name, flatten=False):
4711+ """
4712+ Returns a hash describing a given object.
4713+ what -- "distro", "profile", "system", "image", "repo", etc
4714+ name -- the object name to retrieve
4715+ flatten -- reduce hashes to string representations (True/False)
4716+ """
4717+ self._log("get_item(%s,%s)"%(what,name))
4718+ item=self.api.get_item(what,name)
4719+ if item is not None:
4720+ item=item.to_datastruct()
4721+ if flatten:
4722+ item = utils.flatten(item)
4723+ return self.xmlrpc_hacks(item)
4724+
4725+ def get_distro(self,name,flatten=False,token=None,**rest):
4726+ return self.get_item("distro",name,flatten=flatten)
4727+ def get_profile(self,name,flatten=False,token=None,**rest):
4728+ return self.get_item("profile",name,flatten=flatten)
4729+ def get_system(self,name,flatten=False,token=None,**rest):
4730+ return self.get_item("system",name,flatten=flatten)
4731+ def get_repo(self,name,flatten=False,token=None,**rest):
4732+ return self.get_item("repo",name,flatten=flatten)
4733+ def get_image(self,name,flatten=False,token=None,**rest):
4734+ return self.get_item("image",name,flatten=flatten)
4735+ def get_mgmtclass(self,name,flatten=False,token=None,**rest):
4736+ return self.get_mgmtclass("mgmtclass",name,flatten=flatten)
4737+ def get_package(self,name,flatten=False,token=None,**rest):
4738+ return self.get_package("package",name,flatten=flatten)
4739+ def get_file(self,name,flatten=False,token=None,**rest):
4740+ return self.get_file("file",name,flatten=flatten)
4741+
4742+ def get_items(self, what):
4743+ """
4744+ Returns a list of hashes.
4745+ what is the name of a cobbler object type, as described for get_item.
4746+ Individual list elements are the same for get_item.
4747+ """
4748+ # FIXME: is the xmlrpc_hacks method still required ?
4749+ item = [x.to_datastruct() for x in self.api.get_items(what)]
4750+ return self.xmlrpc_hacks(item)
4751+
4752+ def get_item_names(self, what):
4753+ """
4754+ Returns a list of object names (keys) for the given object type.
4755+ This is just like get_items, but transmits less data.
4756+ """
4757+ return [x.name for x in self.api.get_items(what)]
4758+
4759+ def get_distros(self,page=None,results_per_page=None,token=None,**rest):
4760+ return self.get_items("distro")
4761+ def get_profiles(self,page=None,results_per_page=None,token=None,**rest):
4762+ return self.get_items("profile")
4763+ def get_systems(self,page=None,results_per_page=None,token=None,**rest):
4764+ return self.get_items("system")
4765+ def get_repos(self,page=None,results_per_page=None,token=None,**rest):
4766+ return self.get_items("repo")
4767+ def get_images(self,page=None,results_per_page=None,token=None,**rest):
4768+ return self.get_items("image")
4769+ def get_mgmtclasses(self,page=None,results_per_page=None,token=None,**rest):
4770+ return self.get_items("mgmtclass")
4771+ def get_packages(self,page=None,results_per_page=None,token=None,**rest):
4772+ return self.get_items("package")
4773+ def get_files(self,page=None,results_per_page=None,token=None,**rest):
4774+ return self.get_items("file")
4775+
4776+ def find_items(self, what, criteria=None,sort_field=None,expand=True):
4777+ """
4778+ Returns a list of hashes.
4779+ Works like get_items but also accepts criteria as a hash to search on.
4780+ Example: { "name" : "*.example.org" }
4781+ Wildcards work as described by 'pydoc fnmatch'.
4782+ """
4783+ self._log("find_items(%s); criteria(%s); sort(%s)" % (what,criteria,sort_field))
4784+ items = self.api.find_items(what,criteria=criteria)
4785+ items = self.__sort(items,sort_field)
4786+ if not expand:
4787+ items = [x.name for x in items]
4788+ else:
4789+ items = [x.to_datastruct() for x in items]
4790+ return self.xmlrpc_hacks(items)
4791+
4792+ def find_distro(self,criteria={},expand=False,token=None,**rest):
4793+ return self.find_items("distro",criteria,expand=expand)
4794+ def find_profile(self,criteria={},expand=False,token=None,**rest):
4795+ return self.find_items("profile",criteria,expand=expand)
4796+ def find_system(self,criteria={},expand=False,token=None,**rest):
4797+ return self.find_items("system",criteria,expand=expand)
4798+ def find_repo(self,criteria={},expand=False,token=None,**rest):
4799+ return self.find_items("repo",criteria,expand=expand)
4800+ def find_image(self,criteria={},expand=False,token=None,**rest):
4801+ return self.find_items("image",criteria,expand=expand)
4802+ def find_mgmtclass(self,criteria={},expand=False,token=None,**rest):
4803+ return self.find_items("mgmtclass",criteria,expand=expand)
4804+ def find_package(self,criteria={},expand=False,token=None,**rest):
4805+ return self.find_items("package",criteria,expand=expand)
4806+ def find_file(self,criteria={},expand=False,token=None,**rest):
4807+ return self.find_items("file",criteria,expand=expand)
4808+
4809+ def find_items_paged(self, what, criteria=None, sort_field=None, page=None, items_per_page=None, token=None):
4810+ """
4811+ Returns a list of hashes as with find_items but additionally supports
4812+ returning just a portion of the total list, for instance in supporting
4813+ a web app that wants to show a limited amount of items per page.
4814+ """
4815+ # FIXME: make token required for all logging calls
4816+ self._log("find_items_paged(%s); criteria(%s); sort(%s)" % (what,criteria,sort_field), token=token)
4817+ items = self.api.find_items(what,criteria=criteria)
4818+ items = self.__sort(items,sort_field)
4819+ (items,pageinfo) = self.__paginate(items,page,items_per_page)
4820+ items = [x.to_datastruct() for x in items]
4821+ return self.xmlrpc_hacks({
4822+ 'items' : items,
4823+ 'pageinfo' : pageinfo
4824+ })
4825+
4826+ def has_item(self,what,name,token=None):
4827+ """
4828+ Returns True if a given collection has an item with a given name,
4829+ otherwise returns False.
4830+ """
4831+ self._log("has_item(%s)"%what,token=token,name=name)
4832+ found = self.api.get_item(what,name)
4833+ if found is None:
4834+ return False
4835+ else:
4836+ return True
4837+
4838+ def get_item_handle(self,what,name,token=None):
4839+ """
4840+ Given the name of an object (or other search parameters), return a
4841+ reference (object id) that can be used with modify_* functions or save_* functions
4842+ to manipulate that object.
4843+ """
4844+ found = self.api.get_item(what,name)
4845+ if found is None:
4846+ raise CX("internal error, unknown %s name %s" % (what,name))
4847+ return "%s::%s" % (what,found.name)
4848+
4849+ def get_distro_handle(self,name,token):
4850+ return self.get_item_handle("distro",name,token)
4851+ def get_profile_handle(self,name,token):
4852+ return self.get_item_handle("profile",name,token)
4853+ def get_system_handle(self,name,token):
4854+ return self.get_item_handle("system",name,token)
4855+ def get_repo_handle(self,name,token):
4856+ return self.get_item_handle("repo",name,token)
4857+ def get_image_handle(self,name,token):
4858+ return self.get_item_handle("image",name,token)
4859+ def get_mgmtclass_handle(self,name,token):
4860+ return self.get_item_handle("mgmtclass",name,token)
4861+ def get_package_handle(self,name,token):
4862+ return self.get_item_handle("package",name,token)
4863+ def get_file_handle(self,name,token):
4864+ return self.get_item_handle("file",name,token)
4865+
4866+ def remove_item(self,what,name,token,recursive=True):
4867+ """
4868+ Deletes an item from a collection.
4869+ Note that this requires the name of the distro, not an item handle.
4870+ """
4871+ self._log("remove_item (%s, recursive=%s)" % (what,recursive),name=name,token=token)
4872+ self.check_access(token, "remove_item", name)
4873+ return self.api.remove_item(what,name,delete=True,with_triggers=True,recursive=recursive)
4874+
4875+ def remove_distro(self,name,token,recursive=1):
4876+ return self.remove_item("distro",name,token,recursive)
4877+ def remove_profile(self,name,token,recursive=1):
4878+ return self.remove_item("profile",name,token,recursive)
4879+ def remove_system(self,name,token,recursive=1):
4880+ return self.remove_item("system",name,token,recursive)
4881+ def remove_repo(self,name,token,recursive=1):
4882+ return self.remove_item("repo",name,token,recursive)
4883+ def remove_image(self,name,token,recursive=1):
4884+ return self.remove_item("image",name,token,recursive)
4885+ def remove_mgmtclass(self,name,token,recursive=1):
4886+ return self.remove_item("mgmtclass",name,token,recursive)
4887+ def remove_package(self,name,token,recursive=1):
4888+ return self.remove_item("package",name,token,recursive)
4889+ def remove_file(self,name,token,recursive=1):
4890+ return self.remove_item("file",name,token,recursive)
4891+
4892+ def copy_item(self,what,object_id,newname,token=None):
4893+ """
4894+ Creates a new object that matches an existing object, as specified by an id.
4895+ """
4896+ self._log("copy_item(%s)" % what,object_id=object_id,token=token)
4897+ self.check_access(token,"copy_%s" % what)
4898+ obj = self.__get_object(object_id)
4899+ return self.api.copy_item(what,obj,newname)
4900+
4901+ def copy_distro(self,object_id,newname,token=None):
4902+ return self.copy_item("distro",object_id,newname,token)
4903+ def copy_profile(self,object_id,newname,token=None):
4904+ return self.copy_item("profile",object_id,newname,token)
4905+ def copy_system(self,object_id,newname,token=None):
4906+ return self.copy_item("system",object_id,newname,token)
4907+ def copy_repo(self,object_id,newname,token=None):
4908+ return self.copy_item("repo",object_id,newname,token)
4909+ def copy_image(self,object_id,newname,token=None):
4910+ return self.copy_item("image",object_id,newname,token)
4911+ def copy_mgmtclass(self,object_id,newname,token=None):
4912+ return self.copy_item("mgmtclass",object_id,newname,token)
4913+ def copy_package(self,object_id,newname,token=None):
4914+ return self.copy_item("package",object_id,newname,token)
4915+ def copy_file(self,object_id,newname,token=None):
4916+ return self.copy_item("file",object_id,newname,token)
4917+
4918+ def rename_item(self,what,object_id,newname,token=None):
4919+ """
4920+ Renames an object specified by object_id to a new name.
4921+ """
4922+ self._log("rename_item(%s)" % what,object_id=object_id,token=token)
4923+ obj = self.__get_object(object_id)
4924+ return self.api.rename_item(what,obj,newname)
4925+
4926+ def rename_distro(self,object_id,newname,token=None):
4927+ return self.rename_item("distro",object_id,newname,token)
4928+ def rename_profile(self,object_id,newname,token=None):
4929+ return self.rename_item("profile",object_id,newname,token)
4930+ def rename_system(self,object_id,newname,token=None):
4931+ return self.rename_item("system",object_id,newname,token)
4932+ def rename_repo(self,object_id,newname,token=None):
4933+ return self.rename_item("repo",object_id,newname,token)
4934+ def rename_image(self,object_id,newname,token=None):
4935+ return self.rename_item("image",object_id,newname,token)
4936+ def rename_mgmtclass(self,object_id,newname,token=None):
4937+ return self.rename_item("mgmtclass",object_id,newname,token)
4938+ def rename_package(self,object_id,newname,token=None):
4939+ return self.rename_item("package",object_id,newname,token)
4940+ def rename_file(self,object_id,newname,token=None):
4941+ return self.rename_item("file",object_id,newname,token)
4942+
4943+ def new_item(self,what,token,is_subobject=False):
4944+ """
4945+ Creates a new (unconfigured) object, returning an object
4946+ handle that can be used with modify_* methods and then finally
4947+ save_* methods. The handle only exists in memory until saved.
4948+ "what" specifies the type of object:
4949+ distro, profile, system, repo, or image
4950+ """
4951+ self._log("new_item(%s)"%what,token=token)
4952+ self.check_access(token,"new_%s"%what)
4953+ if what == "distro":
4954+ d = item_distro.Distro(self.api._config,is_subobject=is_subobject)
4955+ elif what == "profile":
4956+ d = item_profile.Profile(self.api._config,is_subobject=is_subobject)
4957+ elif what == "system":
4958+ d = item_system.System(self.api._config,is_subobject=is_subobject)
4959+ elif what == "repo":
4960+ d = item_repo.Repo(self.api._config,is_subobject=is_subobject)
4961+ elif what == "image":
4962+ d = item_image.Image(self.api._config,is_subobject=is_subobject)
4963+ elif what == "mgmtclass":
4964+ d = item_mgmtclass.Mgmtclass(self.api._config,is_subobject=is_subobject)
4965+ elif what == "package":
4966+ d = item_package.Package(self.api._config,is_subobject=is_subobject)
4967+ elif what == "file":
4968+ d = item_file.File(self.api._config,is_subobject=is_subobject)
4969+ else:
4970+ raise CX("internal error, collection name is %s" % what)
4971+ key = "___NEW___%s::%s" % (what,self.__get_random(25))
4972+ self.object_cache[key] = (time.time(), d)
4973+ return key
4974+
4975+ def new_distro(self,token):
4976+ return self.new_item("distro",token)
4977+ def new_profile(self,token):
4978+ return self.new_item("profile",token)
4979+ def new_subprofile(self,token):
4980+ return self.new_item("profile",token,is_subobject=True)
4981+ def new_system(self,token):
4982+ return self.new_item("system",token)
4983+ def new_repo(self,token):
4984+ return self.new_item("repo",token)
4985+ def new_image(self,token):
4986+ return self.new_item("image",token)
4987+ def new_mgmtclass(self,token):
4988+ return self.new_item("mgmtclass",token)
4989+ def new_package(self,token):
4990+ return self.new_item("package",token)
4991+ def new_file(self,token):
4992+ return self.new_item("file",token)
4993+
4994+ def modify_item(self,what,object_id,attribute,arg,token):
4995+ """
4996+ Adjusts the value of a given field, specified by 'what' on a given object id.
4997+ Allows modification of certain attributes on newly created or
4998+ existing distro object handle.
4999+ """
5000+ self._log("modify_item(%s)" % what,object_id=object_id,attribute=attribute,token=token)
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: