Merge lp:~james-page/ubuntu/natty/cobbler/fix-764391 into lp:ubuntu/natty/cobbler

Proposed by James Page
Status: Merged
Merge reported by: Sebastien Bacher
Merged at revision: not available
Proposed branch: lp:~james-page/ubuntu/natty/cobbler/fix-764391
Merge into: lp:ubuntu/natty/cobbler
Diff against target: 1187 lines (+1035/-24)
13 files modified
.pc/40_ubuntu_bind9_management.patch/cobbler/action_check.py (+482/-0)
.pc/40_ubuntu_bind9_management.patch/cobbler/modules/manage_bind.py (+332/-0)
.pc/40_ubuntu_bind9_management.patch/cobbler/modules/sync_post_restart_services.py (+66/-0)
.pc/40_ubuntu_bind9_management.patch/templates/etc/named.template (+31/-0)
.pc/applied-patches (+1/-0)
cobbler/action_check.py (+1/-1)
cobbler/modules/manage_bind.py (+3/-3)
cobbler/modules/sync_post_restart_services.py (+1/-1)
debian/changelog (+11/-0)
debian/control (+1/-0)
debian/patches/40_ubuntu_bind9_management.patch (+103/-0)
debian/patches/series (+1/-0)
templates/etc/named.template (+2/-19)
To merge this branch: bzr merge lp:~james-page/ubuntu/natty/cobbler/fix-764391
Reviewer Review Type Date Requested Status
Dave Walker (community) Approve
Review via email: mp+58112@code.launchpad.net

Description of the change

Fix up management of bind9 on Ubuntu as paths/daemon names where Redhat-esq.

To post a comment you must log in.
Revision history for this message
Dave Walker (davewalker) wrote :

lgtm, uploaded to natty queue.

Thanks.

Revision history for this message
Dave Walker (davewalker) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory '.pc/40_ubuntu_bind9_management.patch'
=== added directory '.pc/40_ubuntu_bind9_management.patch/cobbler'
=== added file '.pc/40_ubuntu_bind9_management.patch/cobbler/action_check.py'
--- .pc/40_ubuntu_bind9_management.patch/cobbler/action_check.py 1970-01-01 00:00:00 +0000
+++ .pc/40_ubuntu_bind9_management.patch/cobbler/action_check.py 2011-04-18 12:44:31 +0000
@@ -0,0 +1,482 @@
1"""
2Validates whether the system is reasonably well configured for
3serving up content. This is the code behind 'cobbler check'.
4
5Copyright 2006-2009, Red Hat, Inc
6Michael DeHaan <mdehaan@redhat.com>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
2102110-1301 USA
22"""
23
24import os
25import re
26import action_sync
27import utils
28import glob
29from utils import _
30import clogger
31
32class BootCheck:
33
34 def __init__(self,config,logger=None):
35 """
36 Constructor
37 """
38 self.config = config
39 self.settings = config.settings()
40 if logger is None:
41 logger = clogger.Logger()
42 self.logger = logger
43
44
45 def run(self):
46 """
47 Returns None if there are no errors, otherwise returns a list
48 of things to correct prior to running application 'for real'.
49 (The CLI usage is "cobbler check" before "cobbler sync")
50 """
51 status = []
52 self.checked_dist = utils.check_dist()
53 self.check_name(status)
54 self.check_selinux(status)
55 if self.settings.manage_dhcp:
56 mode = self.config.api.get_sync().dhcp.what()
57 if mode == "isc":
58 self.check_dhcpd_bin(status)
59 self.check_dhcpd_conf(status)
60 self.check_service(status,"dhcpd")
61 elif mode == "dnsmasq":
62 self.check_dnsmasq_bin(status)
63 self.check_service(status,"dnsmasq")
64
65 if self.settings.manage_dns:
66 mode = self.config.api.get_sync().dns.what()
67 if mode == "bind":
68 self.check_bind_bin(status)
69 self.check_service(status,"named")
70 elif mode == "dnsmasq" and not self.settings.manage_dhcp:
71 self.check_dnsmasq_bin(status)
72 self.check_service(status,"dnsmasq")
73
74 mode = self.config.api.get_sync().tftpd.what()
75 if mode == "in_tftpd":
76 self.check_tftpd_bin(status)
77 self.check_tftpd_dir(status)
78 self.check_tftpd_conf(status)
79 elif mode == "tftpd_py":
80 self.check_ctftpd_bin(status)
81 self.check_ctftpd_dir(status)
82 self.check_ctftpd_conf(status)
83
84 self.check_service(status, "cobblerd")
85
86 self.check_bootloaders(status)
87 self.check_rsync_conf(status)
88 self.check_httpd(status)
89 self.check_iptables(status)
90 self.check_yum(status)
91 self.check_debmirror(status)
92 self.check_for_ksvalidator(status)
93 self.check_for_default_password(status)
94 self.check_for_unreferenced_repos(status)
95 self.check_for_unsynced_repos(status)
96 self.check_for_cman(status)
97
98 return status
99
100 def check_for_ksvalidator(self, status):
101 if self.checked_dist in ["debian", "ubuntu"]:
102 return
103
104 if not os.path.exists("/usr/bin/ksvalidator"):
105 status.append("ksvalidator was not found, install pykickstart")
106
107 return True
108
109 def check_for_cman(self, status):
110 # not doing rpm -q here to be cross-distro friendly
111 if not os.path.exists("/sbin/fence_ilo") and not os.path.exists("/usr/sbin/fence_ilo"):
112 status.append("fencing tools were not found, and are required to use the (optional) power management features. install cman or fence-agents to use them")
113 return True
114
115 def check_service(self, status, which, notes=""):
116 if notes != "":
117 notes = " (NOTE: %s)" % notes
118 rc = 0
119 if self.checked_dist == "redhat" or self.checked_dist == "suse":
120 if os.path.exists("/etc/rc.d/init.d/%s" % which):
121 rc = utils.subprocess_call(self.logger,"/sbin/service %s status > /dev/null 2>/dev/null" % which, shell=True)
122 if rc != 0:
123 status.append(_("service %s is not running%s") % (which,notes))
124 return False
125 elif self.checked_dist in ["debian", "ubuntu"]:
126 # we still use /etc/init.d
127 if os.path.exists("/etc/init.d/%s" % which):
128 rc = utils.subprocess_call(self.logger,"/etc/init.d/%s status /dev/null 2>/dev/null" % which, shell=True)
129 if rc != 0:
130 status.append(_("service %s is not running%s") % which,notes)
131 return False
132 elif self.checked_dist == "ubuntu":
133 if os.path.exists("/etc/init/%s.conf" % which):
134 rc = utils.subprocess_call(self.logger,"status %s > /dev/null 2>&1" % which, shell=True)
135 if rc != 0:
136 status.append(_("service %s is not running%s") % (which,notes))
137 else:
138 status.append(_("Unknown distribution type, cannot check for running service %s" % which))
139 return False
140 return True
141
142 def check_iptables(self, status):
143 if os.path.exists("/etc/rc.d/init.d/iptables"):
144 rc = utils.subprocess_call(self.logger,"/sbin/service iptables status >/dev/null 2>/dev/null", shell=True)
145 if rc == 0:
146 status.append(_("since iptables may be running, ensure 69, 80, and %(xmlrpc)s are unblocked") % { "xmlrpc" : self.settings.xmlrpc_port })
147
148 def check_yum(self,status):
149 if self.checked_dist in ["debian", "ubuntu"]:
150 return
151
152 if not os.path.exists("/usr/bin/createrepo"):
153 status.append(_("createrepo package is not installed, needed for cobbler import and cobbler reposync, install createrepo?"))
154 if not os.path.exists("/usr/bin/reposync"):
155 status.append(_("reposync is not installed, need for cobbler reposync, install/upgrade yum-utils?"))
156 if not os.path.exists("/usr/bin/yumdownloader"):
157 status.append(_("yumdownloader is not installed, needed for cobbler repo add with --rpm-list parameter, install/upgrade yum-utils?"))
158 if self.settings.reposync_flags.find("-l"):
159 if self.checked_dist == "redhat" or self.checked_dist == "suse":
160 yum_utils_ver = utils.subprocess_get(self.logger,"/usr/bin/rpmquery --queryformat=%{VERSION} yum-utils", shell=True)
161 if yum_utils_ver < "1.1.17":
162 status.append(_("yum-utils need to be at least version 1.1.17 for reposync -l, current version is %s") % yum_utils_ver )
163
164 def check_debmirror(self,status):
165 if not os.path.exists("/usr/bin/debmirror"):
166 status.append(_("debmirror package is not installed, it will be required to manage debian deployments and repositories"))
167 if os.path.exists("/etc/debmirror.conf"):
168 f = open("/etc/debmirror.conf")
169 re_dists = re.compile(r'@dists=')
170 re_arches = re.compile(r'@arches=')
171 for line in f.readlines():
172 if re_dists.search(line) and not line.strip().startswith("#"):
173 status.append(_("comment 'dists' on /etc/debmirror.conf for proper debian support"))
174 if re_arches.search(line) and not line.strip().startswith("#"):
175 status.append(_("comment 'arches' on /etc/debmirror.conf for proper debian support"))
176
177
178 def check_name(self,status):
179 """
180 If the server name in the config file is still set to localhost
181 kickstarts run from koan will not have proper kernel line
182 parameters.
183 """
184 if self.settings.server == "127.0.0.1":
185 status.append(_("The 'server' field in /etc/cobbler/settings must be set to something other than localhost, or kickstarting features will not work. This should be a resolvable hostname or IP for the boot server as reachable by all machines that will use it."))
186 if self.settings.next_server == "127.0.0.1":
187 status.append(_("For PXE to be functional, the 'next_server' field in /etc/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network."))
188
189 def check_selinux(self,status):
190 """
191 Suggests various SELinux rules changes to run Cobbler happily with
192 SELinux in enforcing mode. FIXME: this method could use some
193 refactoring in the future.
194 """
195 if self.checked_dist in ["debian", "ubuntu"]:
196 return
197
198 enabled = self.config.api.is_selinux_enabled()
199 if enabled:
200 data2 = utils.subprocess_get(self.logger,"/usr/sbin/getsebool -a",shell=True)
201 for line in data2.split("\n"):
202 if line.find("httpd_can_network_connect ") != -1:
203 if line.find("off") != -1:
204 status.append(_("Must enable a selinux boolean to enable vital web services components, run: setsebool -P httpd_can_network_connect true"))
205 if line.find("rsync_disable_trans ") != -1:
206 if line.find("on") != -1:
207 status.append(_("Must enable the cobbler import and replicate commands, run: setsebool -P rsync_disable_trans=1"))
208
209 data3 = utils.subprocess_get(self.logger,"/usr/sbin/semanage fcontext -l | grep public_content_t",shell=True)
210
211 rule1 = False
212 rule2 = False
213 rule3 = False
214 selinux_msg = "/usr/sbin/semanage fcontext -a -t public_content_t \"%s\""
215 for line in data3.split("\n"):
216 if line.startswith("/tftpboot/.*"):
217 rule1 = True
218 if line.startswith("/var/lib/tftpboot/.*"):
219 rule2 = True
220 if line.startswith("/var/www/cobbler/images/.*"):
221 rule3 = True
222
223 rules = []
224 if os.path.exists("/tftpboot") and not rule1:
225 rules.append(selinux_msg % "/tftpboot/.*")
226 else:
227 if not rule2:
228 rules.append(selinux_msg % "/var/lib/tftpboot/.*")
229 if not rule3:
230 rules.append(selinux_msg % "/var/www/cobbler/images/.*")
231 if len(rules) > 0:
232 status.append("you need to set some SELinux content rules to ensure cobbler serves content correctly in your SELinux environment, run the following: %s" % " && ".join(rules))
233
234 # now check to see that the Django sessions path is accessible
235 # by Apache
236
237 data4 = utils.subprocess_get(self.logger,"/usr/sbin/semanage fcontext -l | grep httpd_sys_content_rw_t",shell=True)
238 selinux_msg = "you need to set some SELinux rules if you want to use cobbler-web (an optional package), run the following: /usr/sbin/semanage fcontext -a -t httpd_sys_content_rw_t \"%s\""
239 rule4 = False
240 for line in data4.split("\n"):
241 if line.startswith("/var/lib/cobbler/webui_sessions/.*"):
242 rule4 = True
243 if not rule4:
244 status.append(selinux_msg % "/var/lib/cobbler/webui_sessions/.*")
245
246
247 def check_for_default_password(self,status):
248 default_pass = self.settings.default_password_crypted
249 if default_pass == "$1$mF86/UHC$WvcIcX2t6crBz2onWxyac.":
250 status.append(_("The default password used by the sample templates for newly installed machines (default_password_crypted in /etc/cobbler/settings) is still set to 'cobbler' and should be changed, try: \"openssl passwd -1 -salt 'random-phrase-here' 'your-password-here'\" to generate new one"))
251
252
253 def check_for_unreferenced_repos(self,status):
254 repos = []
255 referenced = []
256 not_found = []
257 for r in self.config.api.repos():
258 repos.append(r.name)
259 for p in self.config.api.profiles():
260 my_repos = p.repos
261 if my_repos != "<<inherit>>":
262 referenced.extend(my_repos)
263 for r in referenced:
264 if r not in repos and r != "<<inherit>>":
265 not_found.append(r)
266 if len(not_found) > 0:
267 status.append(_("One or more repos referenced by profile objects is no longer defined in cobbler: %s") % ", ".join(not_found))
268
269 def check_for_unsynced_repos(self,status):
270 need_sync = []
271 for r in self.config.repos():
272 if r.mirror_locally == 1:
273 lookfor = os.path.join(self.settings.webdir, "repo_mirror", r.name)
274 if not os.path.exists(lookfor):
275 need_sync.append(r.name)
276 if len(need_sync) > 0:
277 status.append(_("One or more repos need to be processed by cobbler reposync for the first time before kickstarting against them: %s") % ", ".join(need_sync))
278
279
280 def check_httpd(self,status):
281 """
282 Check if Apache is installed.
283 """
284 if self.checked_dist in [ "suse", "redhat" ]:
285 rc = utils.subprocess_get(self.logger,"httpd -v")
286 else:
287 rc = utils.subprocess_get(self.logger,"apache2 -v")
288 if rc.find("Server") == -1:
289 status.append("Apache (httpd) is not installed and/or in path")
290
291
292 def check_dhcpd_bin(self,status):
293 """
294 Check if dhcpd is installed
295 """
296 if not os.path.exists("/usr/sbin/dhcpd"):
297 status.append("dhcpd is not installed")
298
299 def check_dnsmasq_bin(self,status):
300 """
301 Check if dnsmasq is installed
302 """
303 rc = utils.subprocess_get(self.logger,"dnsmasq --help")
304 if rc.find("Valid options") == -1:
305 status.append("dnsmasq is not installed and/or in path")
306
307 def check_bind_bin(self,status):
308 """
309 Check if bind is installed.
310 """
311 rc = utils.subprocess_get(self.logger,"named -v")
312 # it should return something like "BIND 9.6.1-P1-RedHat-9.6.1-6.P1.fc11"
313 if rc.find("BIND") == -1:
314 status.append("named is not installed and/or in path")
315
316 def check_bootloaders(self,status):
317 """
318 Check if network bootloaders are installed
319 """
320 # FIXME: move zpxe.rexx to loaders
321
322 bootloaders = {
323 "elilo" : [ "/var/lib/cobbler/loaders/elilo*.efi" ],
324 "menu.c32" : [ "/usr/share/syslinux/menu.c32",
325 "/usr/lib/syslinux/menu.c32",
326 "/var/lib/cobbler/loaders/menu.c32" ],
327 "yaboot" : [ "/var/lib/cobbler/loaders/yaboot*" ],
328 "pxelinux.0" : [ "/usr/share/syslinux/pxelinux.0",
329 "/usr/lib/syslinux/pxelinux.0",
330 "/var/lib/cobbler/loaders/pxelinux.0" ],
331 "efi" : [ "/var/lib/cobbler/loaders/grub-x86.efi",
332 "/var/lib/cobbler/loaders/grub-x86_64.efi" ],
333 }
334
335 # look for bootloaders at the glob locations above
336 found_bootloaders = []
337 items = bootloaders.keys()
338 for loader_name in items:
339 patterns = bootloaders[loader_name]
340 for pattern in patterns:
341 matches = glob.glob(pattern)
342 if len(matches) > 0:
343 found_bootloaders.append(loader_name)
344 not_found = []
345
346 # invert the list of what we've found so we can report on what we haven't found
347 for loader_name in items:
348 if loader_name not in found_bootloaders:
349 not_found.append(loader_name)
350
351 if len(not_found) > 0:
352 status.append("some network boot-loaders are missing from /var/lib/cobbler/loaders, you may run 'cobbler get-loaders' to download them, or, if you only want to handle x86/x86_64 netbooting, you may ensure that you have installed a *recent* version of the syslinux package installed and can ignore this message entirely. Files in this directory, should you want to support all architectures, should include pxelinux.0, menu.c32, elilo.efi, and yaboot. The 'cobbler get-loaders' command is the easiest way to resolve these requirements.")
353
354 def check_tftpd_bin(self,status):
355 """
356 Check if tftpd is installed
357 """
358 if self.checked_dist in ["debian", "ubuntu"]:
359 return
360
361 if not os.path.exists("/etc/xinetd.d/tftp"):
362 status.append("missing /etc/xinetd.d/tftp, install tftp-server?")
363
364 def check_tftpd_dir(self,status):
365 """
366 Check if cobbler.conf's tftpboot directory exists
367 """
368 if self.checked_dist in ["debian", "ubuntu"]:
369 return
370
371 bootloc = utils.tftpboot_location()
372 if not os.path.exists(bootloc):
373 status.append(_("please create directory: %(dirname)s") % { "dirname" : bootloc })
374
375
376 def check_tftpd_conf(self,status):
377 """
378 Check that configured tftpd boot directory matches with actual
379 Check that tftpd is enabled to autostart
380 """
381 if self.checked_dist in ["debian", "ubuntu"]:
382 return
383
384 if os.path.exists("/etc/xinetd.d/tftp"):
385 f = open("/etc/xinetd.d/tftp")
386 re_disable = re.compile(r'disable.*=.*yes')
387 for line in f.readlines():
388 if re_disable.search(line) and not line.strip().startswith("#"):
389 status.append(_("change 'disable' to 'no' in %(file)s") % { "file" : "/etc/xinetd.d/tftp" })
390 else:
391 status.append("missing configuration file: /etc/xinetd.d/tftp")
392
393 def check_ctftpd_bin(self,status):
394 """
395 Check if the Cobbler tftp server is installed
396 """
397 if self.checked_dist in ["debian", "ubuntu"]:
398 return
399
400 if not os.path.exists("/etc/xinetd.d/ctftp"):
401 status.append("missing /etc/xinetd.d/ctftp")
402
403 def check_ctftpd_dir(self,status):
404 """
405 Check if cobbler.conf's tftpboot directory exists
406 """
407 if self.checked_dist in ["debian", "ubuntu"]:
408 return
409
410 bootloc = utils.tftpboot_location()
411 if not os.path.exists(bootloc):
412 status.append(_("please create directory: %(dirname)s") % { "dirname" : bootloc })
413
414 def check_ctftpd_conf(self,status):
415 """
416 Check that configured tftpd boot directory matches with actual
417 Check that tftpd is enabled to autostart
418 """
419 if self.checked_dist in ["debian", "ubuntu"]:
420 return
421
422 if os.path.exists("/etc/xinetd.d/tftp"):
423 f = open("/etc/xinetd.d/tftp")
424 re_disable = re.compile(r'disable.*=.*no')
425 for line in f.readlines():
426 if re_disable.search(line) and not line.strip().startswith("#"):
427 status.append(_("change 'disable' to 'yes' in %(file)s") % { "file" : "/etc/xinetd.d/tftp" })
428 if os.path.exists("/etc/xinetd.d/ctftp"):
429 f = open("/etc/xinetd.d/ctftp")
430 re_disable = re.compile(r'disable.*=.*yes')
431 for line in f.readlines():
432 if re_disable.search(line) and not line.strip().startswith("#"):
433 status.append(_("change 'disable' to 'no' in %(file)s") % { "file" : "/etc/xinetd.d/ctftp" })
434 else:
435 status.append("missing configuration file: /etc/xinetd.d/ctftp")
436
437 def check_rsync_conf(self,status):
438 """
439 Check that rsync is enabled to autostart
440 """
441 if self.checked_dist in ["debian", "ubuntu"]:
442 return
443
444 if os.path.exists("/etc/xinetd.d/rsync"):
445 f = open("/etc/xinetd.d/rsync")
446 re_disable = re.compile(r'disable.*=.*yes')
447 for line in f.readlines():
448 if re_disable.search(line) and not line.strip().startswith("#"):
449 status.append(_("change 'disable' to 'no' in %(file)s") % { "file" : "/etc/xinetd.d/rsync" })
450 else:
451 status.append(_("file %(file)s does not exist") % { "file" : "/etc/xinetd.d/rsync" })
452
453
454 def check_dhcpd_conf(self,status):
455 """
456 NOTE: this code only applies if cobbler is *NOT* set to generate
457 a dhcp.conf file
458
459 Check that dhcpd *appears* to be configured for pxe booting.
460 We can't assure file correctness. Since a cobbler user might
461 have dhcp on another server, it's okay if it's not there and/or
462 not configured correctly according to automated scans.
463 """
464 if not (self.settings.manage_dhcp == 0):
465 return
466
467 if os.path.exists(self.settings.dhcpd_conf):
468 match_next = False
469 match_file = False
470 f = open(self.settings.dhcpd_conf)
471 for line in f.readlines():
472 if line.find("next-server") != -1:
473 match_next = True
474 if line.find("filename") != -1:
475 match_file = True
476 if not match_next:
477 status.append(_("expecting next-server entry in %(file)s") % { "file" : self.settings.dhcpd_conf })
478 if not match_file:
479 status.append(_("missing file: %(file)s") % { "file" : self.settings.dhcpd_conf })
480 else:
481 status.append(_("missing file: %(file)s") % { "file" : self.settings.dhcpd_conf })
482
0483
=== added directory '.pc/40_ubuntu_bind9_management.patch/cobbler/modules'
=== added file '.pc/40_ubuntu_bind9_management.patch/cobbler/modules/manage_bind.py'
--- .pc/40_ubuntu_bind9_management.patch/cobbler/modules/manage_bind.py 1970-01-01 00:00:00 +0000
+++ .pc/40_ubuntu_bind9_management.patch/cobbler/modules/manage_bind.py 2011-04-18 12:44:31 +0000
@@ -0,0 +1,332 @@
1"""
2This is some of the code behind 'cobbler sync'.
3
4Copyright 2006-2009, Red Hat, Inc
5Michael DeHaan <mdehaan@redhat.com>
6John Eckersberg <jeckersb@redhat.com>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
2102110-1301 USA
22"""
23
24import os
25import os.path
26import shutil
27import time
28import sys
29import glob
30import traceback
31import errno
32import re
33from shlex import shlex
34
35
36import utils
37from cexceptions import *
38import templar
39
40import item_distro
41import item_profile
42import item_repo
43import item_system
44
45from utils import _
46
47
48def register():
49 """
50 The mandatory cobbler module registration hook.
51 """
52 return "manage"
53
54
55class BindManager:
56
57 def what(self):
58 return "bind"
59
60 def __init__(self,config,logger):
61 """
62 Constructor
63 """
64 self.logger = logger
65 self.config = config
66 self.api = config.api
67 self.distros = config.distros()
68 self.profiles = config.profiles()
69 self.systems = config.systems()
70 self.settings = config.settings()
71 self.repos = config.repos()
72 self.templar = templar.Templar(config)
73
74 def regen_hosts(self):
75 pass # not used
76
77 def __forward_zones(self):
78 """
79 Returns a map of zones and the records that belong
80 in them
81 """
82 zones = {}
83 forward_zones = self.settings.manage_forward_zones
84 if type(forward_zones) != type([]):
85 # gracefully handle when user inputs only a single zone
86 # as a string instead of a list with only a single item
87 forward_zones = [forward_zones]
88
89 for zone in forward_zones:
90 zones[zone] = {}
91
92 for system in self.systems:
93 for (name, interface) in system.interfaces.iteritems():
94 host = interface["dns_name"]
95 ip = interface["ip_address"]
96 if not system.is_management_supported(cidr_ok=False):
97 continue
98 if not host or not ip:
99 # gotsta have some dns_name and ip or else!
100 continue
101 if host.find(".") == -1:
102 continue
103
104 # match the longest zone!
105 # e.g. if you have a host a.b.c.d.e
106 # if manage_forward_zones has:
107 # - c.d.e
108 # - b.c.d.e
109 # then a.b.c.d.e should go in b.c.d.e
110 best_match = ''
111 for zone in zones.keys():
112 if re.search('\.%s$' % zone, host) and len(zone) > len(best_match):
113 best_match = zone
114
115 if best_match == '': # no match
116 continue
117
118 # strip the zone off the dns_name and append the
119 # remainder + ip to the zone list
120 host = re.sub('\.%s$' % best_match, '', host)
121
122 zones[best_match][host] = ip
123
124 return zones
125
126 def __reverse_zones(self):
127 """
128 Returns a map of zones and the records that belong
129 in them
130 """
131 zones = {}
132 reverse_zones = self.settings.manage_reverse_zones
133 if type(reverse_zones) != type([]):
134 # gracefully handle when user inputs only a single zone
135 # as a string instead of a list with only a single item
136 reverse_zones = [reverse_zones]
137
138 for zone in reverse_zones:
139 zones[zone] = {}
140
141 for sys in self.systems:
142 for (name, interface) in sys.interfaces.iteritems():
143 host = interface["dns_name"]
144 ip = interface["ip_address"]
145 if not sys.is_management_supported(cidr_ok=False):
146 continue
147 if not host or not ip:
148 # gotsta have some dns_name and ip or else!
149 continue
150
151 # match the longest zone!
152 # e.g. if you have an ip 1.2.3.4
153 # if manage_reverse_zones has:
154 # - 1.2
155 # - 1.2.3
156 # then 1.2.3.4 should go in 1.2.3
157 best_match = ''
158 for zone in zones.keys():
159 if re.search('^%s\.' % zone, ip) and len(zone) > len(best_match):
160 best_match = zone
161
162 if best_match == '': # no match
163 continue
164
165 # strip the zone off the front of the ip
166 # reverse the rest of the octets
167 # append the remainder + dns_name
168 ip = ip.replace(best_match, '', 1)
169 if ip[0] == '.': # strip leading '.' if it's there
170 ip = ip[1:]
171 tokens = ip.split('.')
172 tokens.reverse()
173 ip = '.'.join(tokens)
174 zones[best_match][ip] = host + '.'
175
176 return zones
177
178
179 def __write_named_conf(self):
180 """
181 Write out the named.conf main config file from the template.
182 """
183 settings_file = "/etc/named.conf"
184 template_file = "/etc/cobbler/named.template"
185 forward_zones = self.settings.manage_forward_zones
186 reverse_zones = self.settings.manage_reverse_zones
187
188 metadata = {'forward_zones': self.__forward_zones().keys(),
189 'reverse_zones': [],
190 'zone_include': ''}
191
192 for zone in metadata['forward_zones']:
193 txt = """
194zone "%(zone)s." {
195 type master;
196 file "%(zone)s";
197};
198""" % {'zone': zone}
199 metadata['zone_include'] = metadata['zone_include'] + txt
200
201 for zone in self.__reverse_zones().keys():
202 tokens = zone.split('.')
203 tokens.reverse()
204 arpa = '.'.join(tokens) + '.in-addr.arpa'
205 metadata['reverse_zones'].append((zone, arpa))
206 txt = """
207zone "%(arpa)s." {
208 type master;
209 file "%(zone)s";
210};
211""" % {'arpa': arpa, 'zone': zone}
212 metadata['zone_include'] = metadata['zone_include'] + txt
213
214 try:
215 f2 = open(template_file,"r")
216 except:
217 raise CX(_("error reading template from file: %s") % template_file)
218 template_data = ""
219 template_data = f2.read()
220 f2.close()
221
222 if self.logger is not None:
223 self.logger.info("generating %s" % settings_file)
224 self.templar.render(template_data, metadata, settings_file, None)
225
226 def __ip_sort(self, ips):
227 """
228 Sorts IP addresses (or partial addresses) in a numerical fashion per-octet
229 """
230 # strings to integer octet chunks so we can sort numerically
231 octets = map(lambda x: [int(i) for i in x.split('.')], ips)
232 octets.sort()
233 # integers back to strings
234 octets = map(lambda x: [str(i) for i in x], octets)
235 return ['.'.join(i) for i in octets]
236
237 def __pretty_print_host_records(self, hosts, rectype='A', rclass='IN'):
238 """
239 Format host records by order and with consistent indentation
240 """
241 names = [k for k,v in hosts.iteritems()]
242 if not names: return '' # zones with no hosts
243
244 if rectype == 'PTR':
245 names = self.__ip_sort(names)
246 else:
247 names.sort()
248
249 max_name = max([len(i) for i in names])
250
251 s = ""
252 for name in names:
253 spacing = " " * (max_name - len(name))
254 my_name = "%s%s" % (name, spacing)
255 my_host = hosts[name]
256 s += "%s %s %s %s\n" % (my_name, rclass, rectype, my_host)
257 return s
258
259 def __write_zone_files(self):
260 """
261 Write out the forward and reverse zone files for all configured zones
262 """
263 default_template_file = "/etc/cobbler/zone.template"
264 cobbler_server = self.settings.server
265 serial = int(time.time())
266 forward = self.__forward_zones()
267 reverse = self.__reverse_zones()
268
269 try:
270 f2 = open(default_template_file,"r")
271 except:
272 raise CX(_("error reading template from file: %s") % default_template_file)
273 default_template_data = ""
274 default_template_data = f2.read()
275 f2.close()
276
277 for (zone, hosts) in forward.iteritems():
278 metadata = {
279 'cobbler_server': cobbler_server,
280 'serial': serial,
281 'host_record': ''
282 }
283
284 # grab zone-specific template if it exists
285 try:
286 fd = open('/etc/cobbler/zone_templates/%s' % zone)
287 template_data = fd.read()
288 fd.close()
289 except:
290 template_data = default_template_data
291
292 metadata['host_record'] = self.__pretty_print_host_records(hosts)
293
294 zonefilename='/var/named/' + zone
295 if self.logger is not None:
296 self.logger.info("generating (forward) %s" % zonefilename)
297 self.templar.render(template_data, metadata, zonefilename, None)
298
299 for (zone, hosts) in reverse.iteritems():
300 metadata = {
301 'cobbler_server': cobbler_server,
302 'serial': serial,
303 'host_record': ''
304 }
305
306 # grab zone-specific template if it exists
307 try:
308 fd = open('/etc/cobbler/zone_templates/%s' % zone)
309 template_data = fd.read()
310 fd.close()
311 except:
312 template_data = default_template_data
313
314 metadata['host_record'] = self.__pretty_print_host_records(hosts, rectype='PTR')
315
316 zonefilename='/var/named/' + zone
317 if self.logger is not None:
318 self.logger.info("generating (reverse) %s" % zonefilename)
319 self.templar.render(template_data, metadata, zonefilename, None)
320
321
322 def write_dns_files(self):
323 """
324 BIND files are written when manage_dns is set in
325 /var/lib/cobbler/settings.
326 """
327
328 self.__write_named_conf()
329 self.__write_zone_files()
330
331def get_manager(config,logger):
332 return BindManager(config,logger)
0333
=== added file '.pc/40_ubuntu_bind9_management.patch/cobbler/modules/sync_post_restart_services.py'
--- .pc/40_ubuntu_bind9_management.patch/cobbler/modules/sync_post_restart_services.py 1970-01-01 00:00:00 +0000
+++ .pc/40_ubuntu_bind9_management.patch/cobbler/modules/sync_post_restart_services.py 2011-04-18 12:44:31 +0000
@@ -0,0 +1,66 @@
1import distutils.sysconfig
2import sys
3import os
4import traceback
5import cexceptions
6import os
7import sys
8import xmlrpclib
9import cobbler.module_loader as module_loader
10import cobbler.utils as utils
11
12plib = distutils.sysconfig.get_python_lib()
13mod_path="%s/cobbler" % plib
14sys.path.insert(0, mod_path)
15
16def register():
17 # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster.
18 # the return of this method indicates the trigger type
19 return "/var/lib/cobbler/triggers/sync/post/*"
20
21def run(api,args,logger):
22
23 settings = api.settings()
24
25 manage_dhcp = str(settings.manage_dhcp).lower()
26 manage_dns = str(settings.manage_dns).lower()
27 manage_tftpd = str(settings.manage_tftpd).lower()
28 restart_dhcp = str(settings.restart_dhcp).lower()
29 restart_dns = str(settings.restart_dns).lower()
30
31 which_dhcp_module = module_loader.get_module_from_file("dhcp","module",just_name=True).strip()
32 which_dns_module = module_loader.get_module_from_file("dns","module",just_name=True).strip()
33
34 # special handling as we don't want to restart it twice
35 has_restarted_dnsmasq = False
36
37 rc = 0
38 if manage_dhcp != "0":
39 if which_dhcp_module == "manage_isc":
40 if restart_dhcp != "0":
41 rc = utils.subprocess_call(logger, "dhcpd -t -q", shell=True)
42 if rc != 0:
43 logger.error("dhcpd -t failed")
44 return 1
45 rc = utils.subprocess_call(logger,"service isc-dhcp-server restart", shell=True)
46 elif which_dhcp_module == "manage_dnsmasq":
47 if restart_dhcp != "0":
48 rc = utils.subprocess_call(logger, "service dnsmasq restart")
49 has_restarted_dnsmasq = True
50 else:
51 logger.error("unknown DHCP engine: %s" % which_dhcp_module)
52 rc = 411
53
54 if manage_dns != "0" and restart_dns != "0":
55 if which_dns_module == "manage_bind":
56 rc = utils.subprocess_call(logger, "service named restart", shell=True)
57 elif which_dns_module == "manage_dnsmasq" and not has_restarted_dnsmasq:
58 rc = utils.subprocess_call(logger, "service dnsmasq restart", shell=True)
59 elif which_dns_module == "manage_dnsmasq" and has_restarted_dnsmasq:
60 rc = 0
61 else:
62 logger.error("unknown DNS engine: %s" % which_dns_module)
63 rc = 412
64
65 return rc
66
067
=== added directory '.pc/40_ubuntu_bind9_management.patch/templates'
=== added directory '.pc/40_ubuntu_bind9_management.patch/templates/etc'
=== added file '.pc/40_ubuntu_bind9_management.patch/templates/etc/named.template'
--- .pc/40_ubuntu_bind9_management.patch/templates/etc/named.template 1970-01-01 00:00:00 +0000
+++ .pc/40_ubuntu_bind9_management.patch/templates/etc/named.template 2011-04-18 12:44:31 +0000
@@ -0,0 +1,31 @@
1options {
2 listen-on port 53 { 127.0.0.1; };
3 directory "/var/named";
4 dump-file "/var/named/data/cache_dump.db";
5 statistics-file "/var/named/data/named_stats.txt";
6 memstatistics-file "/var/named/data/named_mem_stats.txt";
7 allow-query { localhost; };
8 recursion yes;
9};
10
11logging {
12 channel default_debug {
13 file "data/named.run";
14 severity dynamic;
15 };
16};
17
18#for $zone in $forward_zones
19zone "${zone}." {
20 type master;
21 file "$zone";
22};
23
24#end for
25#for $zone, $arpa in $reverse_zones
26zone "${arpa}." {
27 type master;
28 file "$zone";
29};
30
31#end for
032
=== modified file '.pc/applied-patches'
--- .pc/applied-patches 2011-04-15 12:47:39 +0000
+++ .pc/applied-patches 2011-04-18 12:44:31 +0000
@@ -10,3 +10,4 @@
1037_koan_install_tree.patch1037_koan_install_tree.patch
1138_koan_qcreate_ubuntu_support.patch1138_koan_qcreate_ubuntu_support.patch
1239_cw_remove_vhost.patch1239_cw_remove_vhost.patch
1340_ubuntu_bind9_management.patch
1314
=== modified file 'cobbler/action_check.py'
--- cobbler/action_check.py 2011-01-18 12:03:14 +0000
+++ cobbler/action_check.py 2011-04-18 12:44:31 +0000
@@ -66,7 +66,7 @@
66 mode = self.config.api.get_sync().dns.what()66 mode = self.config.api.get_sync().dns.what()
67 if mode == "bind":67 if mode == "bind":
68 self.check_bind_bin(status)68 self.check_bind_bin(status)
69 self.check_service(status,"named")69 self.check_service(status,"bind9")
70 elif mode == "dnsmasq" and not self.settings.manage_dhcp:70 elif mode == "dnsmasq" and not self.settings.manage_dhcp:
71 self.check_dnsmasq_bin(status)71 self.check_dnsmasq_bin(status)
72 self.check_service(status,"dnsmasq")72 self.check_service(status,"dnsmasq")
7373
=== modified file 'cobbler/modules/manage_bind.py'
--- cobbler/modules/manage_bind.py 2011-01-18 12:03:14 +0000
+++ cobbler/modules/manage_bind.py 2011-04-18 12:44:31 +0000
@@ -180,7 +180,7 @@
180 """180 """
181 Write out the named.conf main config file from the template.181 Write out the named.conf main config file from the template.
182 """182 """
183 settings_file = "/etc/named.conf"183 settings_file = "/etc/bind/named.conf.local"
184 template_file = "/etc/cobbler/named.template"184 template_file = "/etc/cobbler/named.template"
185 forward_zones = self.settings.manage_forward_zones185 forward_zones = self.settings.manage_forward_zones
186 reverse_zones = self.settings.manage_reverse_zones186 reverse_zones = self.settings.manage_reverse_zones
@@ -291,7 +291,7 @@
291291
292 metadata['host_record'] = self.__pretty_print_host_records(hosts)292 metadata['host_record'] = self.__pretty_print_host_records(hosts)
293293
294 zonefilename='/var/named/' + zone294 zonefilename='/etc/bind/db.' + zone
295 if self.logger is not None:295 if self.logger is not None:
296 self.logger.info("generating (forward) %s" % zonefilename)296 self.logger.info("generating (forward) %s" % zonefilename)
297 self.templar.render(template_data, metadata, zonefilename, None)297 self.templar.render(template_data, metadata, zonefilename, None)
@@ -313,7 +313,7 @@
313313
314 metadata['host_record'] = self.__pretty_print_host_records(hosts, rectype='PTR')314 metadata['host_record'] = self.__pretty_print_host_records(hosts, rectype='PTR')
315315
316 zonefilename='/var/named/' + zone316 zonefilename='/etc/bind/db.' + zone
317 if self.logger is not None:317 if self.logger is not None:
318 self.logger.info("generating (reverse) %s" % zonefilename)318 self.logger.info("generating (reverse) %s" % zonefilename)
319 self.templar.render(template_data, metadata, zonefilename, None)319 self.templar.render(template_data, metadata, zonefilename, None)
320320
=== modified file 'cobbler/modules/sync_post_restart_services.py'
--- cobbler/modules/sync_post_restart_services.py 2011-01-28 14:39:12 +0000
+++ cobbler/modules/sync_post_restart_services.py 2011-04-18 12:44:31 +0000
@@ -53,7 +53,7 @@
5353
54 if manage_dns != "0" and restart_dns != "0":54 if manage_dns != "0" and restart_dns != "0":
55 if which_dns_module == "manage_bind":55 if which_dns_module == "manage_bind":
56 rc = utils.subprocess_call(logger, "service named restart", shell=True)56 rc = utils.subprocess_call(logger, "service bind9 restart", shell=True)
57 elif which_dns_module == "manage_dnsmasq" and not has_restarted_dnsmasq:57 elif which_dns_module == "manage_dnsmasq" and not has_restarted_dnsmasq:
58 rc = utils.subprocess_call(logger, "service dnsmasq restart", shell=True)58 rc = utils.subprocess_call(logger, "service dnsmasq restart", shell=True)
59 elif which_dns_module == "manage_dnsmasq" and has_restarted_dnsmasq:59 elif which_dns_module == "manage_dnsmasq" and has_restarted_dnsmasq:
6060
=== modified file 'debian/changelog'
--- debian/changelog 2011-04-15 12:47:39 +0000
+++ debian/changelog 2011-04-18 12:44:31 +0000
@@ -1,3 +1,14 @@
1cobbler (2.1.0-0ubuntu7) natty; urgency=low
2
3 * Fixed management of bind9 (LP: #764391):
4 - debian/patches/40_ubuntu_bind9_management.patch:
5 - Manage bind9 instead of named daemon.
6 - Generate configuration in /etc/bind.
7 - Use default bind9 configuration as much as possible.
8 - debian/control: Added Suggests: bind9 to cobbler.
9
10 -- James Page <james.page@canonical.com> Mon, 18 Apr 2011 11:15:59 +0100
11
1cobbler (2.1.0-0ubuntu6) natty; urgency=low12cobbler (2.1.0-0ubuntu6) natty; urgency=low
213
3 * Fix DocumentRoot conflict with default vhost (LP: #760012)14 * Fix DocumentRoot conflict with default vhost (LP: #760012)
415
=== modified file 'debian/control'
--- debian/control 2011-04-04 12:55:44 +0000
+++ debian/control 2011-04-18 12:44:31 +0000
@@ -34,6 +34,7 @@
34 cobbler-web,34 cobbler-web,
35 debmirror35 debmirror
36Suggests: createrepo,36Suggests: createrepo,
37 bind9,
37 dhcp3-server,38 dhcp3-server,
38 genisoimage,39 genisoimage,
39 cman,40 cman,
4041
=== added file 'debian/patches/40_ubuntu_bind9_management.patch'
--- debian/patches/40_ubuntu_bind9_management.patch 1970-01-01 00:00:00 +0000
+++ debian/patches/40_ubuntu_bind9_management.patch 2011-04-18 12:44:31 +0000
@@ -0,0 +1,103 @@
1Description: Enable management of Ubuntu bind9 with manage_dns setting.
2 - Generate /etc/bind/named.conf.local to plug into the standard Ubuntu bind9
3 install and preserve the bind9 default installation configuration.
4 - Generate /etc/bind/db.$zone to align to bind9 zone file names.
5 - Restart/check bind9 rather than named.
6Author: James Page <james.page@canonical.com>
7Forwarded: not-needed
8
9Index: fix-764391/templates/etc/named.template
10===================================================================
11--- fix-764391.orig/templates/etc/named.template 2011-04-18 10:46:43.057345158 +0100
12+++ fix-764391/templates/etc/named.template 2011-04-18 10:47:23.426906461 +0100
13@@ -1,31 +1,14 @@
14-options {
15- listen-on port 53 { 127.0.0.1; };
16- directory "/var/named";
17- dump-file "/var/named/data/cache_dump.db";
18- statistics-file "/var/named/data/named_stats.txt";
19- memstatistics-file "/var/named/data/named_mem_stats.txt";
20- allow-query { localhost; };
21- recursion yes;
22-};
23-
24-logging {
25- channel default_debug {
26- file "data/named.run";
27- severity dynamic;
28- };
29-};
30-
31 #for $zone in $forward_zones
32 zone "${zone}." {
33 type master;
34- file "$zone";
35+ file "/etc/bind/db.$zone";
36 };
37
38 #end for
39 #for $zone, $arpa in $reverse_zones
40 zone "${arpa}." {
41 type master;
42- file "$zone";
43+ file "/etc/bind/db.$zone";
44 };
45
46 #end for
47Index: fix-764391/cobbler/modules/manage_bind.py
48===================================================================
49--- fix-764391.orig/cobbler/modules/manage_bind.py 2011-04-18 10:47:46.226932274 +0100
50+++ fix-764391/cobbler/modules/manage_bind.py 2011-04-18 10:49:00.526931926 +0100
51@@ -180,7 +180,7 @@
52 """
53 Write out the named.conf main config file from the template.
54 """
55- settings_file = "/etc/named.conf"
56+ settings_file = "/etc/bind/named.conf.local"
57 template_file = "/etc/cobbler/named.template"
58 forward_zones = self.settings.manage_forward_zones
59 reverse_zones = self.settings.manage_reverse_zones
60@@ -291,7 +291,7 @@
61
62 metadata['host_record'] = self.__pretty_print_host_records(hosts)
63
64- zonefilename='/var/named/' + zone
65+ zonefilename='/etc/bind/db.' + zone
66 if self.logger is not None:
67 self.logger.info("generating (forward) %s" % zonefilename)
68 self.templar.render(template_data, metadata, zonefilename, None)
69@@ -313,7 +313,7 @@
70
71 metadata['host_record'] = self.__pretty_print_host_records(hosts, rectype='PTR')
72
73- zonefilename='/var/named/' + zone
74+ zonefilename='/etc/bind/db.' + zone
75 if self.logger is not None:
76 self.logger.info("generating (reverse) %s" % zonefilename)
77 self.templar.render(template_data, metadata, zonefilename, None)
78Index: fix-764391/cobbler/action_check.py
79===================================================================
80--- fix-764391.orig/cobbler/action_check.py 2011-04-18 10:50:24.566792922 +0100
81+++ fix-764391/cobbler/action_check.py 2011-04-18 10:50:49.546726457 +0100
82@@ -66,7 +66,7 @@
83 mode = self.config.api.get_sync().dns.what()
84 if mode == "bind":
85 self.check_bind_bin(status)
86- self.check_service(status,"named")
87+ self.check_service(status,"bind9")
88 elif mode == "dnsmasq" and not self.settings.manage_dhcp:
89 self.check_dnsmasq_bin(status)
90 self.check_service(status,"dnsmasq")
91Index: fix-764391/cobbler/modules/sync_post_restart_services.py
92===================================================================
93--- fix-764391.orig/cobbler/modules/sync_post_restart_services.py 2011-04-18 10:55:38.985302648 +0100
94+++ fix-764391/cobbler/modules/sync_post_restart_services.py 2011-04-18 10:55:56.105188050 +0100
95@@ -53,7 +53,7 @@
96
97 if manage_dns != "0" and restart_dns != "0":
98 if which_dns_module == "manage_bind":
99- rc = utils.subprocess_call(logger, "service named restart", shell=True)
100+ rc = utils.subprocess_call(logger, "service bind9 restart", shell=True)
101 elif which_dns_module == "manage_dnsmasq" and not has_restarted_dnsmasq:
102 rc = utils.subprocess_call(logger, "service dnsmasq restart", shell=True)
103 elif which_dns_module == "manage_dnsmasq" and has_restarted_dnsmasq:
0104
=== modified file 'debian/patches/series'
--- debian/patches/series 2011-04-15 12:47:39 +0000
+++ debian/patches/series 2011-04-18 12:44:31 +0000
@@ -10,3 +10,4 @@
1037_koan_install_tree.patch1037_koan_install_tree.patch
1138_koan_qcreate_ubuntu_support.patch1138_koan_qcreate_ubuntu_support.patch
1239_cw_remove_vhost.patch1239_cw_remove_vhost.patch
1340_ubuntu_bind9_management.patch
1314
=== modified file 'templates/etc/named.template'
--- templates/etc/named.template 2011-01-18 12:03:14 +0000
+++ templates/etc/named.template 2011-04-18 12:44:31 +0000
@@ -1,31 +1,14 @@
1options {
2 listen-on port 53 { 127.0.0.1; };
3 directory "/var/named";
4 dump-file "/var/named/data/cache_dump.db";
5 statistics-file "/var/named/data/named_stats.txt";
6 memstatistics-file "/var/named/data/named_mem_stats.txt";
7 allow-query { localhost; };
8 recursion yes;
9};
10
11logging {
12 channel default_debug {
13 file "data/named.run";
14 severity dynamic;
15 };
16};
17
18#for $zone in $forward_zones1#for $zone in $forward_zones
19zone "${zone}." {2zone "${zone}." {
20 type master;3 type master;
21 file "$zone";4 file "/etc/bind/db.$zone";
22};5};
236
24#end for7#end for
25#for $zone, $arpa in $reverse_zones8#for $zone, $arpa in $reverse_zones
26zone "${arpa}." {9zone "${arpa}." {
27 type master;10 type master;
28 file "$zone";11 file "/etc/bind/db.$zone";
29};12};
3013
31#end for14#end for

Subscribers

People subscribed via source and target branches

to all changes: