Merge lp:~racb/ubuntu/oneiric/cobbler/858878_858883 into lp:ubuntu/oneiric/cobbler
- Oneiric (11.10)
- 858878_858883
- Merge into oneiric
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 |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dave Walker | Pending | ||
Review via email: mp+81996@code.launchpad.net |
Commit message
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">⇒ {{ 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 }} |
590 | + {% else %} |
591 | + <input type="radio" name="{{ item.dname }}" id="{{ item.dname }}" value="{{ choice }}">{{ choice }} |
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=">>" id="add_{{ item.dname }}" onclick="$('#from_{{ item.dname }} option:selected').remove().appendTo('#{{ item.dname }}');" /> |
627 | + <input class="button" type="button" value="<<" 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 | + ↓ |
810 | + {% endifequal %} |
811 | + {% ifequal value.1 "desc" %} |
812 | + ↑ |
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">⇐</span></a> |
1100 | + {% else %} |
1101 | + <span class="lpointers">⇐</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">⇒</span></a> |
1108 | + {% else %} |
1109 | + <span class="rpointers">⇒</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.