-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhostsetup.tcl
295 lines (249 loc) · 6.99 KB
/
hostsetup.tcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
package require snit
source [file dirname [info script]]/utils.tcl
namespace eval ::host-setup {
::variable ourRuleList [list]
::variable ourRuleDict [dict create]
::variable ourSourceDict [dict create]
::variable ourKnownBuiltins [dict create]
::sshcomm::register-plugin
namespace import ::sshcomm::utils::*
namespace export target
set type_template {
snit::type %TYPE% {
set %_target {}
%UTILS%
option -props
%OPTS%
method initialize {} {}
method reset {} {
foreach stVar [info vars ${selfns}::state*] {
if {[array exists $stVar]} {
array unset $stVar
} else {
set $stVar ""
}
}
$self initialize
}
method finalize {} {}; # empty default
%BODY%
typemethod {list target} {} [list list {*}[set %_target]]
method check-all {} {
set succeed {}
$self reset
foreach tg [$self list target] {
if {![$self check $tg]} {
return [list NG $tg OK $succeed DEBUG $myDebugMsgs]
}
lappend succeed $tg
}
list OK $succeed NG {} DEBUG $myDebugMsgs
}
method apply-all {} {
set succeed {}
$self reset
foreach tg [$self list target] {
if {![lindex [set all [$self ensure $tg]] 0]} {
return [list NG $tg OK $succeed DEBUG $myDebugMsgs \
FAILURE [lrange $all 1 end]]
}
lappend succeed $tg
}
$self finalize
list OK $succeed NG {} DEBUG $myDebugMsgs
}
}
}
proc rule-new {name args} {
[find-type-of-rule $name] %AUTO% {*}$args
}
proc find-type-of-rule rule {
dict get [find-rule $rule] nsname
}
proc find-rule rule {
::variable ourRuleDict
dict get $ourRuleDict $rule
}
proc next-rule-in {ruleList rule} { lsearch-and-get $ruleList $rule 1 }
proc prev-rule-in {ruleList rule} { lsearch-and-get $ruleList $rule -1 }
proc list-targets-of-rule rule {
[find-type-of-rule $rule] list target
}
proc list-rules {} {
::variable ourRuleList
set ourRuleList
}
proc build-opts {opts {outVar ""}} {
if {$outVar ne ""} {
upvar 1 $outVar dict
set dict [dict create]
}
set result {}
foreach {spec value} $opts {
set rest [lassign $spec name]
dict set dict $name [if {[llength $rest] <= 1} {
dict create help [lindex $rest 0] default $value
} elseif {[llength $rest] % 2 != 0} {
error "Invalid option spec($rest)"
} elseif {![dict exists $rest help]} {
error "Option spec doesn't have \"help\" entry"
} else {
if {[set subst [dict-default $rest subst ""]] ne ""} {
set value [subst $subst]
}
dict merge $rest [dict create default $value]
}]
append result [list option $name $value]\n
}
set result
}
proc rule {name opts body} {
::variable ourRuleList
::variable ourRuleDict
set inFile [uplevel 1 [list info script]]
if {$name eq "__FILE__"} {
set name [file rootname [file tail $inFile]]
}
if {[dict exists $ourRuleDict $name] && ![is-builtin-rule $name]} {
error "Redefinition of rule $name in $inFile. \n\
(Previously in [dict get $ourRuleDict $name file])"
}
if {[set title [dict-cut opts -title ""]] eq ""} {
error "Option -title is required for $name in $inFile!"
}
namespace eval $name {
namespace import ::host-setup::*
namespace import ::sshcomm::utils::*
namespace export *
}
if {[set files [dict-cut opts -import ""]] ne ""} {
foreach fn $files {
import-into $name $fn
}
}
set def [__EXPAND [set ::host-setup::type_template] \
%TYPE% $name \
%BODY% $body \
%UTILS% [set ::host-setup::utils]\
%OPTS% [::host-setup::build-opts $opts optsInfo]
]
if {[catch $def res]} {
set vn ::env(DEBUG_HOSTSETUP)
if {[info exists $vn] && [set $vn]} {
lassign $def snit name body
error [list compile-error $res \
{*}[snit::compile type $name $body]]
} else {
error "compile-error $res. name=$name"
}
} else {
lappend ourRuleList $name
dict set ourRuleDict $name [dict create file $inFile nsname $res \
title $title options $optsInfo]
set res
}
}
proc __EXPAND {template args} {
string map $args $template
}
set utils {
# Procs used in snit::macro must be defined by [_proc], not [proc]
_proc from {dictVar key args} {
upvar 1 $dictVar dict
if {[dict exists $dict $key]} {
set result [dict get $dict $key]
dict unset dict $key
set result
} elseif {[llength $args]} {
lindex $args 0
} else {
error "Missing entry '$key' in dict value."
}
}
_proc __EXPAND {template args} {
string map $args $template
}
}
snit::macro target {target spec} {
set ensure [from spec ensure ""]
set check [from spec check ""]
if {$ensure eq "" && $check eq ""} {
error "ensure (or check) is required!"
} elseif {$ensure eq ""} {
set ensure $check
}
set action [from spec action]
set doc [from spec doc ""]
set req [from spec require ""]
if {$spec ne ""} {
error "Unknown target spec for $target! $spec"
}
set targName [join $target _]
set arglist [list [list target $target] \
[list _target $targName]]
uplevel 1 [list lappend %_target $targName]
method [list doc $targName] {} [list return $doc]
method [list check $targName] $arglist $ensure
method [list ensure $targName] $arglist [__EXPAND {
set rc [catch {@COND@} __RESULT__]
if {$rc ni [list 0 2]} {
return [list no error $rc $__RESULT__]
} elseif {[lindex $__RESULT__ 0]} {
return yes
} else {
@ACTION@
$self check [join $target _]
}
} @COND@ $ensure @ACTION@ $action]
}
snit::macro finally body {
method finalize {} $body
}
snit::macro initially body {
method initialize {} $body
}
proc reset-rules {} {
::variable ourRuleList [list]
::variable ourRuleDict [dict create]
::variable ourSourceDict [dict create]
}
proc import-into {target source {glob *}} {
set ns [uplevel 1 [list source-once $source]]
uplevel 1 [list namespace eval $target \
[list namespace import ${ns}::$glob]]
}
proc source-once source {
::variable ourSourceDict
if {[file pathtype [set sn $source]] ne "absolute"} {
set dir [file dirname [file normalize \
[uplevel 1 [list info script]]]]
set source [file normalize [file join $dir $source]]
# puts "Change source path $sn to $source"
}
set vn ourSourceDict($source)
if {[info exists $vn]} {
set vn
} else {
set vn [uplevel 1 [list source $source]]
}
}
proc load-builtin-actions {} {
::variable ourBuiltinActions
load-actions $ourBuiltinActions
::variable ourRuleList
::variable ourKnownBuiltins [dict create]
foreach rule $ourRuleList {
dict set ourKnownBuiltins $rule 1
}
}
proc is-builtin-rule rule {
::variable ourKnownBuiltins
dict exists $ourKnownBuiltins $rule
}
proc load-actions glob {
foreach fn [glob -nocomplain $glob] {
source $fn
}
}
::variable ourBuiltinActions [file dirname [info script]]/action/*.tcl
}