diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2572f503..80add822 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: - 'v*.*.*-RC*' env: - ODOOD_DLANG_COMPILER: ldc-1.32.2 + ODOOD_DLANG_COMPILER: ldc-1.33.0 jobs: build-ubuntu-20_04: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4c94f86c..fdd96c06 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04, ubuntu-20.04] - dc: [dmd-2.102.2, ldc-1.32.2] + dc: [dmd-2.103.1, ldc-1.33.0] runs-on: ${{ matrix.os }} steps: @@ -44,7 +44,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04, ubuntu-20.04] - dc: [dmd-2.102.2, ldc-1.32.0] + dc: [dmd-2.103.1, ldc-1.33.0] runs-on: ${{ matrix.os }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 38bf1b82..945a6a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## Release 0.0.11 (2023-07-27) + +### Added + +- New option `--simplified-log` added to `odood test` command. + Thus it is possible to display meaningful log info (log level, logger, message). + +### Changed + +- Command `odood venv reinstall-odoo` now backups old odoo by default. + But it is possible to disable backup with option `--no-backup` +- Now it is allowed to specify only name of backup to restore database from. + In this case, Odood will try to find corresponding backup in standard + backups directory of project. + +### Fixed + +- Correctly handle `--additional-addons` passed for tests + in case when migration test enabled: update that addons before running tests. + +--- + ## Release 0.0.10 (2023-07-08) ### Added diff --git a/dub.selections.json b/dub.selections.json index 99d3990c..70d020ac 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -17,9 +17,10 @@ "semver": "0.5.0", "silly": "1.1.1", "tabletool": "0.5.0", - "thepath": "0.1.7", + "thepath": "0.1.8", + "theprocess": "0.0.1", "tinyendian": "0.2.0", "unit-threaded": "2.1.6", - "zipper": "0.0.2" + "zipper": "0.0.3" } } diff --git a/subpackages/cli/dub.selections.json b/subpackages/cli/dub.selections.json index 100f54b9..7408b5e5 100644 --- a/subpackages/cli/dub.selections.json +++ b/subpackages/cli/dub.selections.json @@ -17,9 +17,10 @@ "requests": "2.1.1", "semver": "0.5.0", "tabletool": "0.5.0", - "thepath": "0.1.7", + "thepath": "0.1.8", + "theprocess": "0.0.1", "tinyendian": "0.2.0", "unit-threaded": "2.1.6", - "zipper": "0.0.2" + "zipper": "0.0.3" } } diff --git a/subpackages/cli/source/odood/cli/app.d b/subpackages/cli/source/odood/cli/app.d index 04066f15..3222f863 100644 --- a/subpackages/cli/source/odood/cli/app.d +++ b/subpackages/cli/source/odood/cli/app.d @@ -125,13 +125,6 @@ class App: OdoodProgram { override int run(ref string[] args) { try { return super.run(args); - } catch (OdoodException e) { - // TODO: Use custom colodred formatting for errors - if (enable_debug) - error("Odood Exception catched:\n%s".format(e)); - else - error("%s".format(e.msg)); - return 1; } catch (Exception e) { // TODO: Use custom colodred formatting for errors if (enable_debug) diff --git a/subpackages/cli/source/odood/cli/commands/addons.d b/subpackages/cli/source/odood/cli/commands/addons.d index e90f5dae..d0ce2b2c 100644 --- a/subpackages/cli/source/odood/cli/commands/addons.d +++ b/subpackages/cli/source/odood/cli/commands/addons.d @@ -12,11 +12,10 @@ private import thepath: Path; private import commandr: Argument, Option, Flag, ProgramArgs; private import colored; -private import odood.cli.core: OdoodCommand; +private import odood.cli.core: OdoodCommand, OdoodCLIException; private import odood.lib.project: Project; private import odood.utils.odoo.serie: OdooSerie; private import odood.utils.addons.addon: OdooAddon; -private import odood.exception: OdoodException; enum AddonDisplayType { @@ -362,7 +361,7 @@ class CommandAddonsUpdate: OdoodCommand { if (!args.flag("all")) { foreach(addon_name; args.args("addon")) { auto addon = project.addons.getByString(addon_name); - enforce!OdoodException( + enforce!OdoodCLIException( !addon.isNull, "%s does not look like addon name or path to addon".format( addon_name)); @@ -398,7 +397,7 @@ class CommandAddonsUpdate: OdoodCommand { } if (start_again) - project.server.spawn(true); + project.server.start; } } @@ -440,7 +439,7 @@ class CommandAddonsInstall: OdoodCommand { OdooAddon[] addons; foreach(addon_name; args.args("addon")) { auto addon = project.addons.getByString(addon_name); - enforce!OdoodException( + enforce!OdoodCLIException( !addon.isNull, "%s does not look like addon name or path to addon".format( addon_name)); @@ -472,7 +471,7 @@ class CommandAddonsInstall: OdoodCommand { } if (start_again) - project.server.spawn(true); + project.server.start; } } @@ -506,7 +505,7 @@ class CommandAddonsUninstall: OdoodCommand { OdooAddon[] addons; foreach(addon_name; args.args("addon")) { auto addon = project.addons.getByString(addon_name); - enforce!OdoodException( + enforce!OdoodCLIException( !addon.isNull, "%s does not look like addon name or path to addon".format( addon_name)); @@ -532,7 +531,7 @@ class CommandAddonsUninstall: OdoodCommand { } if (start_again) - project.server.spawn(true); + project.server.start; } } @@ -589,7 +588,7 @@ class CommandAddonsIsInstalled: OdoodCommand { auto project = Project.loadProject; auto addon_n = project.addons.getByString(args.arg("addon")); - enforce!OdoodException( + enforce!OdoodCLIException( !addon_n.isNull, "Cannot find addon %s".format(args.arg("addon"))); auto addon = addon_n.get(); diff --git a/subpackages/cli/source/odood/cli/commands/ci.d b/subpackages/cli/source/odood/cli/commands/ci.d index efcaf46a..077d7889 100644 --- a/subpackages/cli/source/odood/cli/commands/ci.d +++ b/subpackages/cli/source/odood/cli/commands/ci.d @@ -9,7 +9,6 @@ private import commandr: Argument, Option, Flag, ProgramArgs; private import odood.cli.core: OdoodCommand; private import odood.lib.project: Project; -private import odood.exception: OdoodException; private import odood.lib.odoo.utils: fixVersionConflict; diff --git a/subpackages/cli/source/odood/cli/commands/database.d b/subpackages/cli/source/odood/cli/commands/database.d index c25a4362..94ada523 100644 --- a/subpackages/cli/source/odood/cli/commands/database.d +++ b/subpackages/cli/source/odood/cli/commands/database.d @@ -9,15 +9,13 @@ private import std.typecons; private import thepath: Path; private import commandr: Argument, Option, Flag, ProgramArgs; -private import odood.cli.core: OdoodCommand, exitWithCode; +private import odood.cli.core: OdoodCommand, exitWithCode, OdoodCLIException; private import odood.lib.project: Project; private import odood.lib.odoo.lodoo: BackupFormat; private import odood.utils.odoo.serie: OdooSerie; private import odood.utils: generateRandomString; private import odood.utils.addons.addon: OdooAddon; -// TODO: Use specific exception tree for CLI part -private import odood.exception: OdoodException; class CommandDatabaseList: OdoodCommand { @@ -72,7 +70,7 @@ class CommandDatabaseCreate: OdoodCommand { OdooAddon[] to_install; foreach(addon_name; args.options("install")) { auto addon = project.addons.getByName(addon_name); - enforce!OdoodException( + enforce!OdoodCLIException( !addon.isNull, "Cannot find addon %s".format(addon_name)); to_install ~= addon.get; @@ -90,7 +88,7 @@ class CommandDatabaseCreate: OdoodCommand { "(because --recreate option specified).", dbname); project.databases.drop(dbname); } else { - throw new OdoodException( + throw new OdoodCLIException( "Database %s already exists!".format(dbname)); } } @@ -200,7 +198,7 @@ class CommandDatabaseBackup: OdoodCommand { public override void execute(ProgramArgs args) { auto project = Project.loadProject; - enforce!OdoodException( + enforce!OdoodCLIException( args.flag("all") || args.args("name").length > 0, "It is required to specify name of database to backup or option -a or --all!"); @@ -237,16 +235,13 @@ class CommandDatabaseRestore: OdoodCommand { this.add(new Argument( "name", "Name of database to restore.").required()); this.add(new Argument( - "backup", "Path to backup to restore database from.").required()); + "backup", "Path to backup (or name of backup) to restore database from.").required()); } public override void execute(ProgramArgs args) { auto project = Project.loadProject; - const auto backup_path = Path(args.arg("backup")).toAbsolute; + const auto backup_path = args.arg("backup"); const string dbname = args.arg("name"); - enforce!OdoodException( - backup_path.exists && backup_path.isFile, - "Wrong backup path (%s) specified!".format(backup_path)); bool start_server = false; if (project.server.isRunning) { @@ -261,7 +256,7 @@ class CommandDatabaseRestore: OdoodCommand { "(because --recreate option specified).", dbname); project.databases.drop(dbname); } else { - throw new OdoodException( + throw new OdoodCLIException( "Database %s already exists!".format(dbname)); } } @@ -280,7 +275,7 @@ class CommandDatabaseRestore: OdoodCommand { } if (start_server) - project.server.spawn(true); + project.server.start; } } diff --git a/subpackages/cli/source/odood/cli/commands/discover.d b/subpackages/cli/source/odood/cli/commands/discover.d index 39b74f7f..32b9edb1 100644 --- a/subpackages/cli/source/odood/cli/commands/discover.d +++ b/subpackages/cli/source/odood/cli/commands/discover.d @@ -11,7 +11,6 @@ private import odood.cli.core: OdoodCommand; private import odood.lib.project: Project; private import odood.lib.project.discover: discoverOdooHelper; private import odood.utils.odoo.serie: OdooSerie; -private import odood.exception: OdoodException; class CommandDiscoverOdooHelper: OdoodCommand { diff --git a/subpackages/cli/source/odood/cli/commands/init.d b/subpackages/cli/source/odood/cli/commands/init.d index de4830b9..459cc25d 100644 --- a/subpackages/cli/source/odood/cli/commands/init.d +++ b/subpackages/cli/source/odood/cli/commands/init.d @@ -7,12 +7,11 @@ private import std.exception: enforce; private import thepath: Path; private import commandr: Option, Flag, ProgramArgs; -private import odood.cli.core: OdoodCommand; +private import odood.cli.core: OdoodCommand, OdoodCLIException; private import odood.lib.project: Project; private import odood.lib.odoo.config: initOdooConfig; private import odood.lib.postgres: createNewPostgresUser; private import odood.utils.odoo.serie: OdooSerie; -private import odood.exception: OdoodException; class CommandInit: OdoodCommand { @@ -79,7 +78,7 @@ class CommandInit: OdoodCommand { auto odoo_repo = args.option( "odoo-repo", "https://github.com/odoo/odoo.git"); - enforce!OdoodException( + enforce!OdoodCLIException( odoo_version.isValid, "Odoo version %s is not valid".format(args.option("odoo-version"))); diff --git a/subpackages/cli/source/odood/cli/commands/script.d b/subpackages/cli/source/odood/cli/commands/script.d index 0c8e5262..9ed792f8 100644 --- a/subpackages/cli/source/odood/cli/commands/script.d +++ b/subpackages/cli/source/odood/cli/commands/script.d @@ -8,9 +8,8 @@ private import commandr: Argument, Option, Flag, ProgramArgs; private import thepath; -private import odood.cli.core: OdoodCommand; +private import odood.cli.core: OdoodCommand, OdoodCLIException; private import odood.lib.project: Project; -private import odood.exception: OdoodException; class CommandScriptPy: OdoodCommand { @@ -26,7 +25,7 @@ class CommandScriptPy: OdoodCommand { auto dbname = args.option("db"); Path script = args.arg("script"); - enforce!OdoodException( + enforce!OdoodCLIException( project.databases.exists(args.option("db")), "Database %s does not exists!".format(args.option("db"))); @@ -53,8 +52,7 @@ class CommandScriptSQL: OdoodCommand { auto dbname = args.option("db"); Path script = args.arg("script"); - // TODO: check existense of db via SQL - enforce!OdoodException( + enforce!OdoodCLIException( project.databases.exists(dbname), "Database %s does not exists!".format(dbname)); diff --git a/subpackages/cli/source/odood/cli/commands/server.d b/subpackages/cli/source/odood/cli/commands/server.d index 68a78932..2c287f39 100644 --- a/subpackages/cli/source/odood/cli/commands/server.d +++ b/subpackages/cli/source/odood/cli/commands/server.d @@ -10,7 +10,6 @@ private import commandr: Option, Flag, ProgramArgs; private import odood.cli.core: OdoodCommand; private import odood.lib.project: Project; private import odood.utils.odoo.serie: OdooSerie; -private import odood.exception: OdoodException; class CommandServerRun: OdoodCommand { @@ -34,7 +33,7 @@ class CommandServerStart: OdoodCommand { public override void execute(ProgramArgs args) { auto project = Project.loadProject; - project.server.spawn(true); + project.server.start; } } @@ -80,7 +79,7 @@ class CommandServerRestart: OdoodCommand { if (project.server.isRunning) project.server.stop(); - project.server.spawn(true); + project.server.start; } } @@ -96,7 +95,7 @@ class CommandServerBrowse: OdoodCommand { import std.process; auto project = Project.loadProject; if (!project.server.isRunning) - project.server.spawn(true); + project.server.start; auto odoo_conf = project.getOdooConfig; diff --git a/subpackages/cli/source/odood/cli/commands/status.d b/subpackages/cli/source/odood/cli/commands/status.d index c58eb3e1..b66e1d21 100644 --- a/subpackages/cli/source/odood/cli/commands/status.d +++ b/subpackages/cli/source/odood/cli/commands/status.d @@ -11,7 +11,6 @@ private import commandr: Option, Flag, ProgramArgs; private import odood.cli.core: OdoodCommand; private import odood.lib.project: Project; private import odood.utils.odoo.serie: OdooSerie; -private import odood.exception: OdoodException; immutable string TMPL_CURRENT_PROJECT_STATUS = " diff --git a/subpackages/cli/source/odood/cli/commands/test.d b/subpackages/cli/source/odood/cli/commands/test.d index 9e0f9d92..e1797119 100644 --- a/subpackages/cli/source/odood/cli/commands/test.d +++ b/subpackages/cli/source/odood/cli/commands/test.d @@ -19,7 +19,6 @@ private import odood.cli.core: OdoodCommand, exitWithCode; private import odood.lib.project: Project; private import odood.lib.odoo.log: OdooLogProcessor, OdooLogRecord; private import odood.utils.odoo.serie: OdooSerie; -private import odood.exception: OdoodException; /** Color log level, depending on log level itself @@ -94,6 +93,8 @@ class CommandTest: OdoodCommand { "Do not drop temporary database after test completed.")); this.add(new Flag( null, "isw", "Ignore warnings that are considered safe.")); + this.add(new Flag( + null, "simplified-log", "Display simplified log messages.")); this.add(new Flag( null, "migration", "Run migration against stable branch.")); this.add(new Flag( @@ -141,9 +142,14 @@ class CommandTest: OdoodCommand { auto testRunner = project.testRunner(); - testRunner.registerLogHandler((in rec) { - printLogRecord(rec); - }); + if (args.flag("simplified-log")) + testRunner.registerLogHandler((in rec) { + printLogRecordSimplified(rec); + }); + else + testRunner.registerLogHandler((in rec) { + printLogRecord(rec); + }); if (args.flag("isw")) testRunner.ignoreSafeWarnings(); @@ -257,6 +263,8 @@ class CommandTest: OdoodCommand { "--fail-under", args.option("coverage-fail-under"), ]; + if (args.flag("coverage-ignore-errors")) + coverage_report_options ~= ["--ignore-errors"]; auto coverage_report_res = project.venv.run( ["coverage", "report",] ~ coverage_report_options diff --git a/subpackages/cli/source/odood/cli/commands/venv.d b/subpackages/cli/source/odood/cli/commands/venv.d index ed7d7cd4..a61b5e2b 100644 --- a/subpackages/cli/source/odood/cli/commands/venv.d +++ b/subpackages/cli/source/odood/cli/commands/venv.d @@ -108,7 +108,7 @@ class CommandVenvUpdateOdoo: OdoodCommand { project.updateOdoo(args.flag("backup")); if (start_server) - project.server.spawn(true); + project.server.start; } } @@ -120,6 +120,8 @@ class CommandVenvReinstallOdoo: OdoodCommand { super("reinstall-odoo", "Reinstall Odoo to different Odoo version."); this.add(new Flag( "b", "backup", "Backup Odoo before update.")); + this.add(new Flag( + null, "no-backup", "Do not take backup of Odoo and venv.")); this.add(new Flag( null, "reinstall-venv", "Reinstall virtualenv too...")); this.add(new Option( @@ -142,14 +144,14 @@ class CommandVenvReinstallOdoo: OdoodCommand { project.reinstallOdoo( OdooSerie(args.option("version")), - args.flag("backup"), + !args.flag("no-backup") || args.flag("backup"), args.flag("reinstall-venv"), args.option("venv-py-version", "auto"), args.option("venv-node-version", "lts"), ); if (start_server) - project.server.spawn(true); + project.server.start; } } diff --git a/subpackages/cli/source/odood/cli/core/exception.d b/subpackages/cli/source/odood/cli/core/exception.d index ba4f16e5..c578d6a8 100644 --- a/subpackages/cli/source/odood/cli/core/exception.d +++ b/subpackages/cli/source/odood/cli/core/exception.d @@ -1,20 +1,26 @@ module odood.cli.core.exception; +private import std.exception; + + +/** Base class for all Odood CLI exceptions + **/ +class OdoodCLIException : Exception { + mixin basicExceptionCtors; +} + /** This exception identifies, that in Odood program commandr Command * is used instead of OdoodCommand. * Currently, it is not allowed to mix odood commands and commandr commands * in one app **/ -class OdoodCLICommandNoExecuteException : Exception { - - this(string msg, string file = __FILE__, size_t line = __LINE__) { - super(msg, file, line); - } +class OdoodCLICommandNoExecuteException : OdoodCLIException { + mixin basicExceptionCtors; } -class OdoodCLIExitException : Exception +class OdoodCLIExitException : OdoodCLIException { private int _exit_code; diff --git a/subpackages/cli/source/odood/cli/core/logger.d b/subpackages/cli/source/odood/cli/core/logger.d index f32f8b41..3805ba60 100644 --- a/subpackages/cli/source/odood/cli/core/logger.d +++ b/subpackages/cli/source/odood/cli/core/logger.d @@ -13,27 +13,28 @@ class OdoodLogger : Logger { this(Args...)(auto ref Args args) { super(args); } - override protected void writeLogMsg(ref LogEntry payload) + // Trusted, because of using stderr, that uses __gshared under the hood + override protected void writeLogMsg(ref LogEntry payload) @trusted { auto msg = payload.msg; final switch (payload.logLevel) { case LogLevel.trace: - writeln("TRACE".darkGray, ": ", msg); + stderr.writeln("TRACE".darkGray, ": ", msg); break; case LogLevel.info: - writeln("INFO".blue, ": ", msg); + stderr.writeln("INFO".blue, ": ", msg); break; case LogLevel.warning: - writeln("WARNING".yellow, ": ", msg); + stderr.writeln("WARNING".yellow, ": ", msg); break; case LogLevel.error: - writeln("ERROR".lightRed, ": ", msg); + stderr.writeln("ERROR".lightRed, ": ", msg); break; case LogLevel.critical: - writeln("CRITICAL".red, ": ", msg); + stderr.writeln("CRITICAL".red, ": ", msg); break; case LogLevel.fatal: - writeln("FATAL".lightMagenta, ": ", msg); + stderr.writeln("FATAL".lightMagenta, ": ", msg); break; case LogLevel.off, LogLevel.all: // No output; This log levels are not used in messages, diff --git a/subpackages/lib/dub.sdl b/subpackages/lib/dub.sdl index 05f21c47..105fef84 100644 --- a/subpackages/lib/dub.sdl +++ b/subpackages/lib/dub.sdl @@ -4,6 +4,7 @@ authors "Dmytro Katyukha" copyright "Copyright © 2022-2023, Dmytro Katyukha" license "MPL-2.0" dependency "thepath" version=">=0.1.7" +dependency "theprocess" version=">=0.0.1" dependency "dini" version="~>2.0.0" dependency "semver" version=">=0.4.0" dependency "dyaml" version=">=0.9.2" diff --git a/subpackages/lib/dub.selections.json b/subpackages/lib/dub.selections.json index 33baad86..3a0ebca4 100644 --- a/subpackages/lib/dub.selections.json +++ b/subpackages/lib/dub.selections.json @@ -12,9 +12,10 @@ "pyd": "0.14.4", "requests": "2.1.1", "semver": "0.5.0", - "thepath": "0.1.7", + "thepath": "0.1.8", + "theprocess": "0.0.1", "tinyendian": "0.2.0", "unit-threaded": "2.1.6", - "zipper": "0.0.2" + "zipper": "0.0.3" } } diff --git a/subpackages/lib/source/odood/lib/addons/manager.d b/subpackages/lib/source/odood/lib/addons/manager.d index 43e00d9b..6b1baed6 100644 --- a/subpackages/lib/source/odood/lib/addons/manager.d +++ b/subpackages/lib/source/odood/lib/addons/manager.d @@ -144,24 +144,18 @@ struct AddonManager { return res; } - /// Scan specified path for addons + /** Scan specified path for addons + * + * Params: + * path = path to addon or directory that contains addons + * recursive = if set to true, then search for addons in subdirectories + * + * Returns: + * Array of OdooAddons found in specified path. + **/ OdooAddon[] scan(in Path path, in bool recursive=false) const { tracef("Searching for addons in %s", path); - if (isOdooAddon(path)) { - return [new OdooAddon(path)]; - } - - OdooAddon[] res; - - auto walk_mode = recursive ? SpanMode.breadth : SpanMode.shallow; - foreach(addon_path; path.walk(walk_mode)) { - if (addon_path.isInside(path.join("setup"))) - // Skip modules defined in OCA setup folder to avoid duplication. - continue; - if (addon_path.isOdooAddon) - res ~= new OdooAddon(addon_path); - } - return res; + return findAddons(path, recursive); } /** Link all addons inside specified directories @@ -520,6 +514,8 @@ struct AddonManager { in bool single_branch=false, in bool recursive=false) { foreach(line; parseOdooRequirements(path)) + // TODO: In case when only single module request, + // add only single module final switch (line.type) { case OdooRequirementsLineType.repo: addRepo( diff --git a/subpackages/lib/source/odood/lib/addons/repository.d b/subpackages/lib/source/odood/lib/addons/repository.d index db92953b..c218b9ac 100644 --- a/subpackages/lib/source/odood/lib/addons/repository.d +++ b/subpackages/lib/source/odood/lib/addons/repository.d @@ -12,7 +12,7 @@ private import thepath: Path; private import odood.lib.project: Project; private import odood.exception: OdoodException; private import odood.utils.git: parseGitURL, gitClone; -private import odood.utils.theprocess; +private import theprocess; class AddonRepository { diff --git a/subpackages/lib/source/odood/lib/install/odoo.d b/subpackages/lib/source/odood/lib/install/odoo.d index cf092f0b..bdb1e7ef 100644 --- a/subpackages/lib/source/odood/lib/install/odoo.d +++ b/subpackages/lib/source/odood/lib/install/odoo.d @@ -24,8 +24,6 @@ private import odood.utils; * project = Project to download Odoo to. **/ void installDownloadOdoo(in Project project) { - // TODO: replace with logger calls, or with colored output. - import std.stdio; auto odoo_archive_path = project.directories.downloads.join( "odoo.%s.zip".format(project.odoo.branch)); scope(exit) { diff --git a/subpackages/lib/source/odood/lib/install/python.d b/subpackages/lib/source/odood/lib/install/python.d index 85fea2a9..6b913eb5 100644 --- a/subpackages/lib/source/odood/lib/install/python.d +++ b/subpackages/lib/source/odood/lib/install/python.d @@ -1,9 +1,6 @@ /// Module contains functions to install python for Odood project module odood.lib.install.python; -private import semver; -private import thepath: Path; - private import std.regex: ctRegex, matchFirst; private import std.exception: enforce; private import std.format: format; @@ -11,9 +8,12 @@ private import std.parallelism: totalCPUs; private import std.conv: to; private import std.logger; +private import semver; +private import thepath: Path; +private import theprocess; + private import odood.lib.project: Project; private import odood.lib.venv: PySerie; -private import odood.utils.theprocess; private import odood.utils.odoo.serie: OdooSerie; private import odood.utils: download, parsePythonVersion; private import odood.exception: OdoodException; diff --git a/subpackages/lib/source/odood/lib/odoo/db_manager.d b/subpackages/lib/source/odood/lib/odoo/db_manager.d index 72533a3a..ce1501bb 100644 --- a/subpackages/lib/source/odood/lib/odoo/db_manager.d +++ b/subpackages/lib/source/odood/lib/odoo/db_manager.d @@ -60,8 +60,9 @@ struct OdooDatabaseManager { **/ bool exists(in string name) const { // TODO: replace with project's db wrapper to check if database exists - // This could simplify performance by avoiding call to python - // interpreter + // This could improve performance by avoiding call to python + // interpreter. Take into account that database could exist, + // but still could not be visible for Odoo. return _project.lodoo(_test_mode).databaseExists(name); } @@ -169,6 +170,10 @@ struct OdooDatabaseManager { import std.algorithm: canFind; import odood.utils.odoo.db: parseDatabaseBackupManifest; + enforce!OdoodException( + backup_path.exists, + "Cannot restore! Backup %s does not exists!".format(backup_path)); + enforce!OdoodException( [".sql", ".zip"].canFind(backup_path.extension), "Cannot restore database backup %s" ~ backup_path.toString ~ @@ -221,6 +226,7 @@ struct OdooDatabaseManager { * Params: * name = name of database to restore * backup_path = path to database backup to restore + * backup_name = name of backup located in standard backup location or path to backup as string. * validate_strict = if set to true, * then raise error if backup is not valid, * otherwise only warning will be emited to log. @@ -234,6 +240,18 @@ struct OdooDatabaseManager { return _project.lodoo(_test_mode).databaseRestore(name, backup_path); } + /// ditto + auto restore( + in string name, + in string backup_name, + in bool validate_strict=true) const { + Path backup_path = Path(backup_name); + if (!backup_path.exists) + // Try to search for backup in standard backup directory. + backup_path = _project.directories.backups.join(backup_name); + return restore(name, backup_path, validate_strict); + } + /** Return database wrapper, that allows to interact with database * via plain SQL and contains some utility methods. * diff --git a/subpackages/lib/source/odood/lib/odoo/lodoo.d b/subpackages/lib/source/odood/lib/odoo/lodoo.d index 368a8a51..5aa1c4b5 100644 --- a/subpackages/lib/source/odood/lib/odoo/lodoo.d +++ b/subpackages/lib/source/odood/lib/odoo/lodoo.d @@ -192,7 +192,6 @@ const struct LOdoo { Path databaseBackup( in string dbname, in BackupFormat backup_format = BackupFormat.zip) { - // TODO: Add ability to specify backup path import std.datetime.systime: Clock; string dest_name="db-backup-%s-%s.%s.%s".format( diff --git a/subpackages/lib/source/odood/lib/odoo/test.d b/subpackages/lib/source/odood/lib/odoo/test.d index 777de640..ac25461b 100644 --- a/subpackages/lib/source/odood/lib/odoo/test.d +++ b/subpackages/lib/source/odood/lib/odoo/test.d @@ -403,10 +403,14 @@ struct OdooTestRunner { /** Take clean up actions before test finished **/ void cleanUp() { - if (_temporary_db && - _db_no_drop == false && - _databases.exists(_test_db_name)) { - _databases.drop(_test_db_name); + if (_temporary_db && _databases.exists(_test_db_name)) { + if (_db_no_drop == false) + _databases.drop(_test_db_name); + else + infof( + "Database %s was not dropt, because test runner " ~ + "configured to not drop temporary db after test.", + _test_db_name); } } @@ -561,7 +565,7 @@ struct OdooTestRunner { auto update_res =_server.pipeServerLog( getCoverageOptions(), [ - "--update=%s".format(getModuleList), + "--update=%s".format(getModuleList(true)), "--log-level=info", "--stop-after-init", "--workers=0", diff --git a/subpackages/lib/source/odood/lib/odoo/utils.d b/subpackages/lib/source/odood/lib/odoo/utils.d index 956b350e..b04228a9 100644 --- a/subpackages/lib/source/odood/lib/odoo/utils.d +++ b/subpackages/lib/source/odood/lib/odoo/utils.d @@ -34,7 +34,7 @@ string fixVersionConflictImpl(in string manifest_content, in OdooSerie serie) { auto new_ver = change_version > head_version ? change_version : head_version; - // TODO: find better way. Check if head nad change versions are valid + // TODO: find better way. Check if head and change versions are valid assert(new_ver.isValid, "New version is not valid!"); return "%s%s%s.%s%s,\n".format( diff --git a/subpackages/lib/source/odood/lib/package.d b/subpackages/lib/source/odood/lib/package.d index a507e23e..0f5f3c4e 100644 --- a/subpackages/lib/source/odood/lib/package.d +++ b/subpackages/lib/source/odood/lib/package.d @@ -1,6 +1,6 @@ module odood.lib; -public immutable string _version = "0.0.10"; +public immutable string _version = "0.0.11"; public import odood.lib.project; diff --git a/subpackages/lib/source/odood/lib/postgres.d b/subpackages/lib/source/odood/lib/postgres.d index 6f4a8d1a..10a8ee5b 100644 --- a/subpackages/lib/source/odood/lib/postgres.d +++ b/subpackages/lib/source/odood/lib/postgres.d @@ -3,7 +3,7 @@ module odood.lib.postgres; private import std.format: format; private import odood.exception: OdoodException; -private import odood.utils.theprocess; +private import theprocess; void createNewPostgresUser(in string user, in string password) { diff --git a/subpackages/lib/source/odood/lib/project/config.d b/subpackages/lib/source/odood/lib/project/config.d index b735f95d..380bcb81 100644 --- a/subpackages/lib/source/odood/lib/project/config.d +++ b/subpackages/lib/source/odood/lib/project/config.d @@ -1,6 +1,8 @@ /// This module handles config of instance managed by odood module odood.lib.project.config; +private import std.format: format; + private import thepath: Path; private static import dyaml; private static import dyaml.dumper; @@ -14,6 +16,17 @@ private import odood.lib.server: OdooServer; package(odood) immutable string DEFAULT_ODOO_REPO="https://github.com/odoo/odoo"; +/** This enum describes what tool is used to run and manage Odoo in background + **/ +enum ProjectServerSupervisor { + /// Server is managed by Odood. + Odood, + + /// Server is managed by init script in /etc/init.d odoo + InitScript, +} + + /** Struct that represents odoo-specific configuration **/ struct ProjectConfigOdoo { @@ -48,6 +61,12 @@ struct ProjectConfigOdoo { /// Name of the user that have to run Odoo string server_user; + /// Managed by OS. + ProjectServerSupervisor server_supervisor = ProjectServerSupervisor.Odood; + + /// Path to init script, of project's server is managed by init script. + Path server_init_script_path; + this(in Path project_root, in ProjectConfigDirectories directories, in OdooSerie odoo_serie, @@ -73,7 +92,7 @@ struct ProjectConfigOdoo { * logfile: some/path, * test: * enable: true - * configfule: some/path + * configfile: some/path **/ this.configfile = config["configfile"].as!string; this.testconfigfile = config["testconfigfile"].as!string; @@ -86,8 +105,28 @@ struct ProjectConfigOdoo { this.repo = config["repo"].as!string; else this.repo = "https://github.com/odoo/odoo"; + + // TODO: Think about moving server configuration to separate section + // in yaml. + // TODO: introduce config versions and automatic migration of config files. if (config.containsKey("server-user")) this.server_user = config["server-user"].as!string; + if (config.containsKey("server-supervisor")) + switch (config["server-supervisor"].as!string) { + case "odood": + this.server_supervisor = ProjectServerSupervisor.Odood; + break; + case "init-script": + this.server_supervisor = ProjectServerSupervisor.InitScript; + break; + default: + assert( + 0, + "Server supervisor type %s is not supported!".format( + config["server-supervisor"].as!string)); + } + else + this.server_supervisor = ProjectServerSupervisor.Odood; } dyaml.Node toYAML() const { @@ -103,6 +142,17 @@ struct ProjectConfigOdoo { ]); if (this.server_user) result["server-user"] = this.server_user; + + /// Serialize supervisor used for this project + final switch(this.server_supervisor) { + case ProjectServerSupervisor.Odood: + result["server-supervisor"] = "odood"; + break; + case ProjectServerSupervisor.InitScript: + result["server-supervisor"] = "init-script"; + break; + } + return result; } } diff --git a/subpackages/lib/source/odood/lib/project/discover.d b/subpackages/lib/source/odood/lib/project/discover.d index 191b9cfd..4649357d 100644 --- a/subpackages/lib/source/odood/lib/project/discover.d +++ b/subpackages/lib/source/odood/lib/project/discover.d @@ -1,7 +1,7 @@ module odood.lib.project.discover; private import std.logger; -private import std.string: splitLines, strip; +private import std.string: splitLines, strip, empty; private import std.algorithm: startsWith; private import std.regex; private import std.typecons: Nullable, nullable; @@ -11,7 +11,10 @@ private import std.format: format; private import thepath: Path; private import odood.lib.project: - ProjectConfigOdoo, ProjectConfigDirectories, Project; + ProjectConfigOdoo, + ProjectConfigDirectories, + Project, + ProjectServerSupervisor; private import odood.utils.odoo.serie: OdooSerie; private import odood.lib.venv: VirtualEnv; private import odood.lib.odoo.python: guessPySerie; @@ -100,6 +103,12 @@ auto parseOdooHelperScriptsConfig(in string config_content) { case "SERVER_RUN_USER": project_odoo.server_user = c["val"]; break; + case "INIT_SCRIPT": + if (!c["val"].empty) { + project_odoo.server_supervisor = ProjectServerSupervisor.InitScript; + project_odoo.server_init_script_path = Path(c["val"]); + } + break; default: // Nothing todo for unknown options diff --git a/subpackages/lib/source/odood/lib/project/project.d b/subpackages/lib/source/odood/lib/project/project.d index 67f2d702..73cd2a25 100644 --- a/subpackages/lib/source/odood/lib/project/project.d +++ b/subpackages/lib/source/odood/lib/project/project.d @@ -71,9 +71,6 @@ class Project { * that contains odood.yml config file **/ static auto loadProject(in Path path) { - - // TODO: convert path to absolute - // do we need this? Will be converted in constructor. if (path.exists && path.isFile) { Node config = dyaml.Loader.fromFile(path.toString()).load(); return new Project(config, path); @@ -359,7 +356,6 @@ class Project { // TODO: Add support for cases when odoo installed via git // In this case it is better to just run git pull - // TODO: Add support for updating to other version of Odoo enforce!OdoodException( !this.odoo.path.join(".git").exists, "Cannot update odoo that is git repo yet!"); diff --git a/subpackages/lib/source/odood/lib/server/server.d b/subpackages/lib/source/odood/lib/server/server.d index 9cb13c43..61ab6d7b 100644 --- a/subpackages/lib/source/odood/lib/server/server.d +++ b/subpackages/lib/source/odood/lib/server/server.d @@ -9,18 +9,18 @@ private import std.logger; private import std.exception: enforce; private import std.conv: to; private import std.format: format; -private import std.string: join; +private import std.string: join, strip; private import std.algorithm: map; private import thepath: Path; -private import odood.lib.project: Project; +private import odood.lib.project: Project, ProjectServerSupervisor; private import odood.utils.odoo.serie: OdooSerie; private import odood.exception: OdoodException; private import odood.utils: isProcessRunning; private import odood.lib.server.exception; private import odood.lib.server.log_pipe; -private import odood.utils.theprocess; +private import theprocess; package(odood) struct CoverageOptions { @@ -83,7 +83,7 @@ struct OdooServer { **/ pid_t getPid() const { if (_project.odoo.pidfile.exists) { - auto pid = _project.odoo.pidfile.readFileText.to!pid_t; + auto pid = _project.odoo.pidfile.readFileText.strip.to!pid_t; if (isProcessRunning(pid)) return pid; return -2; @@ -172,6 +172,10 @@ struct OdooServer { !isRunning, "Server already running!"); + enforce!OdoodException( + !detach || _project.odoo.server_supervisor == ProjectServerSupervisor.Odood, + "Cannot run Odoo server in beckground, because it is not managed byt Odood."); + auto runner = getServerRunner( "--pidfile=%s".format(_project.odoo.pidfile)); if (detach) { @@ -194,7 +198,6 @@ struct OdooServer { **/ auto pipeServerLog(in CoverageOptions coverage, string[] options...) const { auto runner = getServerRunner(coverage, options) - // TODO: Do we need to run it in current work dir? .inWorkDir(Path.current); // Run in current directory to make coverage work. tracef( @@ -262,10 +265,28 @@ struct OdooServer { return isProcessRunning(odoo_pid); } - /** Stop the Odoo server + /** Start the Odoo server * **/ - void stop() const { + void start() const { + final switch(_project.odoo.server_supervisor) { + case ProjectServerSupervisor.Odood: + this.spawn(true); + break; + case ProjectServerSupervisor.InitScript: + Process("/etc/init.d/odoo") + .withArgs("start") + .execute + .ensureOk(); + break; + } + + } + + /** Stop the Odoo server via Odood + * + **/ + void stopOdoodServer() const { import core.sys.posix.signal: kill, SIGTERM; import core.stdc.errno; import core.thread: Thread; @@ -291,4 +312,22 @@ struct OdooServer { } info("Server stopped."); } + + + /** Stop the Odoo server + * + **/ + void stop() const { + final switch(_project.odoo.server_supervisor) { + case ProjectServerSupervisor.Odood: + this.stopOdoodServer(); + break; + case ProjectServerSupervisor.InitScript: + Process("/etc/init.d/odoo") + .withArgs("stop") + .execute + .ensureOk(); + break; + } + } } diff --git a/subpackages/lib/source/odood/lib/venv.d b/subpackages/lib/source/odood/lib/venv.d index 95ba89d7..9623a3e7 100644 --- a/subpackages/lib/source/odood/lib/venv.d +++ b/subpackages/lib/source/odood/lib/venv.d @@ -12,7 +12,7 @@ private static import dyaml; private import semver: SemVer, VersionPart; private import odood.exception: OdoodException; -private import odood.utils.theprocess; +private import theprocess; private import odood.utils; // TOOD: May be it have sense to move this to utils subpackage. @@ -296,10 +296,6 @@ const struct VirtualEnv { .ensureStatus(); } - // TODO: Install 'make' and 'libsqlite3-dev' if needed - // Possibly have to be added when installation of system packages - // will be implemented - // Configure python build info("Running python's configure script..."); Process(python_build_dir.join("configure")) diff --git a/subpackages/utils/dub.sdl b/subpackages/utils/dub.sdl index 22be833a..9ba0f73a 100644 --- a/subpackages/utils/dub.sdl +++ b/subpackages/utils/dub.sdl @@ -6,7 +6,8 @@ license "MPL-2.0" dependency "requests" version="~>2" dependency "thepath" version=">=0.1.7" -dependency "zipper" version=">=0.0.2" +dependency "theprocess" version=">=0.0.1" +dependency "zipper" version=">=0.0.3" dependency "semver" version=">=0.4.0" dependency "pyd" version=">=0.14.4" diff --git a/subpackages/utils/dub.selections.json b/subpackages/utils/dub.selections.json index 1726ac9d..f3dff693 100644 --- a/subpackages/utils/dub.selections.json +++ b/subpackages/utils/dub.selections.json @@ -9,8 +9,9 @@ "pyd": "0.14.4", "requests": "2.1.1", "semver": "0.5.0", - "thepath": "0.1.7", + "thepath": "0.1.8", + "theprocess": "0.0.1", "unit-threaded": "2.1.6", - "zipper": "0.0.2" + "zipper": "0.0.3" } } diff --git a/subpackages/utils/source/odood/utils/addons/addon.d b/subpackages/utils/source/odood/utils/addons/addon.d index 0197743f..f3599fa0 100644 --- a/subpackages/utils/source/odood/utils/addons/addon.d +++ b/subpackages/utils/source/odood/utils/addons/addon.d @@ -2,8 +2,9 @@ module odood.utils.addons.addon; private import std.typecons: Nullable, nullable, tuple; private import std.algorithm.searching: startsWith; -private import std.exception : enforce; -private import std.conv : to; +private import std.exception: enforce; +private import std.conv: to; +private import std.file: SpanMode; private import pyd.embedded: py_eval; private import pyd.pydobject: PydObject; @@ -21,6 +22,11 @@ private struct OdooAddonManifest { _manifest = py_eval(path.readFileText); } + /// Allows to access manifest as pyd object + auto raw_manifest() { + return _manifest; + } + /// Is addon installable bool installable() { return _manifest.get("installable", true).to_d!bool; @@ -56,6 +62,12 @@ private struct OdooAddonManifest { )(currency, price, false); } + /// Return list of dependencies of addon + string[] dependencies() { + if (_manifest.has_key("depends")) + return _manifest["depends"].to_d!(string[]); + return []; + } // TODO: Parse the version to some specific struct, that // have to automatically guess module version in same way as odoo do @@ -157,3 +169,30 @@ Nullable!Path getAddonManifestPath(in Path path) { return path.join("__openerp__.py").nullable; return Nullable!Path.init; } + + +/** Find odoo addons in specified path. + * + * Params: + * path = path to addon or directory that contains addons + * recursive = if set to true, then search for addons in subdirectories + * + * Returns: + * Array of OdooAddons found in specified path. + **/ +OdooAddon[] findAddons(in Path path, in bool recursive=false) { + if (isOdooAddon(path)) + return [new OdooAddon(path)]; + + OdooAddon[] res; + + auto walk_mode = recursive ? SpanMode.breadth : SpanMode.shallow; + foreach(addon_path; path.walk(walk_mode)) { + if (addon_path.isInside(path.join("setup"))) + // Skip modules defined in OCA setup folder to avoid duplication. + continue; + if (addon_path.isOdooAddon) + res ~= new OdooAddon(addon_path); + } + return res; +} diff --git a/subpackages/utils/source/odood/utils/git.d b/subpackages/utils/source/odood/utils/git.d index 9acb24a4..90b03602 100644 --- a/subpackages/utils/source/odood/utils/git.d +++ b/subpackages/utils/source/odood/utils/git.d @@ -8,7 +8,7 @@ private import std.format: format; private import thepath: Path; private import odood.exception: OdoodException; -private import odood.utils.theprocess; +private import theprocess; // TODO: Add parsing of branch name from url diff --git a/subpackages/utils/source/odood/utils/package.d b/subpackages/utils/source/odood/utils/package.d index bb9ebfbb..1f740266 100644 --- a/subpackages/utils/source/odood/utils/package.d +++ b/subpackages/utils/source/odood/utils/package.d @@ -15,10 +15,10 @@ private import std.typecons: Nullable, nullable; private import std.regex; private import thepath: Path; +private import theprocess; private import semver; private import odood.exception: OdoodException; -private import odood.utils.theprocess; /** Parse python version diff --git a/subpackages/utils/source/odood/utils/theprocess.d b/subpackages/utils/source/odood/utils/theprocess.d deleted file mode 100644 index 430b7887..00000000 --- a/subpackages/utils/source/odood/utils/theprocess.d +++ /dev/null @@ -1,546 +0,0 @@ -/** Module to easily run and interact with other processes - **/ -// TODO: Move to separate package (out of Odood) -module odood.utils.theprocess; - -private import std.format; -private import std.process; -private import std.file; -private import std.stdio; -private import std.exception; -private import std.string: join; -private import std.typecons; - -version(Posix) { - private import core.sys.posix.unistd; - private import core.sys.posix.pwd; -} - -private import thepath; - - -/** Resolve program name according to system path - * - * Params: - * name = name of program to find - * Returns: - * Nullable!Path to program. - **/ -@safe Nullable!Path resolveProgram(in string program) { - import std.path: pathSeparator; - import std.array: split; - foreach(sys_path; environment["PATH"].split(pathSeparator)) { - auto sys_program_path = Path(sys_path).join(program); - if (!sys_program_path.exists) - continue; - - // TODO: check with lstat if link is not broken - return sys_program_path.nullable; - } - return Nullable!Path.init; -} - -/// -version(Posix) unittest { - import unit_threaded.assertions; - - resolveProgram("sh").isNull.shouldBeFalse; - resolveProgram("sh").get.toString.shouldEqual("/usr/bin/sh"); - - resolveProgram("unexisting_program").isNull.shouldBeTrue; -} - - -/// Exception to be raise by Process struct -class ProcessException : Exception -{ - mixin basicExceptionCtors; -} - - -/** Process result, produced by 'execute' method of Process. - **/ -@safe const struct ProcessResult { - private string _program; - private string[] _args; - - /// exit code of the process - int status; - - /// text output of the process - string output; - - // Do not allow to create records without params - @disable this(); - - private pure this( - in string program, - in string[] args, - in int status, - in string output) nothrow { - this._program = program; - this._args = args; - this.status = status; - this.output = output; - } - - /** Ensure that program exited with expected exit code - * - * Params: - * msg = message to throw in exception in case of check failure - * expected = expected exit-code, if differ, then - * exception will be thrown. - **/ - auto ref ensureStatus(E : Throwable = ProcessException)( - in string msg, in int expected=0) const { - enforce!E(status == expected, msg); - return this; - } - - /// ditto - auto ref ensureStatus(E : Throwable = ProcessException)(in int expected=0) const { - return ensureStatus( - "Program %s with args %s failed! Expected exit code %s, got %s.\nOutput: %s".format( - _program, _args, expected, status, output), - expected); - } -} - - -/** This struct is used to prepare configuration for process and run it. - * - * Following ways to run process supported: - * - execute: run process and catch its output and exit code - * - spawn: spawn the process in background, and optionally pip its output. - * - pipe: spawn the process and attach configurable pipes to catch output. - * - * The configuration of process to run performend in following a way: - * 1. Create the Process instance specifying the program to run - * 2. Apply desired configuration (args, env, workDir) - * via calls to corresponding methods. - * 3. Run one of `execute`, `spawn` or `pipe` method, that will do actual - * start of the process. - * - * Configuration methods usually prefixed with `set` word, but also, - * they have semantic aliases. For example, method `setArgs` also has - * alias `withArgs`, and method `setWorkDir` has alias `inWorkDir`. - * Additionally, configuration methods always - * return the reference to current instance of the Process being configured. - * - * Examples: - * --- - * // It is possible to run process in following way: - * auto result = Process('my-program') - * .withArgs("--verbose", "--help") - * .withEnv("MY_ENV_VAR", "MY_VALUE") - * .inWorkDir("my/working/directory") - * .execute() - * .ensureStatus!MyException("My error message on failure"); - * writeln(result.output); - * --- - * // Also, in Posix system it is possible to run command as different user: - * auto result = Process('my-program') - * .withUser("bob") - * .execute() - * .ensureStatus!MyException("My error message on failure"); - * writeln(result.output); - * --- - **/ -@safe struct Process { - private string _program; - private string[] _args; - private string[string] _env=null; - private string _workdir=null; - private std.process.Config _config=std.process.Config.none; - - version(Posix) { - /* On posix we have ability to run process with different user, - * thus we have to keep desired uid/gid to run process with and - * original uid/git to revert uid/gid change after process completed. - */ - private Nullable!uid_t _uid; - private Nullable!gid_t _gid; - private Nullable!uid_t _original_uid; - private Nullable!gid_t _original_gid; - } - - /** Create new Process instance to run specified program. - * - * Params: - * program = name of program to run or path of program to run - **/ - this(in string program) { - _program = program; - } - - /// ditto - this(in Path program) { - _program = program.toAbsolute.toString; - } - - /** Return string representation of process to be started - **/ - string toString() const { - return "Program: %s, args: %s, env: %s, workdir: %s".format( - _program, _args.join(" "), _env, _workdir); - } - - /** Set arguments for the process - * - * Params: - * args = array of arguments to run program with - * - * Returns: - * reference to this (process instance) - **/ - auto ref setArgs(in string[] args...) { - _args = args.dup; - return this; - } - - /// ditto - alias withArgs = setArgs; - - /** Add arguments to the process. - * - * This could be used if you do not know all the arguments for program - * to run at single point, and you need to add it conditionally. - * - * Params: - * args = array of arguments to run program with - * - * Returns: - * reference to this (process instance) - * - * Examples: - * --- - * auto program = Process('my-program') - * .withArgs("--some-option"); - * - * if (some condition) - * program.addArgs("--some-other-opt", "--verbose"); - * - * auto result = program - * .execute() - * .ensureStatus!MyException("My error message on failure"); - * writeln(result.output); - * --- - **/ - auto ref addArgs(in string[] args...) { - _args ~= args; - return this; - } - - /** Set work directory for the process to be started - * - * Params: - * workdir = working directory path to run process in - * - * Returns: - * reference to this (process instance) - * - **/ - auto ref setWorkDir(in string workdir) { - _workdir = workdir; - return this; - } - - /// ditto - auto ref setWorkDir(in Path workdir) { - _workdir = workdir.toString; - return this; - } - - /// ditto - alias inWorkDir = setWorkDir; - - /** Set environemnt for the process to be started - * - * Params: - * env = associative array to update environment to run process with. - * - * Returns: - * reference to this (process instance) - * - **/ - auto ref setEnv(in string[string] env) { - foreach(i; env.byKeyValue) - _env[i.key] = i.value; - return this; - } - - /** Set environment variable (specified by key) to provided value - * - * Params: - * key = environment variable name - * value = environment variable value - * - * Returns: - * reference to this (process instance) - * - **/ - auto ref setEnv(in string key, in string value) { - _env[key] = value; - return this; - } - - /// ditto - alias withEnv = setEnv; - - /** Set process configuration - **/ - auto ref setConfig(in std.process.Config config) { - _config = config; - return this; - } - - /// ditto - alias withConfig = setConfig; - - /** Set configuration flag for process to be started - **/ - auto ref setFlag(in std.process.Config.Flags flag) { - _config.flags |= flag; - return this; - } - - /// ditto - auto ref setFlag(in std.process.Config flags) { - _config |= flags; - return this; - } - - /// ditto - alias withFlag = setFlag; - - /** Set UID to run process with - * - * Params: - * uid = UID (id of system user) to run process with - * - * Returns: - * reference to this (process instance) - * - **/ - version(Posix) auto ref setUID(in uid_t uid) { - _uid = uid; - return this; - } - - /// ditto - version(Posix) alias withUID = setUID; - - /** Set GID to run process with - * - * Params: - * gid = GID (id of system group) to run process with - * - * Returns: - * reference to this (process instance) - * - **/ - version(Posix) auto ref setGID(in gid_t gid) { - _gid = gid; - return this; - } - - /// ditto - version(Posix) alias withGID = setGID; - - /** Run process as specified user - * - * If this method applied, then the UID and GID to run process with - * will be taked from record in passwd database - * - * Params: - * username = login of user to run process as - * - * Returns: - * reference to this (process instance) - * - **/ - version(Posix) auto ref setUser(in string username) @trusted { - import std.string: toStringz; - - /* pw info has following fields: - * - pw_name, - * - pw_passwd, - * - pw_uid, - * - pw_gid, - * - pw_gecos, - * - pw_dir, - * - pw_shell, - */ - auto pw = getpwnam(username.toStringz); - errnoEnforce( - pw !is null, - "Cannot get info about user %s".format(username)); - setUID(pw.pw_uid); - setGID(pw.pw_gid); - // TODO: add ability to automatically set user's home directory - // if needed - return this; - } - - /// - version(Posix) alias withUser = setUser; - - /// Called before running process to run pre-exec hooks; - private void setUpProcess() { - version(Posix) { - /* We set real user and real group here, - * keeping original effective user and effective group - * (usually original user/group is root, when such logic used) - * Later in preExecFunction, we can update effective user - * for child process to be same as real user. - * This is needed, because bash, changes effective user to real - * user when effective user is different from real. - * Thus, we have to set both real user and effective user - * for child process. - * - * We can accomplish this in two steps: - * - Change real uid/gid here for current process - * - Change effective uid/gid to match real uid/gid - * in preexec fuction. - * Because preexec function is executed in child process, - * that will be replaced by specified command proces, it works. - * - * Also, note, that first we have to change group ID, because - * when we change user id first, it may not be possible to change - * group. - */ - - /* - * TODO: May be it have sense to change effective user/group - * instead of real user, and update real user in - * child process. - */ - if (!_gid.isNull && _gid.get != getgid) { - _original_gid = getgid().nullable; - errnoEnforce( - setregid(_gid.get, -1) == 0, - "Cannot set real GID to %s before starting process: %s".format( - _gid, this.toString)); - } - if (!_uid.isNull && _uid.get != getuid) { - _original_uid = getuid().nullable; - errnoEnforce( - setreuid(_uid.get, -1) == 0, - "Cannot set real UID to %s before starting process: %s".format( - _uid, this.toString)); - } - - if (!_original_uid.isNull || !_original_gid.isNull) - _config.preExecFunction = () @trusted nothrow @nogc { - /* Because we cannot pass any parameters here, - * we just need to make real user/group equal to - * effective user/group for child proces. - * This is needed, because bash could change effective user - * when it is different from real user. - * - * We change here effective user/group equal - * to real user/group because we have changed - * real user/group in parent process - * before running this function. - * - * Also, note, that this function will be executed - * in child process, just before calling execve. - */ - if (setegid(getgid) != 0) - return false; - if (seteuid(getuid) != 0) - return false; - return true; - }; - - } - } - - /// Called after process started to run post-exec hooks; - private void tearDownProcess() { - version(Posix) { - // Restore original uid/gid after process started. - if (!_original_gid.isNull) - errnoEnforce( - setregid(_original_gid.get, -1) == 0, - "Cannot restore real GID to %s after process started: %s".format( - _original_gid, this.toString)); - if (!_original_uid.isNull) - errnoEnforce( - setreuid(_original_uid.get, -1) == 0, - "Cannot restore real UID to %s after process started: %s".format( - _original_uid, this.toString)); - } - } - - /** Execute the configured process and capture output. - * - * Params: - * max_output = max size of output to capture. - * - * Returns: - * ProcessResult instance that contains output and exit-code - * of program - * - **/ - auto execute(in size_t max_output=size_t.max) { - setUpProcess(); - auto res = std.process.execute( - [_program] ~ _args, - _env, - _config, - max_output, - _workdir); - tearDownProcess(); - return ProcessResult(_program, _args, res.status, res.output); - } - - /// Spawn process - auto spawn(File stdin=std.stdio.stdin, - File stdout=std.stdio.stdout, - File stderr=std.stdio.stderr) { - setUpProcess(); - auto res = std.process.spawnProcess( - [_program] ~ _args, - stdin, - stdout, - stderr, - _env, - _config, - _workdir); - tearDownProcess(); - return res; - } - - /// Pipe process - auto pipe(in Redirect redirect=Redirect.all) { - setUpProcess(); - auto res = std.process.pipeProcess( - [_program] ~ _args, - redirect, - _env, - _config, - _workdir); - tearDownProcess(); - return res; - } -} - - -// Test simple api -@safe unittest { - import unit_threaded.assertions; - - auto process = Process("my-program") - .withArgs("--verbose", "--help") - .withEnv("MY_VAR", "42") - .inWorkDir("/my/path"); - process._program.should == "my-program"; - process._args.should == ["--verbose", "--help"]; - process._env.should == ["MY_VAR": "42"]; - process._workdir.should == "/my/path"; - process.toString.should == - "Program: %s, args: %s, env: %s, workdir: %s".format( - process._program, process._args.join(" "), - process._env, process._workdir); -} diff --git a/tests/basic.d b/tests/basic.d index 1947ad07..5180976d 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -86,6 +86,12 @@ void testDatabaseManagement(in Project project) { project.databases.exists(project.genDbName("test-1")).shouldBeTrue(); project.databases.exists(project.genDbName("test-2")).shouldBeTrue(); + // Drop restored database and try to restore database by backup name + project.databases.drop(project.genDbName("test-2")); + project.databases.restore(project.genDbName("test-2"), backup_path.baseName); + project.databases.exists(project.genDbName("test-1")).shouldBeTrue(); + project.databases.exists(project.genDbName("test-2")).shouldBeTrue(); + // Drop databases project.databases.drop(project.genDbName("test-1")); project.databases.drop(project.genDbName("test-2"));