diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-03-10 22:45:18 +0200 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-03-24 01:18:13 +0200 |
commit | 7d9c43916b0600ac4879dfe9793eab807a83ab2b (patch) | |
tree | ec54ed64c9a557b6ea4ad88d31138a02d3e0cd04 /aconf/persistence/backends | |
parent | cb6c243dc356ef1d46d7ddb96e6ea6ae007c6cca (diff) | |
download | aconf-7d9c43916b0600ac4879dfe9793eab807a83ab2b.tar.bz2 aconf-7d9c43916b0600ac4879dfe9793eab807a83ab2b.tar.xz |
rename ACF2 to Alpine Configurator (aconf)
Diffstat (limited to 'aconf/persistence/backends')
-rw-r--r-- | aconf/persistence/backends/augeas.lua | 273 | ||||
-rw-r--r-- | aconf/persistence/backends/files.lua | 107 | ||||
-rw-r--r-- | aconf/persistence/backends/json.lua | 79 | ||||
-rw-r--r-- | aconf/persistence/backends/null.lua | 10 | ||||
-rw-r--r-- | aconf/persistence/backends/service.lua | 32 | ||||
-rw-r--r-- | aconf/persistence/backends/volatile.lua | 46 |
6 files changed, 547 insertions, 0 deletions
diff --git a/aconf/persistence/backends/augeas.lua b/aconf/persistence/backends/augeas.lua new file mode 100644 index 0000000..a448e65 --- /dev/null +++ b/aconf/persistence/backends/augeas.lua @@ -0,0 +1,273 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local topology = require('aconf.model.root').topology +local class = require('aconf.object').class +local pth = require('aconf.path') +local tostr = require('aconf.persistence.util').tostring + +local util = require('aconf.util') +local copy = util.copy + + +local stringy = require('stringy') + + +local function array_join(tbl, value) + local res = copy(tbl) + table.insert(res, value) + return res +end + +local function array_without_last(tbl) + local res = copy(tbl) + res[#res] = nil + return res +end + + +local function basename(path) + local res, pred = path:match('^.*/([#%w._-]+)([^/]*)$') + assert(res) + assert(res ~= '#') + assert(pred == '' or pred:match('^%[.+%]$')) + return res +end + +local function append_pred(path, pred) return path..'['..pred..']' end + + +local function key_mode(mode) return mode and stringy.startswith(mode, '@') end + +local function key(mode) + assert(key_mode(mode)) + return mode == '@' and '.' or mode:sub(2, -1) +end + +local function append_key_pred(path, mode, value) + return append_pred(path, key(mode).." = '"..value.."'") +end + + +local function conv_path(path) + local res = '/files' + if #path == 0 then return res, nil, {} end + + path = copy(path) + local mode + local keys = {} + + repeat + local comp = path[1] + + if comp == '#' or key_mode(comp) then + assert(not mode) + mode = comp + elseif not mode then + res = res..'/'..comp + keys = {} + else + if mode == '#' then + assert(type(comp) == 'number') + res = append_pred(res, comp) + else + assert(type(comp) == 'string' and comp:match('^[%w %_-%.]+$')) + res = append_key_pred(res, mode, comp) + table.insert(keys, key(mode)) + end + mode = nil + end + + table.remove(path, 1) + until #path == 0 + + return res, mode, keys +end + + +local function aug_top(path) + path = copy(path) + table.insert(path, 1, 'augeas') + return topology(path) +end + + +local backend = class() + +function backend:init() self.aug = require('augeas').init() end + +function backend:get(path, top) + local apath, mode, keys = conv_path(path) + local existence = top == true + if existence or not top then top = aug_top(path) end + + local tpe = top and top.type + local leaf = tpe ~= 'table' and not mode + + local matches = self.aug:match(apath..(not leaf and not mode and '/*' or '')) + + if #matches == 0 and not mode then return end + + if mode and #path > 1 and not self:get( + array_without_last(array_without_last(path)), true + ) then + return + end + + if not tpe and not mode then + if #matches > 1 then + leaf = false + mode = '#' + else + local children = self.aug:match(apath..'/*') + if #children > 0 then + leaf = false + matches = children + end + end + end + + if leaf then + assert(#matches == 1) + return self.aug:get(apath) + end + + if existence then return true end + + local names = {} + + for i, child in ipairs(matches) do + local name + if not mode then + name = basename(child) + name = tonumber(name) or name + if util.contains(keys, name) then name = nil end + elseif mode == '#' then name = i + else + name = self.aug:get(child..(mode == '@' and '' or '/'..key(mode))) + end + + if name and self:get(array_join(path, name), true) then + names[name] = true + end + end + + return util.keys(names) +end + +function backend:set(mods) + local gc = {} + + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + + local function insert(path, new) + local apath, mode, keys = conv_path(path) + if mode then path[#path] = nil end + if #path == 0 then return apath, keys end + local name = path[#path] + + local parent = array_without_last(path) + local ppath, pmode = conv_path(parent) + + if pmode then + gc[pth.join(unpack(array_without_last(parent)))] = false + end + + if pmode == '#' then + local count = #self.aug:match(ppath) + while count < name do + insert(parent, true) + count = count + 1 + end + return apath, keys + end + + local matches = self.aug:match(apath) + local count = #matches + + if count > 0 and not new then return apath, keys end + + if key_mode(pmode) then + apath = pmode == '@' and append_key_pred( + ppath, '@', '' + ) or append_pred(ppath, 'count('..key(pmode)..') = 0') + + matches = self.aug:match(apath) + assert(#matches < 2) + apath = matches[1] or insert(parent, true) + + local key = key(pmode) + self.aug:set(apath..'/'..key, name) + + return apath, keys + end + + if #matches == 0 then + matches = self.aug:match(ppath..'/*') + + local function order(path) + local top = aug_top(path) + return top and top.order + end + local ord = order(path) + + for _, sibling in ipairs(matches) do + local sord = order(array_join(parent, basename(sibling))) + if sord and sord > ord then + self.aug:insert(sibling, name, true) + return apath, keys + end + end + end + + if #matches == 0 then + if new then self.aug:set(apath, nil) end + return apath, keys + end + + self.aug:insert(matches[#matches], name) + return append_pred(apath, count + 1), keys + end + + local apath, keys = insert(path) + local is_table = type(value) == 'table' + + if not (is_table or util.contains(keys, '.')) then + self.aug:set(apath, value ~= nil and tostr(value) or nil) + end + + util.setdefault(gc, pth.join(unpack(path)), true) + end + + for path, _ in pairs(gc) do + local p = pth.split(path) + while #p > 0 do + local value = self:get(p) + + if ( + type(value) == 'string' and value ~= '' + ) or ( + type(value) == 'table' and #value > 0 + ) then + break + end + + if gc[pth.join(unpack(p))] ~= false then self.aug:rm(conv_path(p)) end + p[#p] = nil + end + end + + if self.aug:save() ~= 0 then + print('Augeas save failed') + for _, ep in ipairs(self.aug:match('/augeas//error')) do + print(ep, self.aug:get(ep)) + end + assert(false) + end +end + + +return backend diff --git a/aconf/persistence/backends/files.lua b/aconf/persistence/backends/files.lua new file mode 100644 index 0000000..e19763d --- /dev/null +++ b/aconf/persistence/backends/files.lua @@ -0,0 +1,107 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local topology = require('aconf.model.root').topology +local pth = require('aconf.path') +local util = require('aconf.persistence.util') +local copy = require('aconf.util').copy + +local posix = require('posix') +local stringy = require('stringy') + + +local function get_scope(top) + if not top or top.type ~= 'reference' or not pth.is_unique(top.scope) then + return + end + + return stringy.startswith( + top.scope, '/files/' + ) and top.scope:sub(7, -1) or nil +end + + +local backend = require('aconf.object').class() + +-- TODO cache expiration +function backend:init() self.cache = {} end + +function backend:get(path, top) + local name = pth.join('/', unpack(path)) + + if not self.cache[name] then + local t = posix.stat(name, 'type') + if not t then return end + + if t == 'regular' then + self.cache[name] = util.read_file(name) + + elseif t == 'link' then + -- TODO handle relative symlinks + local target = posix.readlink(name) + assert(target) + + local scope = get_scope(top) + assert(scope) + scope = scope..'/' + + local slen = scope:len() + assert(target:sub(1, slen) == scope) + return target:sub(slen + 1, -1) + + elseif t == 'directory' then + local res = {} + for _, fname in ipairs(posix.dir(name)) do + if not ({['.']=true, ['..']=true})[fname] then + table.insert(res, pth.name(fname)) + end + end + return res + + else error('Unsupported file type: '..name) end + end + + return self.cache[name] +end + +function backend:set(mods) + for _, mod in pairs(mods) do + local path, value = unpack(mod) + local name = pth.join('/', unpack(path)) + + if value == nil then + print('DEL', name) + + local t = posix.stat(name, 'type') + if t == 'directory' then + assert(posix.rmdir(name)) + elseif t then assert(os.remove(name)) end + + self.cache[name] = nil + + elseif type(value) == 'table' then + assert(posix.mkdir(name)) + + else + local scope = get_scope(topology('/files'..name)) + + if scope then + -- TODO use relative symlink + os.remove(name) + assert(posix.link(pth.to_absolute(value, scope), name, true)) + + else + local file = util.open_file(name, 'w') + file:write(util.tostring(value)) + file:close() + + self.cache[name] = value + end + end + end +end + + +return backend diff --git a/aconf/persistence/backends/json.lua b/aconf/persistence/backends/json.lua new file mode 100644 index 0000000..607315a --- /dev/null +++ b/aconf/persistence/backends/json.lua @@ -0,0 +1,79 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local pth = require('aconf.path') +local Cache = require('aconf.persistence.backends.volatile') +local util = require('aconf.persistence.util') +local copy = require('aconf.util').copy + +local json = require('cjson') +local posix = require('posix') + + +local backend = require('aconf.object').class() + +function backend:init() + -- TODO cache expiration + self.cache = {} + self.dirty = {} +end + +function backend:split_path(path) + local fpath = copy(path) + local jpath = {} + local res + + while #fpath > 0 do + local fp = pth.join('/', unpack(fpath)) + if self.cache[fp] then return fp, jpath end + table.insert(jpath, 1, fpath[#fpath]) + table.remove(fpath) + end + + fpath = '/' + + while true do + fpath = pth.join(fpath, jpath[1]) + table.remove(jpath, 1) + + local t = posix.stat(fpath, 'type') + if t == 'link' then t = posix.stat(posix.readlink(fpath), 'type') end + if not t or not ({directory=true, regular=true})[t] then + error('File or directory does not exist: '..fpath) + end + + if t == 'regular' then return fpath, jpath end + + assert(#jpath > 0) + end +end + +function backend:get(path, top) + local fpath, jpath = self:split_path(path) + if not self.cache[fpath] then + self.cache[fpath] = Cache(json.decode(util.read_file(fpath))) + end + return self.cache[fpath]:get(jpath, top) +end + +function backend:set(mods) + local dirty = {} + + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + local fpath, jpath = self:split_path(path) + self.cache[fpath]:_set(jpath, value) + dirty[fpath] = true + end + + for path, _ in pairs(dirty) do + local file = util.open_file(path, 'w') + file:write(json.encode(self.cache[path]:_get{})) + file:close() + end +end + + +return backend diff --git a/aconf/persistence/backends/null.lua b/aconf/persistence/backends/null.lua new file mode 100644 index 0000000..770e5e8 --- /dev/null +++ b/aconf/persistence/backends/null.lua @@ -0,0 +1,10 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local backend = require('aconf.object').class() +function backend:get(path, top) if #path == 0 then return {} end end +function backend:set(mods) end + +return backend diff --git a/aconf/persistence/backends/service.lua b/aconf/persistence/backends/service.lua new file mode 100644 index 0000000..4569ce8 --- /dev/null +++ b/aconf/persistence/backends/service.lua @@ -0,0 +1,32 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local rc = require('rc') +local stringy = require('stringy') + + +local backend = require('aconf.object').class() + +function backend:get(path, top) + if #path == 1 then return {'enabled', 'status'} end + assert(#path == 2) + local status = rc.service_status(path[1]) + if path[2] == 'status' then return status end + if path[2] == 'enabled' then return stringy.startswith(status, 'start') end +end + +function backend:set(mods) + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + assert(#path == 2 and path[2] == 'enabled') + + local name = path[1] + if value then rc.service_add(name) + else rc.service_delete(name) end + os.execute('rc-service '..name..' '..(value and 'start' or 'stop')) + end +end + +return backend diff --git a/aconf/persistence/backends/volatile.lua b/aconf/persistence/backends/volatile.lua new file mode 100644 index 0000000..7016a9b --- /dev/null +++ b/aconf/persistence/backends/volatile.lua @@ -0,0 +1,46 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local util = require('aconf.util') + + +local backend = require('aconf.object').class() + +function backend:init(data) self.data = data or {} end + +function backend:_get(path) + local res = self.data + for _, comp in ipairs(path) do + if res == nil then return end + assert(type(res) == 'table') + res = res[comp] + end + return res +end + +function backend:get(path, top) + local res = self:_get(path) + return type(res) == 'table' and util.keys(res) or res +end + +function backend:_set(path, value) + if type(value) == 'table' then value = {} end + + if #path == 0 then self.data = value + + else + local comps = util.copy(path) + local name = comps[#comps] + table.remove(comps) + self:_get(comps)[name] = value + end +end + +function backend:set(mods) + for _, mod in ipairs(mods) do self:_set(unpack(mod)) end +end + + +return backend |