Better DNS slave handling
[hcoop/zz_old/domtool2-proto.git] / src / domain.sml
CommitLineData
a11c0ff3 1(* HCoop Domtool (http://hcoop.sourceforge.net/)
2 * Copyright (c) 2006, Adam Chlipala
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
ae3a5b8c 17 *)
a11c0ff3 18
19(* Domain-related primitive actions *)
20
21structure Domain :> DOMAIN = struct
22
084d02b1 23structure SM = DataStructures.StringMap
24
25val nodes = map #2 Config.nodeIps
26val nodeMap = foldl (fn ((node, ip), mp) => SM.insert (mp, node, ip))
27 SM.empty Config.nodeIps
28
85af7d3e 29fun validIp s =
30 case map Int.fromString (String.fields (fn ch => ch = #".") s) of
31 [SOME n1, SOME n2, SOME n3, SOME n4] =>
32 n1 >= 0 andalso n1 < 256 andalso n2 >= 0 andalso n2 < 256 andalso n3 >= 0 andalso n3 < 256 andalso n4 >= 0 andalso n4 < 256
33 | _ => false
34
2f68506c 35fun isIdent ch = Char.isLower ch orelse Char.isDigit ch
36
37fun validHost s =
38 size s > 0 andalso size s < 20
39 andalso CharVector.all (fn ch => isIdent ch orelse ch = #"-") s
40
41fun validDomain s =
42 size s > 0 andalso size s < 100
43 andalso List.all validHost (String.fields (fn ch => ch = #".") s)
44
084d02b1 45fun validNode s = List.exists (fn s' => s = s') nodes
668e333e 46
85af7d3e 47val _ = Env.type_one "ip"
48 Env.string
49 validIp
50
2f68506c 51val _ = Env.type_one "host"
52 Env.string
53 validHost
54
55val _ = Env.type_one "domain"
56 Env.string
57 validDomain
58
668e333e 59val _ = Env.type_one "node"
60 Env.string
61 validNode
62
a11c0ff3 63open Ast
64
85af7d3e 65val dl = ErrorMsg.dummyLoc
66
67val nsD = (EString Config.defaultNs, dl)
68val serialD = (EVar "serialAuto", dl)
69val refD = (EInt Config.defaultRefresh, dl)
70val retD = (EInt Config.defaultRetry, dl)
71val expD = (EInt Config.defaultExpiry, dl)
72val minD = (EInt Config.defaultMinimum, dl)
73
74val soaD = multiApp ((EVar "soa", dl),
75 dl,
76 [nsD, serialD, refD, retD, expD, minD])
77
668e333e 78val masterD = (EApp ((EVar "internalMaster", dl),
79 (EString Config.defaultNode, dl)),
80 dl)
81
85af7d3e 82val _ = Main.registerDefault ("DNS",
83 (TBase "dnsKind", dl),
668e333e 84 (multiApp ((EVar "useDns", dl),
85 dl,
86 [soaD, masterD, (EList [], dl)])))
85af7d3e 87
88val _ = Main.registerDefault ("TTL",
89 (TBase "int", dl),
90 (EInt Config.Bind.defaultTTL, dl))
91
92type soa = {ns : string,
93 serial : int option,
94 ref : int,
95 ret : int,
96 exp : int,
97 min : int}
98
99val serial = fn (EVar "serialAuto", _) => SOME NONE
100 | (EApp ((EVar "serialConst", _), n), _) => Option.map SOME (Env.int n)
101 | _ => NONE
102
103val soa = fn (EApp ((EApp ((EApp ((EApp ((EApp ((EApp
104 ((EVar "soa", _), ns), _),
105 sl), _),
106 rf), _),
107 ret), _),
108 exp), _),
109 min), _) =>
110 (case (Env.string ns, serial sl, Env.int rf,
111 Env.int ret, Env.int exp, Env.int min) of
112 (SOME ns, SOME sl, SOME rf,
113 SOME ret, SOME exp, SOME min) =>
114 SOME {ns = ns,
115 serial = sl,
116 ref = rf,
117 ret = ret,
118 exp = exp,
119 min = min}
120 | _ => NONE)
121 | _ => NONE
122
668e333e 123datatype master =
124 ExternalMaster of string
125 | InternalMaster of string
126
127val master = fn (EApp ((EVar "externalMaster", _), e), _) => Option.map ExternalMaster (Env.string e)
128 | (EApp ((EVar "internalMaster", _), e), _) => Option.map InternalMaster (Env.string e)
129 | _ => NONE
130
85af7d3e 131datatype dnsKind =
668e333e 132 UseDns of {soa : soa,
133 master : master,
134 slaves : string list}
85af7d3e 135 | NoDns
136
668e333e 137val dnsKind = fn (EApp ((EApp ((EApp
138 ((EVar "useDns", _), sa), _),
139 mstr), _),
140 slaves), _) =>
141 (case (soa sa, master mstr, Env.list Env.string slaves) of
142 (SOME sa, SOME mstr, SOME slaves) =>
143 SOME (UseDns {soa = sa,
144 master = mstr,
145 slaves = slaves})
146 | _ => NONE)
85af7d3e 147 | _ => NONE
148
a11c0ff3 149val befores = ref (fn (_ : string) => ())
150val afters = ref (fn (_ : string) => ())
151
152fun registerBefore f =
153 let
154 val old = !befores
155 in
156 befores := (fn x => (old x; f x))
157 end
158
159fun registerAfter f =
160 let
161 val old = !afters
162 in
163 afters := (fn x => (old x; f x))
164 end
165
166val current = ref ""
668e333e 167val currentPath = ref (fn (_ : string) => "")
ae3a5b8c 168
c12828f2 169val scratch = ref ""
170
ae3a5b8c 171fun currentDomain () = !current
172
668e333e 173fun domainFile {node, name} = ((*print ("Opening " ^ !currentPath node ^ name ^ "\n");*)
174 TextIO.openOut (!currentPath node ^ name))
ae3a5b8c 175
176fun getPath domain =
177 let
178 val toks = String.fields (fn ch => ch = #".") domain
179
180 val elems = foldr (fn (piece, elems) =>
181 let
182 val elems = piece :: elems
c12828f2 183
668e333e 184 fun doNode node =
185 let
186 val path = String.concatWith "/"
187 (Config.resultRoot :: node :: rev elems)
188 val tmpPath = String.concatWith "/"
189 (Config.tmpDir :: node :: rev elems)
190 in
191 (if Posix.FileSys.ST.isDir
192 (Posix.FileSys.stat path) then
193 ()
194 else
195 (OS.FileSys.remove path;
196 OS.FileSys.mkDir path))
197 handle OS.SysErr _ => OS.FileSys.mkDir path;
198
199 (if Posix.FileSys.ST.isDir
200 (Posix.FileSys.stat tmpPath) then
201 ()
202 else
203 (OS.FileSys.remove tmpPath;
204 OS.FileSys.mkDir tmpPath))
205 handle OS.SysErr _ => OS.FileSys.mkDir tmpPath
206 end
207 in
084d02b1 208 app doNode nodes;
ae3a5b8c 209 elems
210 end) [] toks
211 in
668e333e 212 fn (root, site) => String.concatWith "/" (root :: site :: rev ("" :: elems))
c12828f2 213 end
214
215datatype file_action' =
216 Add' of {src : string, dst : string}
217 | Delete' of string
218 | Modify' of {src : string, dst : string}
219
668e333e 220fun findDiffs (site, dom, acts) =
c12828f2 221 let
668e333e 222 val gp = getPath dom
223 val realPath = gp (Config.resultRoot, site)
224 val tmpPath = gp (Config.tmpDir, site)
225
226 (*val _ = print ("getDiffs(" ^ site ^ ", " ^ dom ^ ")... " ^ realPath ^ "; " ^ tmpPath ^ "\n")*)
c12828f2 227
228 val dir = Posix.FileSys.opendir realPath
229
230 fun loopReal acts =
231 case Posix.FileSys.readdir dir of
232 NONE => (Posix.FileSys.closedir dir;
233 acts)
234 | SOME fname =>
235 let
236 val real = OS.Path.joinDirFile {dir = realPath,
237 file = fname}
238 val tmp = OS.Path.joinDirFile {dir = tmpPath,
239 file = fname}
240 in
241 if Posix.FileSys.ST.isDir (Posix.FileSys.stat real) then
242 loopReal acts
243 else if Posix.FileSys.access (tmp, []) then
1f53f82b 244 if Slave.shell [Config.diff, " ", real, " ", tmp] then
c12828f2 245 loopReal acts
246 else
668e333e 247 loopReal ((site, dom, realPath, Modify' {src = tmp, dst = real}) :: acts)
c12828f2 248 else
668e333e 249 loopReal ((site, dom, realPath, Delete' real) :: acts)
c12828f2 250 end
251
668e333e 252 val acts = loopReal acts
c12828f2 253
1f53f82b 254 val dir = Posix.FileSys.opendir tmpPath
c12828f2 255
256 fun loopTmp acts =
257 case Posix.FileSys.readdir dir of
258 NONE => (Posix.FileSys.closedir dir;
259 acts)
260 | SOME fname =>
261 let
262 val real = OS.Path.joinDirFile {dir = realPath,
263 file = fname}
264 val tmp = OS.Path.joinDirFile {dir = tmpPath,
265 file = fname}
266 in
267 if Posix.FileSys.ST.isDir (Posix.FileSys.stat tmp) then
268 loopTmp acts
269 else if Posix.FileSys.access (real, []) then
270 loopTmp acts
271 else
668e333e 272 loopTmp ((site, dom, realPath, Add' {src = tmp, dst = real}) :: acts)
c12828f2 273 end
274
275 val acts = loopTmp acts
276 in
277 acts
ae3a5b8c 278 end
a11c0ff3 279
668e333e 280fun findAllDiffs () =
281 let
282 val dir = Posix.FileSys.opendir Config.tmpDir
283 val len = length (String.fields (fn ch => ch = #"/") Config.tmpDir) + 1
284
285 fun exploreSites diffs =
286 case Posix.FileSys.readdir dir of
287 NONE => diffs
288 | SOME site =>
289 let
290 fun explore (dname, diffs) =
291 let
292 val dir = Posix.FileSys.opendir dname
293
294 fun loop diffs =
295 case Posix.FileSys.readdir dir of
296 NONE => diffs
297 | SOME name =>
298 let
299 val fname = OS.Path.joinDirFile {dir = dname,
300 file = name}
301 in
302 loop (if Posix.FileSys.ST.isDir (Posix.FileSys.stat fname) then
303 let
304 val dom = String.fields (fn ch => ch = #"/") fname
305 val dom = List.drop (dom, len)
306 val dom = String.concatWith "." (rev dom)
307
308 val dname' = OS.Path.joinDirFile {dir = dname,
309 file = name}
310 in
311 explore (dname',
312 findDiffs (site, dom, diffs))
313 end
314 else
315 diffs)
316 end
317 in
318 loop diffs
319 before Posix.FileSys.closedir dir
320 end
321 in
322 explore (OS.Path.joinDirFile {dir = Config.tmpDir,
323 file = site}, diffs)
324 end
325 in
326 exploreSites []
327 before Posix.FileSys.closedir dir
328 end
329
330val masterNode : string option ref = ref NONE
331fun dnsMaster () = !masterNode
332
85af7d3e 333val _ = Env.containerV_one "domain"
334 ("domain", Env.string)
335 (fn (evs, dom) =>
336 let
337 val kind = Env.env dnsKind (evs, "DNS")
338 val ttl = Env.env Env.int (evs, "TTL")
339
668e333e 340 val path = getPath dom
85af7d3e 341
342 val () = (current := dom;
668e333e 343 currentPath := (fn site => path (Config.tmpDir, site)))
85af7d3e 344
668e333e 345 fun saveSoa (kind, soa : soa) node =
85af7d3e 346 let
668e333e 347 val outf = domainFile {node = node, name = "soa"}
85af7d3e 348 in
349 TextIO.output (outf, kind);
350 TextIO.output (outf, "\n");
351 TextIO.output (outf, Int.toString ttl);
352 TextIO.output (outf, "\n");
353 TextIO.output (outf, #ns soa);
354 TextIO.output (outf, "\n");
355 case #serial soa of
356 NONE => ()
357 | SOME n => TextIO.output (outf, Int.toString n);
358 TextIO.output (outf, "\n");
359 TextIO.output (outf, Int.toString (#ref soa));
360 TextIO.output (outf, "\n");
361 TextIO.output (outf, Int.toString (#ret soa));
362 TextIO.output (outf, "\n");
363 TextIO.output (outf, Int.toString (#exp soa));
364 TextIO.output (outf, "\n");
365 TextIO.output (outf, Int.toString (#min soa));
366 TextIO.output (outf, "\n");
367 TextIO.closeOut outf
368 end
369
084d02b1 370 fun saveNamed (kind, soa : soa, masterIp) node =
85af7d3e 371 let
668e333e 372 val outf = domainFile {node = node, name = "named.conf"}
85af7d3e 373 in
374 TextIO.output (outf, "\nzone \"");
375 TextIO.output (outf, dom);
376 TextIO.output (outf, "\" IN {\n\ttype ");
377 TextIO.output (outf, kind);
378 TextIO.output (outf, ";\n\tfile \"");
379 TextIO.output (outf, Config.Bind.zonePath);
380 TextIO.output (outf, "/");
381 TextIO.output (outf, dom);
382 TextIO.output (outf, ".zone\";\n");
383 case kind of
384 "master" => TextIO.output (outf, "\tallow-update { none; };\n")
084d02b1 385 | _ => (TextIO.output (outf, "\tmasters { ");
386 TextIO.output (outf, masterIp);
387 TextIO.output (outf, " };\n"));
388 TextIO.output (outf, "};\n");
85af7d3e 389 TextIO.closeOut outf
390 end
85af7d3e 391 in
392 case kind of
668e333e 393 NoDns => masterNode := NONE
394 | UseDns dns =>
084d02b1 395 let
396 val masterIp =
397 case #master dns of
398 InternalMaster node => valOf (SM.find (nodeMap, node))
399 | ExternalMaster ip => ip
400 in
401 app (saveSoa ("slave", #soa dns)) (#slaves dns);
402 app (saveNamed ("slave", #soa dns, masterIp)) (#slaves dns);
403 case #master dns of
404 InternalMaster node =>
405 (masterNode := SOME node;
406 saveSoa ("master", #soa dns) node;
407 saveNamed ("master", #soa dns, masterIp) node)
408 | _ => masterNode := NONE;
409 !befores dom
410 end
85af7d3e 411 end,
668e333e 412 fn () => !afters (!current))
413
414val () = Env.registerPre (fn () => (ignore (Slave.shellF ([Config.rm, " -rf ", Config.tmpDir, ""],
415 fn cl => "Temp file cleanup failed: " ^ cl));
416 OS.FileSys.mkDir Config.tmpDir;
417 app (fn node => OS.FileSys.mkDir
418 (OS.Path.joinDirFile {dir = Config.tmpDir,
419 file = node}))
084d02b1 420 nodes;
668e333e 421 app (fn node => OS.FileSys.mkDir
422 (OS.Path.joinDirFile {dir = Config.resultRoot,
423 file = node})
424 handle OS.SysErr _ => ())
084d02b1 425 nodes))
668e333e 426
427val () = Env.registerPost (fn () =>
428 let
429 val diffs = findAllDiffs ()
85af7d3e 430
668e333e 431 val diffs = map (fn (site, dom, dir, Add' {src, dst}) =>
432 (Slave.shellF ([Config.cp, " ", src, " ", dst],
433 fn cl => "Copy failed: " ^ cl);
434 (site,
85af7d3e 435 {action = Slave.Add,
436 domain = dom,
437 dir = dir,
668e333e 438 file = dst}))
439 | (site, dom, dir, Delete' dst) =>
440 (OS.FileSys.remove dst
441 handle OS.SysErr _ =>
442 ErrorMsg.error NONE ("Delete failed for " ^ dst);
443 (site,
85af7d3e 444 {action = Slave.Delete,
445 domain = dom,
446 dir = dir,
668e333e 447 file = dst}))
448 | (site, dom, dir, Modify' {src, dst}) =>
449 (Slave.shellF ([Config.cp, " ", src, " ", dst],
450 fn cl => "Copy failed: " ^ cl);
451 (site,
85af7d3e 452 {action = Slave.Modify,
453 domain = dom,
454 dir = dir,
668e333e 455 file = dst}))) diffs
456 in
457 if !ErrorMsg.anyErrors then
458 ()
459 else
460 Slave.handleChanges (map #2 diffs);
461 ignore (Slave.shellF ([Config.rm, " -rf ", Config.tmpDir, ""],
462 fn cl => "Temp file cleanup failed: " ^ cl))
463 end)
85af7d3e 464
465
a11c0ff3 466
467end