diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3d0b65c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# 1.1.0 (2024.04.05) + +* Add bash completion +* Add installation script +* Support for `docker compose` command +* Terminate partially started containers if the entire workspace cannot be started + +# 1.0.0 (2024.04.04) + +* Initial release diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index f292e33..947a0c8 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -21,6 +21,8 @@ set(SOURCES state_repository.cpp process_executor.h process_executor.cpp + compose_executor.h + compose_executor.cpp yaml_workspaces_repository.h yaml_workspaces_repository.cpp diff --git a/cli/compose_executor.cpp b/cli/compose_executor.cpp new file mode 100644 index 0000000..b1642ce --- /dev/null +++ b/cli/compose_executor.cpp @@ -0,0 +1,30 @@ +#include "compose_executor.h" + +#include + +using namespace std; + +ComposeExecutorImpl::ComposeExecutorImpl(ProcessExecutorPtr processExecutor) + : processExecutor(processExecutor) +{ + if (processExecutor->exec("docker compose --help > /dev/null") == 0) + composeCommand = "docker compose"; + else + composeCommand = "docker-compose"; +} + +void ComposeExecutorImpl::up(const std::string& file, bool detach) +{ + std::string command = format("{} -f {} up", composeCommand, file); + if (detach) + command += " -d"; + processExecutor->execOrThrow(command); +} + +void ComposeExecutorImpl::down(const std::string& file, bool withVolumes) +{ + std::string command = format("{} -f {} down", composeCommand, file); + if (withVolumes) + command += " -v"; + processExecutor->execOrThrow(command); +} diff --git a/cli/compose_executor.h b/cli/compose_executor.h new file mode 100644 index 0000000..9d5b650 --- /dev/null +++ b/cli/compose_executor.h @@ -0,0 +1,23 @@ +#pragma once + +#include "process_executor.h" + +class ComposeExecutor { +public: + virtual void up(const std::string& file, bool detach) = 0; + virtual void down(const std::string& file, bool withVolumes) = 0; +}; + +using ComposeExecutorPtr = std::shared_ptr; + +class ComposeExecutorImpl : public ComposeExecutor { +public: + ComposeExecutorImpl(ProcessExecutorPtr processExecutor); + + void up(const std::string& file, bool detach); + void down(const std::string& file, bool withVolumes); + +private: + ProcessExecutorPtr processExecutor; + std::string composeCommand; +}; diff --git a/cli/main.cpp b/cli/main.cpp index bbc13d7..de59111 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -13,7 +13,7 @@ #include "commands/remove_command.h" #include "commands/up_command.h" -static const char* APP_VERSION = "1.0.0"; +static const char* APP_VERSION = "1.1.0"; using namespace std; using namespace Args; @@ -48,7 +48,8 @@ int main(int argc, char** argv) auto workspacesRepo = make_shared(yamlConfig); auto stateRepo = make_shared(yamlConfig); auto processExecutor = make_shared(); - auto workspaceService = make_shared(workspacesRepo, stateRepo, processExecutor); + auto composeExecutor = make_shared(processExecutor); + auto workspaceService = make_shared(workspacesRepo, stateRepo, composeExecutor); Commands commands = { make_shared(workspaceService), make_shared(workspaceService), diff --git a/cli/process_executor.cpp b/cli/process_executor.cpp index 355b6d1..12d90e8 100644 --- a/cli/process_executor.cpp +++ b/cli/process_executor.cpp @@ -4,9 +4,14 @@ #include #include -void ProcessExecutor::exec(const std::string& cmdLine) +int ProcessExecutor::exec(const std::string &cmdLine) { - auto res = std::system(cmdLine.c_str()); + return std::system(cmdLine.c_str()); +} + +void ProcessExecutor::execOrThrow(const std::string& cmdLine) +{ + auto res = exec(cmdLine); if (res != 0) throw std::runtime_error(std::format("Command exited with status {}: {}", res, cmdLine)); } diff --git a/cli/process_executor.h b/cli/process_executor.h index 6f0d773..f5f335b 100644 --- a/cli/process_executor.h +++ b/cli/process_executor.h @@ -5,7 +5,8 @@ class ProcessExecutor { public: - void exec(const std::string& cmdLine); + int exec(const std::string& cmdLine); + void execOrThrow(const std::string& cmdLine); }; using ProcessExecutorPtr = std::shared_ptr; diff --git a/cli/workspace_service.cpp b/cli/workspace_service.cpp index 03fe59b..b96a0aa 100644 --- a/cli/workspace_service.cpp +++ b/cli/workspace_service.cpp @@ -8,10 +8,10 @@ using namespace std; namespace tc = termcolor; WorkspaceService::WorkspaceService( - const WorkspacesRepositoryPtr& repo, const StateRepositoryPtr& stateRepo, const ProcessExecutorPtr& processExecutor) + const WorkspacesRepositoryPtr& repo, const StateRepositoryPtr& stateRepo, const ComposeExecutorPtr& composeExecutor) : wpRepo(repo) , stateRepo(stateRepo) - , processExecutor(processExecutor) + , composeExecutor(composeExecutor) { } @@ -53,10 +53,7 @@ void WorkspaceService::down(bool purge) if (!currentWpName.has_value()) return; auto wp = getWorkspace(currentWpName.value()); - string additionalFlags; - if (purge) - additionalFlags += " -v"; - processExecutor->exec(format("docker-compose -f {} down{}", wp.composeFile, additionalFlags)); + composeExecutor->down(wp.composeFile, purge); stateRepo->setCurrentWorkspace(std::nullopt); cout << "✅ " << "Workspace \"" << tc::bold << *currentWpName << tc::reset << "\" stopped" << endl; if (purge) @@ -70,8 +67,16 @@ void WorkspaceService::up(const std::string& name, bool clean) if (currentWpName.has_value() && *currentWpName != name) down(false); if (clean) - processExecutor->exec(format("docker-compose -f {} down -v", wp.composeFile)); - processExecutor->exec(format("docker-compose -f {} up -d", wp.composeFile)); + composeExecutor->down(wp.composeFile, true); + try { + composeExecutor->up(wp.composeFile, true); + } catch (...) { + cerr << "❌ " << "Cannot start workspace \"" << tc::bold << name << tc::reset + << "\". Shutting down partially started containers." << endl; + composeExecutor->down(wp.composeFile, false); + throw; + } + stateRepo->setCurrentWorkspace(name); cout << "✅ " << "Workspace \"" << tc::bold << name << tc::reset << "\" activated" << endl; } diff --git a/cli/workspace_service.h b/cli/workspace_service.h index c9e6deb..f598ae6 100644 --- a/cli/workspace_service.h +++ b/cli/workspace_service.h @@ -2,14 +2,14 @@ #include -#include "process_executor.h" +#include "compose_executor.h" #include "state_repository.h" #include "workspaces_repository.h" class WorkspaceService { public: WorkspaceService(const WorkspacesRepositoryPtr& repo, const StateRepositoryPtr& stateRepo, - const ProcessExecutorPtr& processExecutor); + const ComposeExecutorPtr& composeExecutor); void list(bool namesOnly); void add(const std::string& name, const std::string& composeFile); @@ -23,7 +23,7 @@ class WorkspaceService { WorkspacesRepositoryPtr wpRepo; StateRepositoryPtr stateRepo; - ProcessExecutorPtr processExecutor; + ComposeExecutorPtr composeExecutor; }; using WorkspaceServicePtr = std::shared_ptr; diff --git a/install.sh b/install.sh index 208a374..707b54c 100644 --- a/install.sh +++ b/install.sh @@ -3,7 +3,7 @@ set -e set -x -curl -L https://github.com/navrocky/dcw/releases/download/1.0.0/dcw -o /usr/local/bin/dcw +curl -L https://github.com/navrocky/dcw/releases/download/1.1.0/dcw -o /usr/local/bin/dcw chmod +x /usr/local/bin/dcw curl -L https://github.com/navrocky/dcw/raw/master/completion.bash -o /etc/bash_completion.d/dcw_completion.bash