Software / code / prosody
Comparison
tools/migration/prosody-migrator.lua @ 10003:4d702f0c6273
migrator: Rewrite to use storage modules
This allows migrating to and from any storage module that supports the
right methods. Based on experimental mod_migrate work.
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Sun, 05 May 2019 21:32:34 +0200 |
| parent | 8062:739bb455cafd |
| child | 10004:e057e8318130 |
comparison
equal
deleted
inserted
replaced
| 10002:b6b5b9d7417d | 10003:4d702f0c6273 |
|---|---|
| 1 #!/usr/bin/env lua | 1 #!/usr/bin/env lua |
| 2 | 2 |
| 3 CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR"); | 3 CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); |
| 4 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR"); | 4 CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); |
| 5 CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); | |
| 6 CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR"); | |
| 5 | 7 |
| 6 -- Substitute ~ with path to home directory in paths | 8 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- |
| 7 if CFG_CONFIGDIR then | 9 |
| 8 CFG_CONFIGDIR = CFG_CONFIGDIR:gsub("^~", os.getenv("HOME")); | 10 local function is_relative(path) |
| 11 local path_sep = package.config:sub(1,1); | |
| 12 return ((path_sep == "/" and path:sub(1,1) ~= "/") | |
| 13 or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\"))) | |
| 9 end | 14 end |
| 10 | 15 |
| 16 -- Tell Lua where to find our libraries | |
| 11 if CFG_SOURCEDIR then | 17 if CFG_SOURCEDIR then |
| 12 CFG_SOURCEDIR = CFG_SOURCEDIR:gsub("^~", os.getenv("HOME")); | 18 local function filter_relative_paths(path) |
| 19 if is_relative(path) then return ""; end | |
| 20 end | |
| 21 local function sanitise_paths(paths) | |
| 22 return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";")); | |
| 23 end | |
| 24 package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path); | |
| 25 package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath); | |
| 26 end | |
| 27 | |
| 28 -- Substitute ~ with path to home directory in data path | |
| 29 if CFG_DATADIR then | |
| 30 if os.getenv("HOME") then | |
| 31 CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME")); | |
| 32 end | |
| 13 end | 33 end |
| 14 | 34 |
| 15 local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua"; | 35 local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua"; |
| 36 | |
| 37 local startup = require "util.startup"; | |
| 38 startup.prosodyctl(); | |
| 39 -- TODO startup.migrator ? | |
| 16 | 40 |
| 17 -- Command-line parsing | 41 -- Command-line parsing |
| 18 local options = {}; | 42 local options = {}; |
| 19 local i = 1; | 43 local i = 1; |
| 20 while arg[i] do | 44 while arg[i] do |
| 27 else | 51 else |
| 28 i = i + 1; | 52 i = i + 1; |
| 29 end | 53 end |
| 30 end | 54 end |
| 31 | 55 |
| 32 if CFG_SOURCEDIR then | |
| 33 package.path = CFG_SOURCEDIR.."/?.lua;"..package.path; | |
| 34 package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath; | |
| 35 else | |
| 36 package.path = "../../?.lua;"..package.path | |
| 37 package.cpath = "../../?.so;"..package.cpath | |
| 38 end | |
| 39 | 56 |
| 40 local envloadfile = require "util.envload".envloadfile; | 57 local envloadfile = require "util.envload".envloadfile; |
| 41 | 58 |
| 42 local config_file = options.config or default_config; | 59 local config_file = options.config or default_config; |
| 43 local from_store = arg[1] or "input"; | 60 local from_store = arg[1] or "input"; |
| 67 if not config[to_store] then | 84 if not config[to_store] then |
| 68 have_err = true; | 85 have_err = true; |
| 69 print("Error: Output store '"..to_store.."' not found in the config file."); | 86 print("Error: Output store '"..to_store.."' not found in the config file."); |
| 70 end | 87 end |
| 71 | 88 |
| 72 function load_store_handler(name) | 89 for store, conf in pairs(config) do -- COMPAT |
| 73 local store_type = config[name].type; | 90 if conf.type == "prosody_files" then |
| 74 if not store_type then | 91 conf.type = "internal"; |
| 75 print("Error: "..name.." store type not specified in the config file"); | 92 elseif conf.type == "prosody_sql" then |
| 76 return false; | 93 conf.type = "sql"; |
| 77 else | |
| 78 local ok, err = pcall(require, "migrator."..store_type); | |
| 79 if not ok then | |
| 80 print(("Error: Failed to initialize '%s' store:\n\t%s") | |
| 81 :format(name, err)); | |
| 82 return false; | |
| 83 end | |
| 84 end | 94 end |
| 85 return true; | |
| 86 end | 95 end |
| 87 | |
| 88 have_err = have_err or not(load_store_handler(from_store, "input") and load_store_handler(to_store, "output")); | |
| 89 | 96 |
| 90 if have_err then | 97 if have_err then |
| 91 print(""); | 98 print(""); |
| 92 print("Usage: "..arg[0].." FROM_STORE TO_STORE"); | 99 print("Usage: "..arg[0].." FROM_STORE TO_STORE"); |
| 93 print("If no stores are specified, 'input' and 'output' are used."); | 100 print("If no stores are specified, 'input' and 'output' are used."); |
| 99 end | 106 end |
| 100 print(""); | 107 print(""); |
| 101 os.exit(1); | 108 os.exit(1); |
| 102 end | 109 end |
| 103 | 110 |
| 104 local itype = config[from_store].type; | 111 local async = require "util.async"; |
| 105 local otype = config[to_store].type; | 112 local server = require "net.server"; |
| 106 local reader = require("migrator."..itype).reader(config[from_store]); | 113 local watchers = { |
| 107 local writer = require("migrator."..otype).writer(config[to_store]); | 114 error = function (_, err) |
| 115 error(err); | |
| 116 end; | |
| 117 waiting = function () | |
| 118 server.loop(); | |
| 119 end; | |
| 120 }; | |
| 108 | 121 |
| 109 local json = require "util.json"; | 122 local cm = require "core.configmanager"; |
| 123 local hm = require "core.hostmanager"; | |
| 124 local sm = require "core.storagemanager"; | |
| 125 local um = require "core.usermanager"; | |
| 126 | |
| 127 local function users(store, host) | |
| 128 if store.users then | |
| 129 return store:users(); | |
| 130 else | |
| 131 return um.users(host); | |
| 132 end | |
| 133 end | |
| 134 | |
| 135 local function prepare_config(host, conf) | |
| 136 if conf.type == "internal" then | |
| 137 sm.olddm.set_data_path(conf.path or prosody.paths.data); | |
| 138 elseif conf.type == "sql" then | |
| 139 cm.set(host, "sql", conf); | |
| 140 end | |
| 141 end | |
| 142 | |
| 143 local function get_driver(host, conf) | |
| 144 prepare_config(host, conf); | |
| 145 return assert(sm.load_driver(host, conf.type)); | |
| 146 end | |
| 147 | |
| 148 local migration_runner = async.runner(function (job) | |
| 149 for host, stores in pairs(job.input.hosts) do | |
| 150 prosody.hosts[host] = startup.make_host(host); | |
| 151 sm.initialize_host(host); | |
| 152 um.initialize_host(host); | |
| 153 | |
| 154 local input_driver = get_driver(host, job.input); | |
| 155 | |
| 156 local output_driver = get_driver(host, job.output); | |
| 157 | |
| 158 for _, store in ipairs(stores) do | |
| 159 local p, typ = store:match("()%-(%w+)$"); | |
| 160 if typ then store = store:sub(1, p-1); else typ = "keyval"; end | |
| 161 log("info", "Migrating host %s store %s (%s)", host, store, typ); | |
| 162 | |
| 163 local origin = assert(input_driver:open(store, typ)); | |
| 164 local destination = assert(output_driver:open(store, typ)); | |
| 165 | |
| 166 if typ == "keyval" then -- host data | |
| 167 local data, err = origin:get(nil); | |
| 168 assert(not err, err); | |
| 169 assert(destination:set(nil, data)); | |
| 170 end | |
| 171 | |
| 172 for user in users(origin, host) do | |
| 173 if typ == "keyval" then | |
| 174 local data, err = origin:get(user); | |
| 175 assert(not err, err); | |
| 176 assert(destination:set(user, data)); | |
| 177 else | |
| 178 error("Don't know how to migrate data of type '"..typ.."'."); | |
| 179 end | |
| 180 end | |
| 181 end | |
| 182 end | |
| 183 end, watchers); | |
| 110 | 184 |
| 111 io.stderr:write("Migrating...\n"); | 185 io.stderr:write("Migrating...\n"); |
| 112 for x in reader do | 186 |
| 113 --print(json.encode(x)) | 187 migration_runner:run({ input = config[from_store], output = config[to_store] }); |
| 114 writer(x); | 188 |
| 115 end | |
| 116 writer(nil); -- close | |
| 117 io.stderr:write("Done!\n"); | 189 io.stderr:write("Done!\n"); |