diff options
Diffstat (limited to 'gnats-model.lua')
-rw-r--r-- | gnats-model.lua | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/gnats-model.lua b/gnats-model.lua new file mode 100644 index 0000000..28b9d9e --- /dev/null +++ b/gnats-model.lua @@ -0,0 +1,498 @@ +module(..., package.seeall) + +-- Load libraries +require("fs") +require("procps") +require("date") +require("getopts") +require("format") +require("posix") +require("daemoncontrol") +require("validator") +require("socket") +require("processinfo") + +-- Set variables +local configfile = "" +local processname = "gnats" +local packagename = "gnats" +local baseurl = "/etc/gnats" --No trailing / +local gnatsopts = " -H dev.alpinelinux.org " +-- constants +local SECT_HEADER = 1 +local SECT_SFIELDS = 2 +local SECT_MFIELDS = 3 +local header = {} +local sfields = {} +local mfields = {} +local fields_single = { + ["number"] = "", ["category"] = "", ["synopsis"] = "", + ["confidential"] = "", ["severity"] = "", ["priority"] = "", + ["responsible"] = "", ["state"] = "", ["quarter"] = "", + ["keywords"] = "", ["date_required"] = "", ["class"] = "", + ["submitter_id"] = "", ["arrival_date"] = "", ["closed_date"] = "", + ["last_modified"] = "", ["originator"] = "", ["release"] = "", + ["notify_list"] = "" +} + +local fields_multiple = { + ["organization"]="", ["environment"]="", ["description"]="", + ["how_to_repeat"]="", ["fix"]="", ["release_note"]="", + ["audit_trail"]="", ["unformatted"]="" } + +local descr = { +} + +-- ################################################################################ +-- LOCAL FUNCTIONS + +-- Function to recursively inserts all filenames in a dir into an array +local function recursedir(path, filearray) + local k,v + for k,v in pairs(posix.dir(path) or {}) do + -- Ignore files that begins with a '.' + if not string.match(v, "^%.") then + local f = path .. "/" .. v + -- If subfolder exists, list files in this subfolder + if (posix.stat(f).type == "directory") then + recursedir(f, filearray) + else + table.insert(filearray, f) + end + end + end +end + +-- ################################################################################ +-- LOCAL GNATS SPECIFIC FUNCTIONS + +local function process_header(line) + local k, v + if string.find(line, "^%s*$") then + return 1 + end + + k, v = string.match(line, "^([%a%d_.-]+): (.*)$") + if k then + header[string.lower(k:gsub("-", "_"))] = v + end + return 0 +end + +local function process_sfields(line) + local k, v, i, j, key + local found = 0 + key, v = string.match(line, "^>([%a-]+):%s*(.*)$") + if key then + k = string.lower(key:gsub("-", "_")) + if fields_single[k] == nil then + return 1 + end + fields_single[k] = v + end + return 0 +end + +local current_mfield = 0 + +local function mfield_add_line(line) + if fields_multiple[current_mfield] == nil then + fields_multiple[current_mfield] = line or "" + else + fields_multiple[current_mfield] = fields_multiple[current_mfield].."\n"..(line or "") + end +end + +local function process_mfields(line) + local k, v = string.match(line, "^>([%a-]+):%s*(.*)$") + if k then + k = string.lower(k:gsub("-", "_")) + if fields_multiple[k] then + current_mfield = k + end + else + v = line + end + mfield_add_line(v) + return 0 +end + +local function get_array(name) + local a = {} + local f = assert(io.popen("/usr/bin/query-pr "..gnatsopts.." --valid-values " .. tostring(name))) + for line in f:lines() do + table.insert(a, line) + end + f:close() + return a +end + +local function get_various_info() + local f = assert(io.popen("/usr/bin/query-pr "..gnatsopts.." -x -q | wc -l")) + local count = f:read("*l") + f:close() + return count +end + +-- ################################################################################ +-- GNATS SPECIFIC FUNCTIONS + + +-- generate an array of select options +function get_select_opt(self, name) + return get_array(name) +end + +-- generate an array of select options +function list_responsible() + local a = {} + local f = assert(io.popen("/usr/bin/query-pr "..gnatsopts.." --list-responsible ")) + for line in f:lines() do + table.insert(a, string.match(line,"^(.-):.*$")) + end + f:close() + return a +end + +function summary() + -- TODO: this func should return an iterator instead of a table + local line + local pr = {} + local prs = {} + local i,k,v + local search_opts = "" + + -- create command line options for quer-pr. For example: + -- + -- --priority low --class sw-bug --category doc --skip-closed + -- + -- Note that --skip-closed has value = "" + -- + for k,v in pairs(search_criteria) do + if v then + search_opts = search_opts.." --"..k.." "..v + end + end + + local f = assert(io.popen("query-pr "..gnatsopts.. (search_opts or ""))) + + i = 0 + for line in f:lines() do + -- if line is empty, insert to array and reset record + if line == "" then + prs[i] = pr + pr = {} + end + -- extract the key/value: + -- >Number: 24 + -- >Category: pending + -- >Synopsis: bug report + -- >Confidential: no + -- >Severity: serious + -- >Priority: medium + -- >Responsible: gnats-admin + -- >State: open + -- >Class: sw-bug + -- >Submitter-Id: unknown + -- >Arrival-Date: Sat Apr 07 14:45:02 +0000 2007 + -- >Originator: Nathan Angelacos <nangel@nothome.org> + -- >Release: + -- + -- >Number: 25 + -- ... + k,v = line:match(">(.-):%s+(.*)") + if k then + -- convert to lowercase and replace '-' with '_' + -- + -- Submitter-Id -> submitter_id + local key = string.lower(k:gsub("-", "_")) + if key == "number" then + i = tonumber(v) + end + if key == "arrival_date" then + local datetable = date.string_to_table(v) + if (tonumber(datetable.month) < 10) then datetable.month = "0" .. tostring(datetable.month) end +-- if (tonumber(datetable.day) < 10) then datetable.day = "0" .. tostring(datetable.day) end + pr['submit_date'] = string.sub(datetable.year,3) .. "/" .. datetable.month .. "/" .. datetable.day + pr['submit_date_in_seconds'] = os.time(datetable) + end + + pr[ key ] = v + end + end + + local prs_sorted = {} + for k,v in pairs(prs) do + table.insert(prs_sorted, v) + end + table.sort(prs_sorted, function(a,b) return (a.submit_date_in_seconds > b.submit_date_in_seconds) end) + + return prs_sorted +end + +-- validate if "value" is a valid field +function validate_field(self, value, field) + -- check if for or field is nil + if field == nil or value == nil then + return nil + end + + -- loop through the array + for k,v in pairs(get_array(field)) do + if value == v then + return value + end + end + return nil +end + +-- validate for insecure chars in field +function validate_textfield(self,value) + if value == nil or value == "" then + return "" + end + return string.match(value, "^[-_%a%d@.]*$") +end + +-- just set the private variable +function set_search_criteria(self, criteria) + search_criteria = criteria +end +-- append an n valid input message +function set_invalid_field(self,field) + table.insert(invalid_input, field) +end + +--[==[ +-- return a table of invald input fields +function get_invalid_inputs() + return invalid_input +end +--]==] + +--[=[ +-- dump the pr +function write_pr(id) + local cmd = "query-pr -F "..gnatsopts.." "..tostring(id) + local f = assert(io.popen(cmd)) + io.write(f:read("*all")) + f:close() +end +--]=] +-- read pr to header, sfields and mfields +function read_pr(self, id) + local cmd = "query-pr -F "..gnatsopts.." "..tostring(id) + local f = assert(io.popen(cmd)) + local line + local section = SECT_HEADER + + for line in f:lines() do + -- read header + if section == SECT_HEADER then + section = section + process_header(line) + end + if section == SECT_SFIELDS then + section = section + process_sfields(line) + end + if section == SECT_MFIELDS then + section = section + process_mfields(line) + end + end + return header, fields_single, fields_multiple +end + +-- ################################################################################ +-- PUBLIC FUNCTIONS + +function startstop_service ( self, action ) + local cmd = action.value + local cmdresult,cmdmessage,cmderror,cmdaction = daemoncontrol.daemoncontrol(processname, cmd) + action.descr=cmdmessage + action.errtxt=cmderror + return cmdresult,action +end + +function valid_filename(self, path) + local files = {} + recursedir(baseurl,files) + for k,v in pairs(files) do + if (v == path) then + return true + end + end + return false, "Not a valid filename!" +end + +function getstatus() + local status = {} + local config = getconfig() + local value, errtxt = processinfo.package_version(packagename) + status.version = cfe({ name = "version", + label="Program version", + value=value, + errtxt=errtxt, + }) + + local files = {} + recursedir(baseurl,files) + status.configfiles = cfe({ name="configfiles", + label="Config files", + type="select", + descr=table.concat(files,"\n"), + option=files, + }) + status.configfiles.size = #status.configfiles.option + + status.numbugs = cfe({ name="numbugs", + label="Still not closed reports", + value=get_various_info(), + }) + + local alpinerelease = fs.read_file_as_array("/etc/alpine-release") or {""} + status.alpinerelease = cfe({ name="alpinerelease", + label="Alpine release", + value=alpinerelease[1], + }) + + status.debug = cfe({ name="debug", + label="DEBUG output", + value=get_various_info(), + }) + + + return status +end + +function get_logfile () + local file = {} + local cmdtxt = "cat /var/log/messages | grep " .. processname + local cmd, error = io.popen(cmdtxt ,r) + local cmdoutput = cmd:read("*a") + cmd:close() + + file["filename"] = cfe({ + name="filename", + label="File name", + value=cmdtxt, + }) + + file["filecontent"] = cfe({ + type="longtext", + name="filecontent", + label="File content", + value=cmdoutput, + }) + + return file +end + +function getconfig() + if (fs.is_file(configfile)) then + return getopts.getoptsfromfile(configfile) + end + return false +end + +function get_filedetails(self,path) + if not (valid_filename(self,path)) then + return false, {['errtxt'] = "Not a valid filename!"} + end + local file = {} + local filedetails = {} + local config = {} + local filenameerrtxt + if (path) and (fs.is_file(path)) then + filedetails = fs.stat(path) + config = getconfig(path) + else + config = {} + config.filename = {} + config["filename"]["errtxt"]="Config file '".. path .. "' is missing!" + end + + file["filename" .. (num or "")] = cfe({ + name="filename" .. (num or ""), + label="File name", + value=path, + errtxt=filenameerrtxt + }) + file["filesize" .. (num or "")] = cfe({ + name="filesize" .. (num or ""), + label="File size", + value=filedetails.size or 0, + }) + file["mtime" .. (num or "")] = cfe({ + name="mtime" .. (num or ""), + label="File date", + value=filedetails.mtime or "---", + }) + file["filecontent" .. (num or "")] = cfe({ + type="longtext", + name="filecontent" .. (num or ""), + label="File content", + value=fs.read_file(path), + }) + + -- Sum all errors into one cfe + local sumerrors = "" + for k,v in pairs(config or {}) do + if (config[k]) and (config[k]["errtxt"]) and (config[k]["errtxt"] ~= "") then + sumerrors = sumerrors .. config[k]["errtxt"] .. "\n" + end + end + if (sumerrors ~= "") then + file["sumerrors" .. (num or "")] = cfe ({ + name="sumerrors" .. (num or ""), + label = "Configuration errors", + errtxt = string.match(sumerrors, "(.-)\n$"), + }) + end + + return true, file +end +function update_filecontent (self, modifications,path) + if not (valid_filename(self,path)) then + return false, {['errtxt'] = "Not a valid filename!"} + end + local file_result,err = fs.write_file(path, format.dostounix(modifications)) + if (err) and (#err > 0) then + return false, err + end + return true, file_result +end + +function sendbug (self, message) + if not (message) then + return false, "No valid message to send" + end + + if not (string.match(string.lower(tostring(message[1])), "^to:")) or + not (string.match(string.lower(tostring(message[2])), "^from:")) then + return false, "Message is malformatted." + end + + local cmdtxt = "/usr/bin/which sendmail" + local cmd, error = io.popen(cmdtxt ,r) + local cmdoutput = cmd:read("*a") + cmd:close() + if not (cmdoutput) then + return false, "Sendmail is not installed!" + end + + local mailtxt = "outgoing_mail" + fs.write_file(mailtxt, table.concat(message , "\n")) + + local cmdtxt = "/usr/sbin/sendmail -oi -t < " .. mailtxt .. " 2>&1" + local cmd, error = io.popen(cmdtxt ,r) + local cmdoutput = cmd:read("*a") + cmd:close() + + local cmd, error = io.popen("/bin/rm -f " .. mailtxt ,r) + cmd:close() + + if (#cmdoutput > 0) then + return false, cmdoutput + end + return true +end + |