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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
|
local SL = require("scriptlee")
local newHttpClient = SL.newHttpClient
local newShellcmd = SL.newShellcmd
local newSqlite = SL.newSqlite
local objectSeal = SL.objectSeal
local parseJSON = SL.parseJSON
local sleep = SL.posix.sleep
local startOrExecute = SL.reactor.startOrExecute
SL = nil
local log = io.stdout
function printHelp()
io.write("\n"
.." WARN: This is experimental.\n"
.." \n"
.." Options:\n"
.." --backendHost <inaddr> (eg \"localhost\")\n"
.." --backendPort <int> (eg 80)\n"
.." --backendPath <str> (eg \"/houston\")\n"
.." --sshPort <int> (eg 22)\n"
.." --sshUser <str> (eg \"eddieuser\")\n"
.." --state <path> (eg \"path/to/state\")\n"
.." \n"
.." --exportLatestStatus\n"
.." \n")
end
function parseArgs( app )
app.backendPort = 80
app.statePath = ":memory:"
local iA = 0
::nextArg::
iA = iA + 1
local arg = _ENV.arg[iA]
if not arg then
goto verifyResult
elseif arg == "--help" then
app.isHelp = true return 0
elseif arg == "--backendHost" then
iA = iA + 1; arg = _ENV.arg[iA]
if not arg then log:write("EINVAL: --backendHost needs value\n")return end
app.backendHost = arg
elseif arg == "--backendPort" then
iA = iA + 1; arg = _ENV.arg[iA]
if not arg then log:write("EINVAL: --backendPort needs value\n")return end
app.backendHost = arg
elseif arg == "--backendPath" then
iA = iA + 1; arg = _ENV.arg[iA]
if not arg then log:write("EINVAL: --backendPath needs value\n")return end
app.backendPath = arg
elseif arg == "--sshPort" then
iA = iA + 1; arg = _ENV.arg[iA]
if not arg then log:write("EINVAL: --sshPort needs value\n")return end
app.sshPort = arg
elseif arg == "--sshUser" then
iA = iA + 1; arg = _ENV.arg[iA]
if not arg then log:write("EINVAL: --sshUser needs value\n")return end
app.sshUser = arg
elseif arg == "--state" then
iA = iA + 1; arg = _ENV.arg[iA]
if not arg then log:write("EINVAL: --state needs value\n")return end
app.statePath = arg
elseif arg == "--exportLatestStatus" then
app.exportLatestStatus = true
else
log:write("EINVAL: ".. arg .."\n")return
end
goto nextArg
::verifyResult::
if app.exportLatestStatus then
if not app.statePath then log:write("EINVAL: --state missing\n")return end
else
if not app.backendHost then log:write("EINVAL: --backendHost missing\n")return end
if not app.backendPath then log:write("EINVAL: --backendPath missing\n")return end
if app.backendPath:find("^C:.") then log:write("[WARN ] MSYS_NO_PATHCONV=1 likely missing? ".. app.backendPath.."\n") end
end
return 0
end
function removeCompletedEddies( app )
local db = getStateDb(app)
local rs = db:prepare("SELECT eddieName FROM Eddie"
.." JOIN EddieLog ON Eddie.id = eddieId"
.." WHERE status = \"OK\";"):execute()
local eddieNamesToRemoveSet = {}
while rs:next() do
assert(rs:type(1) == "TEXT", rs:type(1))
assert(rs:name(1) == "eddieName", rs:name(1))
local eddieName = rs:value(1)
eddieNamesToRemoveSet[eddieName] = true
end
local oldEddies = app.eddies
app.eddies = {}
local numKeep, numDrop = 0, 0
for _, eddie in pairs(oldEddies) do
if not eddieNamesToRemoveSet[eddie.eddieName] then
--log:write("[DEBUG] Keep '".. eddie.eddieName .."'\n")
numKeep = numKeep + 1
table.insert(app.eddies, eddie)
else
numDrop = numDrop + 1
--log:write("[DEBUG] Drop '".. eddie.eddieName .."': Already done\n")
end
end
log:write("[DEBUG] todo: ".. numKeep ..", done: ".. numDrop .."\n")
end
function setEddieStatus( app, statusStr, eddieName, stderrStr, stdoutStr )
assert(type(app) == "table")
assert(type(eddieName) == "string")
assert(statusStr == "OK" or statusStr == "ERROR")
log:write("[DEBUG] setEddieStatus(".. eddieName ..", ".. statusStr ..")\n")
local db = getStateDb(app)
local stmt = db:prepare("INSERT INTO Eddie(eddieName)VALUES($eddieName);")
stmt:bind("$eddieName", eddieName)
local ok, emsg = xpcall(function()
stmt:execute()
end, debug.traceback)
if not ok and not emsg:find("UNIQUE constraint failed: Eddie.eddieName") then
error(emsg)
end
local stmt = db:prepare("INSERT INTO EddieLog('when',eddieId,status,stderr,stdout)"
.."VALUES($when, (SELECT rowid FROM Eddie WHERE eddieName = $eddieName), $status, $stderr, $stdout)")
stmt:reset()
stmt:bind("$when", os.date("!%Y-%m-%dT%H:%M:%S+00:00"))
stmt:bind("$eddieName", eddieName)
stmt:bind("$status", statusStr)
stmt:bind("$stderr", stderrStr)
stmt:bind("$stdout", stdoutStr)
stmt:execute()
end
function getStateDb( app )
if not app.stateDb then
app.stateDb = newSqlite{ database = app.statePath }
app.stateDb:prepare("CREATE TABLE IF NOT EXISTS Eddie(\n"
.." id INTEGER PRIMARY KEY,\n"
.." eddieName TEXT UNIQUE NOT NULL)\n"
..";"):execute()
app.stateDb:prepare("CREATE TABLE IF NOT EXISTS EddieLog(\n"
.." id INTEGER PRIMARY KEY,\n"
.." 'when' TEXT NOT NULL,\n"
.." eddieId INT NOT NULL,\n"
.." status TEXT, -- OneOf OK, ERROR\n"
.." stderr TEXT NOT NULL,\n"
.." stdout TEXT NOT NULL)\n"
..";\n"):execute()
end
return app.stateDb
end
function loadEddies( app )
local httpClient = newHttpClient{}
local req = objectSeal{
base = false,
method = "GET",
path = app.backendPath .."/data/preflux/inventory",
rspCode = false,
rspBody = false,
isDone = false,
}
req.base = httpClient:request{
cls = req,
host = app.backendHost, port = app.backendPort,
method = req.method, url = req.path,
onRspHdr = function( rspHdr, req )
req.rspCode = rspHdr.status
if rspHdr.status ~= 200 then
log:write(".-----------------------------------------\n")
log:write("| ".. req.method .." ".. req.path .."\n")
log:write("| Host: ".. app.backendHost ..":".. app.backendPort .."\n")
log:write("+-----------------------------------------\n")
log:write("| ".. rspHdr.proto .." ".. rspHdr.status .." ".. rspHdr.phrase .."\n")
for i,h in ipairs(rspHdr.headers) do
log:write("| ".. h[1] ..": ".. h[2] .."\n")
end
log:write("| \n")
end
end,
onRspChunk = function( buf, req )
if req.rspCode ~= 200 then log:write("| ".. buf:gsub("\n", "\n| ")) return end
if buf then
if not req.rspBody then req.rspBody = buf
else req.rspBody = req.rspBody .. buf end
end
end,
onRspEnd = function( req )
if req.rspCode ~= 200 then log:write("\n'-----------------------------------------\n") end
req.isDone = true
end,
}
req.base:closeSnk()
assert(req.isDone)
if req.rspCode ~= 200 then log:write("ERROR: Couldn't load eddies\n")return end
local prefluxInventory = parseJSON(req.rspBody)
local eddies = {}
for eddieName, detail in pairs(prefluxInventory.hosts) do
table.insert(eddies, objectSeal{
eddieName = eddieName,
lastSeen = detail.lastSeen:value(),
})
end
app.eddies = eddies
end
function makeWhateverWithEddies( app )
local ssh = "C:/Users/fankhauseand/.opt/gitPortable-2.27.0-x64/usr/bin/ssh.exe"
local cmdLinePre = ssh .." -oConnectTimeout=3 -oRemoteCommand=none"
if app.sshPort then cmdLinePre = cmdLinePre .." -p".. app.sshPort end
if app.sshUser then cmdLinePre = cmdLinePre .." \"-oUser=".. app.sshUser .."\"" end
for k,eddie in pairs(app.eddies) do
local eddieName = eddie.eddieName
local isEddie = eddieName:find("^eddie%d%d%d%d%d$")
local isTeddie = eddieName:find("^teddie%d%d$")
local isVted = eddieName:find("^vted%d%d$")
local isAws = eddieName:find("^10.117.%d+.%d+$")
local isDevMachine = eddieName:find("^w00[a-z0-9][a-z0-9][a-z0-9]$")
if isAws or isDevMachine or isVted then
log:write("[DEBUG] Skip \"".. eddieName .."\"\n")
goto nextEddie
end
assert(isEddie or isTeddie, eddieName or"nil")
local okMarker = "OK_".. math.random(10000000, 99999999) .."wCAkgQQA2AJAzAIA"
local cmdLine = cmdLinePre .." ".. eddieName
.." -- \"true"
.. " && if test \"".. eddieName .."\" != \"$(hostname|sed 's,.pnet.ch$,,'); then true\""
.. " && echo WrongHost expected=".. eddieName .." actual=$(hostname|sed 's,.pnet.ch$,,') && false"
.. " ;fi"
.. " && echo hostname=$(hostname|sed 's,.pnet.ch,,')"
.. " && echo stage=${PAISA_ENV:?}"
.. " && echo Scan /data/instances/default/??ARTIFACT_BASE_DIR?"
--[[report only]]
--.. " && test -e /data/instances/default/??ARTIFACT_BASE_DIR? && ls -Ahl /data/instances/default/??ARTIFACT_BASE_DIR?"
--[[Find un-/affected eddies]]
.. " && if test -e /data/instances/default/??ARTIFACT_BASE_DIR?; then true"
.. " ;else true"
.. " && echo ".. okMarker
.. " ;fi"
--[[DELETE them]]
--.. " && if test -e /data/instances/default/??ARTIFACT_BASE_DIR?; then true"
--.. " && find /data/instances/default/??ARTIFACT_BASE_DIR? -type d -mtime +420 -print -delete"
--.. " ;fi"
--.. " && echo ".. okMarker ..""
--[[]]
.. " \""
log:write("\n")
log:write("[INFO ] Try ".. eddieName .." ...\n")
log:write("[DEBUG] ".. cmdLine.."\n")
--log:write("[DEBUG] sleep ...\n")sleep(3)
local isStdioDone, isSuccess, stderrStr, stdoutStr = false, false, "", ""
local cmd = newShellcmd{
cmdLine = cmdLine,
onStdout = function( buf )
if buf then
if buf:find("\n"..okMarker.."\n",0,true) then isSuccess = true end
stdoutStr = stdoutStr .. buf
io.stdout:write(buf)
else isStdioDone = true end
end,
onStderr = function( buf )
stderrStr = buf and stderrStr .. buf or stderrStr
io.stderr:write(buf or"")
end,
}
cmd:start()
cmd:closeSnk()
local exitCode, signal = cmd:join(42)
if exitCode ~= 0 and signal ~= nil then
log:write("[WARN ] code="..tostring(exitCode)..", signal="..tostring(signal).."\n")
end
while not isStdioDone do sleep(0.042) end
-- Analyze outcome
if not isSuccess then
setEddieStatus(app, "ERROR", eddieName, stderrStr, stdoutStr)
goto nextEddie
end
setEddieStatus(app, "OK", eddieName, stderrStr, stdoutStr)
::nextEddie::
end
end
function sortEddiesMostRecentlySeenFirst( app )
table.sort(app.eddies, function(a, b) return a.lastSeen > b.lastSeen end)
end
function quoteCsvVal( v )
local typ = type(v)
if false then
elseif typ == "string" then
if v:find("[\"\r\n]",0,false) then
v = '"'.. v:gsub('"', '""') ..'"'
end
else error("TODO_a928rzuga98oirh "..typ)end
return v
end
function exportLatestStatus( app )
local snk = io.stdout
local db = getStateDb(app)
local stmt = db:prepare("SELECT \"when\",eddieName,status,stderr,stdout FROM EddieLog"
.." JOIN Eddie ON Eddie.id = eddieId"
.." ORDER BY eddieId,[when]"
.." ;")
rs = stmt:execute()
snk:write("c;when;eddieName;status;stderr;stdout\n")
local prevWhen, prevEddieName, prevStatus, prevStderr, prevStdout
local qt = quoteCsvVal
while rs:next() do
local when , eddieName , status , stderr , stdout
= rs:value(1), rs:value(2), rs:value(3), rs:value(4), rs:value(5)
--log:write("[DEBUG] "..tostring(when).." "..tostring(eddieName).." "..tostring(status).."\n")
assert(when and eddieName and status and stderr and stdout)
if eddieName == prevEddieName then
if not prevWhen or when > prevWhen then
--log:write("[DEBUG] ".. when .." ".. eddieName .." take\n")
goto assignPrevThenNextEntry
else
--log:write("[DEBUG] ".. when .." ".. eddieName .." obsolete\n")
goto nextEntry
end
elseif prevEddieName then
--log:write("[DEBUG] ".. when .." ".. eddieName .." Eddie complete\n")
snk:write("r;".. qt(when) ..";".. qt(eddieName) ..";".. qt(status) ..";".. qt(stderr) ..";".. qt(stdout) .."\n")
else
--log:write("[DEBUG] ".. when .." ".. eddieName .." Another eddie\n")
goto assignPrevThenNextEntry
end
::assignPrevThenNextEntry::
--[[]] prevWhen, prevEddieName, prevStatus, prevStderr, prevStdout
= when , eddieName , status , stderr , stdout
::nextEntry::
end
snk:write("t;status;OK\n")
end
function run( app )
if app.exportLatestStatus then
exportLatestStatus(app)
return
end
loadEddies(app)
assert(app.eddies)
removeCompletedEddies(app)
sortEddiesMostRecentlySeenFirst(app)
makeWhateverWithEddies(app)
end
function main()
local app = objectSeal{
isHelp = false,
backendHost = false,
backendPort = false,
backendPath = false,
sshPort = false,
sshUser = false,
statePath = false,
stateDb = false,
exportLatestStatus = false,
eddies = false,
}
if parseArgs(app) ~= 0 then os.exit(1) end
if app.isHelp then printHelp() return end
run(app)
end
startOrExecute(main)
|