diff options
author | Andreas Fankhauser hiddenalpha.ch | 2024-05-12 12:55:07 +0200 |
---|---|---|
committer | Andreas Fankhauser hiddenalpha.ch | 2024-05-12 12:55:07 +0200 |
commit | 5cc4c2b95df9bf6998d92496ca0e0064314fba39 (patch) | |
tree | ac7d979f81a86703508ff23e2694a6b4d6bfe4d5 /src/main/lua/paisa-fleet/RmArtifactBaseDir.lua | |
parent | 1dc9f8c9db499a326ceee6cb3b48878ee17ff9fa (diff) | |
parent | 95d934e8e3918832c03f05b2fc32cb5d5272cb83 (diff) | |
download | UnspecifiedGarbage-5cc4c2b95df9bf6998d92496ca0e0064314fba39.zip UnspecifiedGarbage-5cc4c2b95df9bf6998d92496ca0e0064314fba39.tar.gz |
Merge remote-tracking branch 'ha-public/master' into wip-LatitudeReSetup-20230620wip-LatitudeReSetup-20230620
Diffstat (limited to 'src/main/lua/paisa-fleet/RmArtifactBaseDir.lua')
-rw-r--r-- | src/main/lua/paisa-fleet/RmArtifactBaseDir.lua | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/src/main/lua/paisa-fleet/RmArtifactBaseDir.lua b/src/main/lua/paisa-fleet/RmArtifactBaseDir.lua new file mode 100644 index 0000000..949d1fe --- /dev/null +++ b/src/main/lua/paisa-fleet/RmArtifactBaseDir.lua @@ -0,0 +1,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) + |