@wocker/mariadb-plugin
Version:
Mariadb plugin for wocker
719 lines (718 loc) • 30.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MariadbService = void 0;
const core_1 = require("@wocker/core");
const utils_1 = require("@wocker/utils");
const Path = __importStar(require("path"));
const cli_table3_1 = __importDefault(require("cli-table3"));
const format_1 = require("date-fns/format");
const Config_1 = require("../makes/Config");
const Service_1 = require("../makes/Service");
let MariadbService = class MariadbService {
constructor(appConfigService, pluginConfigService, dockerService, proxyService) {
this.appConfigService = appConfigService;
this.pluginConfigService = pluginConfigService;
this.dockerService = dockerService;
this.proxyService = proxyService;
}
get configPath() {
return "config.json";
}
get config() {
if (!this._config) {
this._config = Config_1.Config.make(this.fs, this.configPath);
}
return this._config;
}
get fs() {
return this.pluginConfigService.fs;
}
get dbFs() {
return new core_1.FileSystem(this.appConfigService.fs.path("db/mariadb"));
}
get dataFs() {
return new core_1.FileSystem(Path.join(__dirname, "../../data"));
}
query(service, query) {
return __awaiter(this, void 0, void 0, function* () {
const container = yield this.dockerService.getContainer(service.containerName);
if (!container) {
return null;
}
const exec = yield container.exec({
Cmd: ["mariadb", ...service.auth, "-e", query],
AttachStdout: true,
AttachStderr: true
});
const stream = yield exec.start({});
return new Promise((resolve, reject) => {
let result = "";
stream.on("data", (data) => {
result += data.toString();
});
stream.on("end", () => {
resolve(result);
});
stream.on("error", reject);
});
});
}
getDatabases(service) {
return __awaiter(this, void 0, void 0, function* () {
const res = yield this.query(service, "SHOW DATABASES;");
if (!res) {
return [];
}
return res.split(/\r?\n/)
.filter((database) => {
return !!database && !/Database$/.test(database);
})
.filter((database) => {
return database !== "mysql";
})
.filter((database) => {
return !/_schema/.test(database);
});
});
}
getDumpsDatabases(service) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.fs.exists(`dump/${service.name}`)) {
return [];
}
return this.fs.readdir(`dump/${service.name}`);
});
}
getFiles(service, database) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.fs.exists(`dump/${service.name}/${database}`)) {
return [];
}
return this.fs.readdir(`dump/${service.name}/${database}`);
});
}
init(adminHostname) {
return __awaiter(this, void 0, void 0, function* () {
const config = this.config;
if (!adminHostname) {
adminHostname = (yield (0, utils_1.promptInput)({
message: "Admin hostname",
required: true,
default: config.admin.hostname
}));
}
config.admin.hostname = adminHostname;
config.save();
});
}
list() {
return __awaiter(this, void 0, void 0, function* () {
const config = this.config;
const table = new cli_table3_1.default({
head: ["Name", "Host", "User", "External", "Storage", "IP"]
});
for (const service of config.services) {
let ip = "";
if (!service.host) {
const container = yield this.dockerService.getContainer(service.containerName);
if (container) {
const { NetworkSettings: {
// IPAddress,
Networks: { workspace: { IPAddress } } } } = yield container.inspect();
ip = `${IPAddress}`;
}
}
table.push([
service.name + (config.default === service.name ? " (default)" : ""),
service.host ? service.host : service.containerName,
service.username,
!!service.host,
!service.host ? service.storage : "",
ip || "-"
]);
}
return table.toString();
});
}
getServices() {
return (this.config.services || []).map((service) => {
return service.name;
});
}
start(name, restart) {
return __awaiter(this, void 0, void 0, function* () {
if (!name && !this.config.hasDefaultService()) {
yield this.create();
}
const service = this.config.getServiceOrDefault(name);
if (service.host) {
throw new Error("Service is external");
}
yield this.dockerService.pullImage(service.imageTag);
if (restart) {
yield this.dockerService.removeContainer(service.containerName);
}
let container = yield this.dockerService.getContainer(service.containerName);
if (!container) {
console.info(`Starting ${service.name} service...`);
const volumes = [];
switch (service.storage) {
case Service_1.STORAGE_VOLUME: {
if (!this.pluginConfigService.isVersionGTE("1.0.19")) {
throw new Error("Please update wocker for using volume storage");
}
if (!(yield this.dockerService.hasVolume(service.volume))) {
yield this.dockerService.createVolume(service.volume);
}
volumes.push(`${service.volume}:/var/lib/mysql`);
break;
}
case Service_1.STORAGE_FILESYSTEM:
default: {
if (!this.dbFs.exists(service.name)) {
this.dbFs.mkdir(service.name, {
recursive: true
});
}
volumes.push(`${this.dbFs.path(service.name)}:/var/lib/mysql`);
break;
}
}
container = yield this.dockerService.createContainer({
name: service.containerName,
image: service.imageTag,
restart: "always",
env: Object.assign(Object.assign(Object.assign(Object.assign({}, service.username ? {
MARIADB_USER: service.username
} : {}), service.password ? {
MARIADB_PASSWORD: service.password
} : {}), service.passwordHash ? {
MARIADB_ROOT_PASSWORD_HASH: service.passwordHash
} : {}), service.rootPassword ? {
MARIADB_ROOT_PASSWORD: service.rootPassword
} : {}),
volumes,
ports: service.containerPort
? [`${service.containerPort}:3306`]
: undefined
});
}
const { State: { Running } } = yield container.inspect();
if (!Running) {
yield container.start();
}
});
}
startAdmin() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.config.admin.enabled) {
return;
}
console.info("Phpmyadmin starting...");
const config = this.config;
const servers = [];
for (const service of config.services) {
if (service.host) {
continue;
}
const container = yield this.dockerService.getContainer(service.containerName);
if (!container) {
continue;
}
servers.push(service);
}
yield this.dockerService.removeContainer(config.admin.hostname);
if (servers.length === 0) {
return;
}
for (const service of config.services) {
if (!service.host) {
continue;
}
servers.push(service);
}
let conf = this.dataFs.readFile("conf/config.user.inc.php");
let file = conf.toString() + servers.map((service, index) => {
const host = service.host || service.containerName;
const user = service.host ? service.username : "root";
const password = service.host ? service.password : service.rootPassword;
const res = [
index !== 0 ? `$i++;` : "",
`$cfg['Servers'][$i]['host'] = '${host}';`
];
if (user && password) {
res.push(`$cfg['Servers'][$i]['auth_type'] = 'config';`);
res.push(`$cfg['Servers'][$i]['user'] = '${user}';`);
res.push(`$cfg['Servers'][$i]['password'] = '${password}';`);
}
else if (user) {
res.push(`$cfg['Servers'][$i]['auth_type'] = 'cookie';`);
res.push(`$cfg['Servers'][$i]['user'] = '${user}';`);
}
return res.join("\n");
}).join("\n");
this.fs.writeFile("config.user.inc.php", file);
this.fs.mkdir("dump", { recursive: true });
this.fs.mkdir("save", { recursive: true });
this.fs.mkdir("upload", { recursive: true });
let container = yield this.dockerService.getContainer(config.admin.hostname);
if (!container) {
yield this.dockerService.pullImage("phpmyadmin/phpmyadmin:latest");
container = yield this.dockerService.createContainer({
name: config.admin.hostname,
image: "phpmyadmin/phpmyadmin:latest",
restart: "always",
env: {
VIRTUAL_HOST: config.admin.hostname,
VIRTUAL_PORT: "80"
},
volumes: [
`${this.fs.path("config.user.inc.php")}:/etc/phpmyadmin/config.user.inc.php`,
`${this.fs.path("save")}:/etc/phpmyadmin/save`,
`${this.fs.path("upload")}:/etc/phpmyadmin/upload`
]
});
}
const { State: { Running } } = yield container.inspect();
if (!Running) {
yield container.start();
yield this.dockerService.exec(config.admin.hostname, [
"bash", "-c",
[
"apt-get update",
"apt-get install acl",
"setfacl -R -m u:www-data:rwx /etc/phpmyadmin/save"
].join(" && ")
]);
yield this.proxyService.start();
}
});
}
stop(name) {
return __awaiter(this, void 0, void 0, function* () {
const config = this.config;
const service = name
? config.getService(name)
: config.getDefaultService();
if (!service) {
throw new Error("Service not found");
}
console.info("Mariadb stopping...");
yield this.dockerService.removeContainer(service.containerName);
});
}
create() {
return __awaiter(this, arguments, void 0, function* (serviceProps = {}) {
if (serviceProps.name && this.config.hasService(serviceProps.name)) {
console.info(`Service "${serviceProps.name}" is already exists`);
delete serviceProps.name;
}
if (!serviceProps.name) {
serviceProps.name = yield (0, utils_1.promptInput)({
message: "Service name",
required: "Service name is required",
validate: (value) => {
if (value && this.config.hasService(value)) {
return `Service "${value}" is already exists`;
}
return true;
}
});
}
if (!serviceProps.username) {
serviceProps.username = yield (0, utils_1.promptInput)({
message: "User",
required: true
});
}
if (!serviceProps.password) {
serviceProps.password = yield (0, utils_1.promptInput)({
message: "Password",
type: "password",
required: true
});
const confirmPassword = yield (0, utils_1.promptInput)({
message: "Confirm password",
type: "password"
});
if (serviceProps.password !== confirmPassword) {
throw new Error("Password didn't match");
}
}
if (!serviceProps.host) {
if (!serviceProps.rootPassword && serviceProps.username !== "root") {
serviceProps.rootPassword = yield (0, utils_1.promptInput)({
message: "Root password",
type: "password",
required: true
});
const confirmPassword = yield (0, utils_1.promptInput)({
message: "Confirm root password",
type: "password",
required: true
});
if (serviceProps.rootPassword !== confirmPassword) {
throw new Error("Password didn't match");
}
}
if (!serviceProps.storage || ![Service_1.STORAGE_VOLUME, Service_1.STORAGE_FILESYSTEM].includes(serviceProps.storage)) {
serviceProps.storage = yield (0, utils_1.promptSelect)({
message: "Storage:",
options: [Service_1.STORAGE_VOLUME, Service_1.STORAGE_FILESYSTEM]
});
}
if (!serviceProps.containerPort) {
const needPort = yield (0, utils_1.promptConfirm)({
message: "Do you need to expose container port?",
default: false
});
if (needPort) {
serviceProps.containerPort = yield (0, utils_1.promptInput)({
required: true,
message: "Container port:",
type: "number",
min: 1,
default: 3306
});
}
}
}
this.config.setService(new Service_1.Service(serviceProps));
this.config.save();
});
}
upgrade() {
return __awaiter(this, arguments, void 0, function* (serviceProps = {}) {
const service = this.config.getServiceOrDefault(serviceProps.name);
if (serviceProps.storage) {
if (![Service_1.STORAGE_FILESYSTEM, Service_1.STORAGE_VOLUME].includes(serviceProps.storage)) {
throw new Error("Invalid storage type");
}
service.storage = serviceProps.storage;
}
if (serviceProps.volume) {
service.volume = serviceProps.volume;
}
if (serviceProps.imageName) {
service.imageName = serviceProps.imageName;
}
if (serviceProps.imageVersion) {
service.imageVersion = serviceProps.imageVersion;
}
if (serviceProps.containerPort) {
service.containerPort = serviceProps.containerPort;
}
this.config.setService(service);
this.config.save();
});
}
destroy(name, yes, force) {
return __awaiter(this, void 0, void 0, function* () {
if (!name) {
throw new Error("Service name required");
}
const service = this.config.getService(name);
if (this.config.default === service.name) {
if (!force) {
throw new Error("Can't destroy default service");
}
}
if (!yes) {
const confirm = yield (0, utils_1.promptConfirm)({
message: `Are you sure you want to delete the "${name}" database? This action cannot be undone and all data will be lost.`,
default: false
});
if (!confirm) {
throw new Error("Aborted");
}
}
if (!service.host) {
yield this.dockerService.removeContainer(service.containerName);
switch (service.storage) {
case Service_1.STORAGE_VOLUME: {
if (service.volume !== service.defaultVolume) {
console.info(`Deletion of custom volume "${service.volume}" skipped.`);
break;
}
if (!this.pluginConfigService.isVersionGTE("1.0.19")) {
throw new Error("Please update wocker for using volume storage");
}
if (yield this.dockerService.hasVolume(service.volume)) {
yield this.dockerService.rmVolume(service.volume);
}
break;
}
case Service_1.STORAGE_FILESYSTEM:
default: {
this.dbFs.rm(service.name, {
recursive: true,
force: true
});
break;
}
}
}
this.config.unsetService(name);
this.config.save();
});
}
setDefault(name) {
return __awaiter(this, void 0, void 0, function* () {
const service = this.config.getService(name);
this.config.default = service.name;
this.config.save();
});
}
mariadb(name, database) {
return __awaiter(this, void 0, void 0, function* () {
const service = this.config.getServiceOrDefault(name);
const container = yield this.dockerService.getContainer(service.containerName);
if (!container) {
throw new Error(`Service "${service.name}" is not started`);
}
if (!database) {
if (!process.stdin.isTTY) {
throw new Error("Database name missing");
}
database = yield (0, utils_1.promptSelect)({
message: "Database:",
options: yield this.getDatabases(service)
});
}
const exec = yield container.exec({
Cmd: ["mariadb", ...service.auth, database],
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: process.stdin.isTTY
});
const stream = yield exec.start({
hijack: true,
stdin: true,
Tty: process.stdin.isTTY
});
yield this.dockerService.attachStream(stream);
});
}
backup(name, database, filename) {
return __awaiter(this, void 0, void 0, function* () {
const service = this.config.getServiceOrDefault(name);
const container = yield this.dockerService.getContainer(service.containerName);
if (!container) {
throw new Error("Service not running");
}
if (!database) {
const databases = yield this.getDatabases(service);
database = (yield (0, utils_1.promptSelect)({
message: "Database:",
options: databases
}));
}
if (!filename) {
const date = (0, format_1.format)(new Date(), "yyyy-MM-dd HH-mm");
filename = yield (0, utils_1.promptInput)({
message: "File",
default: date,
suffix: ".sql"
});
filename += ".sql";
}
this.pluginConfigService.fs.mkdir(`dump/${service.name}/${database}`, {
recursive: true
});
const file = this.fs.createWriteStream(`dump/${service.name}/${database}/${filename}`);
const exec = yield container.exec({
Cmd: ["mariadb-dump", ...service.auth, database, "--add-drop-table", "--hex-blob"],
Tty: true,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true
});
const stream = yield exec.start({
Tty: true,
stdin: true,
hijack: true
});
yield new Promise((resolve, reject) => {
stream.on("data", (data) => {
file.write(data.toString("utf-8"));
});
stream.on("end", resolve);
stream.on("error", reject);
});
});
}
deleteBackup(name, database, filename, confirm) {
return __awaiter(this, void 0, void 0, function* () {
const service = this.config.getServiceOrDefault(name);
if (!database) {
const databases = yield this.getDumpsDatabases(service);
database = yield (0, utils_1.promptSelect)({
message: "Database:",
options: databases
});
}
if (!filename) {
const files = yield this.getFiles(service, database);
filename = yield (0, utils_1.promptSelect)({
message: "File:",
options: files
});
}
const path = `dump/${service.name}/${database}/${filename}`;
if (!this.fs.exists(path)) {
throw new Error(`File "${filename}" does not exists.`);
}
if (!confirm) {
confirm = yield (0, utils_1.promptConfirm)({
message: "Are you sure you want to delete?",
default: false
});
}
if (!confirm) {
throw new Error("Canceled");
}
this.fs.rm(path);
console.info(`File "${filename}" deleted`);
return;
});
}
restore(name, database, filename) {
return __awaiter(this, void 0, void 0, function* () {
const service = this.config.getServiceOrDefault(name);
const container = yield this.dockerService.getContainer(service.containerName);
if (!container) {
throw new Error("Mariadb instance isn't started");
}
if (!database) {
database = yield (0, utils_1.promptSelect)({
options: yield this.getDumpsDatabases(service),
message: "Database:"
});
}
if (!filename) {
filename = yield (0, utils_1.promptSelect)({
options: yield this.getFiles(service, database),
message: "Filename:"
});
}
const exec = yield container.exec({
Cmd: ["mariadb", ...service.auth, database],
AttachStdin: true,
AttachStderr: true,
AttachStdout: true
});
const stream = yield exec.start({
hijack: true,
stdin: true
});
yield new Promise((resolve, reject) => {
const file = this.fs.createReadStream(`dump/${service.name}/${database}/${filename}`);
file.on("data", (data) => {
stream.write(data);
});
file.on("error", reject);
file.on("end", () => resolve(undefined));
stream.on("data", (data) => {
process.stdout.write(data);
});
stream.on("error", (err) => {
file.close();
reject(err);
});
});
stream.write("exit\n");
});
}
dump(name, database) {
return __awaiter(this, void 0, void 0, function* () {
const service = this.config.getServiceOrDefault(name);
const container = yield this.dockerService.getContainer(service.containerName);
if (!container) {
throw new Error("Service isn't started");
}
if (!database) {
if (!process.stdin.isTTY) {
throw new Error("Database is missing");
}
const databases = yield this.getDatabases(service);
database = yield (0, utils_1.promptSelect)({
message: "Database:",
options: databases
});
}
const exec = yield container.exec({
Cmd: ["mariadb-dump", ...service.auth, database, "--add-drop-table"],
AttachStdout: true,
AttachStderr: true
});
const stream = yield exec.start({
Tty: process.stdin.isTTY,
hijack: true
});
stream.pipe(process.stdout);
});
}
};
exports.MariadbService = MariadbService;
exports.MariadbService = MariadbService = __decorate([
(0, core_1.Injectable)(),
__metadata("design:paramtypes", [core_1.AppConfigService,
core_1.PluginConfigService,
core_1.DockerService,
core_1.ProxyService])
], MariadbService);