summaryrefslogtreecommitdiffstats
path: root/gnats-model.lua
diff options
context:
space:
mode:
Diffstat (limited to 'gnats-model.lua')
-rw-r--r--gnats-model.lua498
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
+