aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2023-02-02 10:51:07 -0800
committerGitHub <[email protected]>2023-02-02 19:51:07 +0100
commitc9a2721560dc4cd8bc2d2190126bb86c3ab8c3d7 (patch)
tree24e932b290ddf1e2f98651ab0b39273a6227b2ce
parentReduce lock scopes in oplog (#220) (diff)
downloadzen-c9a2721560dc4cd8bc2d2190126bb86c3ab8c3d7.tar.xz
zen-c9a2721560dc4cd8bc2d2190126bb86c3ab8c3d7.zip
Add `project-create` and `oplog-create` to zen command line tool (#221)
* Add `project-create` and `oplog-create` to zen command line tool
-rw-r--r--CHANGELOG.md2
-rw-r--r--zen/cmds/projectstore.cpp132
-rw-r--r--zen/cmds/projectstore.h36
-rw-r--r--zen/zen.cpp66
-rw-r--r--zen/zen.h9
5 files changed, 242 insertions, 3 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5690c28c..3bb224d8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,6 @@
##
+- Feature: Zen command line tool `project-create` to create a project store project
+- Feature: Zen command line tool `oplog-create` to create a project store oplog
- Improvement: Faster oplog replay - reduces time to open an existing oplog
- Improvement: Clearer error messages and logging when requests to project store fails
- Changed: Removed remnants of old mesh experiment
diff --git a/zen/cmds/projectstore.cpp b/zen/cmds/projectstore.cpp
index 03e1130cb..d31a11abc 100644
--- a/zen/cmds/projectstore.cpp
+++ b/zen/cmds/projectstore.cpp
@@ -253,6 +253,138 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
///////////////////////////////////////
+CreateProjectCommand::CreateProjectCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+ m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value("http://localhost:1337"), "<hosturl>");
+ m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>");
+ m_Options.add_option("", "", "rootdir", "Absolute path to root directory", cxxopts::value(m_RootDir), "<root>");
+ m_Options.add_option("", "", "enginedir", "Absolute path to engine root directory", cxxopts::value(m_EngineRootDir), "<engineroot>");
+ m_Options.add_option("", "", "projectdir", "Absolute path to project directory", cxxopts::value(m_ProjectRootDir), "<projectroot>");
+ m_Options.add_option("", "", "projectfile", "Absolute path to .uproject file", cxxopts::value(m_ProjectFile), "<projectfile>");
+ m_Options.parse_positional({"project", "rootdir", "enginedir", "projectdir", "projectfile"});
+}
+
+CreateProjectCommand::~CreateProjectCommand() = default;
+
+int
+CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ using namespace std::literals;
+
+ if (!ParseOptions(argc, argv))
+ {
+ return 0;
+ }
+
+ cpr::Session Session;
+ Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
+
+ if (m_ProjectId.empty())
+ {
+ ZEN_ERROR("Project name must be given");
+ return 1;
+ }
+
+ Session.SetUrl({fmt::format("{}/prj/{}", m_HostName, m_ProjectId)});
+ cpr::Response Response = Session.Get();
+ if (zen::IsHttpSuccessCode(Response.status_code))
+ {
+ ZEN_CONSOLE("Project already exists.\n{}", Response.text);
+ return 1;
+ }
+
+ if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound))
+ {
+ zen::CbObjectWriter Project;
+ Project.AddString("id"sv, m_ProjectId);
+ Project.AddString("root"sv, m_RootDir);
+ Project.AddString("engine"sv, m_EngineRootDir);
+ Project.AddString("project"sv, m_ProjectRootDir);
+ Project.AddString("projectfile"sv, m_ProjectFile);
+ zen::IoBuffer ProjectPayload = Project.Save().GetBuffer().AsIoBuffer();
+ Session.SetBody(cpr::Body{(const char*)ProjectPayload.GetData(), ProjectPayload.GetSize()});
+ Session.SetHeader(cpr::Header{{"Accept", "text"}});
+ Response = Session.Post();
+ }
+
+ ZEN_CONSOLE("{}", FormatResponse(Response));
+ return GetReturnCode(Response);
+}
+
+///////////////////////////////////////
+
+CreateOplogCommand::CreateOplogCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+ m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value("http://localhost:1337"), "<hosturl>");
+ m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>");
+ m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogId), "<oplogid>");
+ m_Options.add_option("", "", "gcpath", "Absolute path to oplog lifetime marker file", cxxopts::value(m_GcPath), "<path>");
+ m_Options.parse_positional({"project", "oplog", "gcpath"});
+}
+
+CreateOplogCommand::~CreateOplogCommand() = default;
+
+int
+CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ using namespace std::literals;
+
+ if (!ParseOptions(argc, argv))
+ {
+ return 0;
+ }
+
+ cpr::Session Session;
+ Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
+
+ if (m_ProjectId.empty())
+ {
+ ZEN_ERROR("Project name must be given");
+ return 1;
+ }
+
+ if (m_OplogId.empty())
+ {
+ ZEN_ERROR("Oplog name must be given");
+ return 1;
+ }
+
+ Session.SetUrl({fmt::format("{}/prj/{}/oplog/{}", m_HostName, m_ProjectId, m_OplogId)});
+ cpr::Response Response = Session.Get();
+ if (zen::IsHttpSuccessCode(Response.status_code))
+ {
+ ZEN_CONSOLE("Oplog already exists.\n{}", Response.text);
+ return 1;
+ }
+
+ if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound))
+ {
+ Session.SetHeader(cpr::Header{{"Accept", "text"}});
+ if (!m_GcPath.empty())
+ {
+ zen::CbObjectWriter Oplog;
+ Oplog.AddString("gcpath"sv, m_GcPath);
+ zen::IoBuffer OplogPayload = Oplog.Save().GetBuffer().AsIoBuffer();
+ Session.SetBody(cpr::Body{(const char*)OplogPayload.GetData(), OplogPayload.GetSize()});
+ Session.SetHeader(cpr::Header{{"Accept", "text"}, {"Content-Type", std::string(ToString(zen::HttpContentType::kCbObject))}});
+ }
+
+ Response = Session.Post();
+ }
+
+ ZEN_CONSOLE("{}", FormatResponse(Response));
+
+ return GetReturnCode(Response);
+}
+
+///////////////////////////////////////
+
ExportProjectCommand::ExportProjectCommand()
{
m_Options.add_options()("h,help", "Print help");
diff --git a/zen/cmds/projectstore.h b/zen/cmds/projectstore.h
index 5d42afd81..1bdcbdfac 100644
--- a/zen/cmds/projectstore.h
+++ b/zen/cmds/projectstore.h
@@ -35,6 +35,42 @@ private:
std::string m_OplogName;
};
+class CreateProjectCommand : public ZenCmdBase
+{
+public:
+ CreateProjectCommand();
+ ~CreateProjectCommand();
+
+ virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual cxxopts::Options& Options() override { return m_Options; }
+
+private:
+ cxxopts::Options m_Options{"project-create", "Create project"};
+ std::string m_HostName;
+ std::string m_ProjectId;
+ std::string m_RootDir;
+ std::string m_EngineRootDir;
+ std::string m_ProjectRootDir;
+ std::string m_ProjectFile;
+};
+
+class CreateOplogCommand : public ZenCmdBase
+{
+public:
+ CreateOplogCommand();
+ ~CreateOplogCommand();
+
+ virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual cxxopts::Options& Options() override { return m_Options; }
+
+private:
+ cxxopts::Options m_Options{"oplog-create", "Create oplog"};
+ std::string m_HostName;
+ std::string m_ProjectId;
+ std::string m_OplogId;
+ std::string m_GcPath;
+};
+
class ExportProjectCommand : public ZenCmdBase
{
public:
diff --git a/zen/zen.cpp b/zen/zen.cpp
index 180dea5f4..d7b552814 100644
--- a/zen/zen.cpp
+++ b/zen/zen.cpp
@@ -24,12 +24,17 @@
#include <zencore/string.h>
#include <zencore/zencore.h>
+#include <zenhttp/httpcommon.h>
+
#if ZEN_WITH_TESTS
# define ZEN_TEST_WITH_RUNNER
# include <zencore/testing.h>
#endif
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <cpr/cpr.h>
#include <gsl/gsl-lite.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
#if ZEN_USE_MIMALLOC
# include <mimalloc-new-delete.h>
@@ -87,6 +92,63 @@ ZenCmdBase::ParseOptions(int argc, char** argv)
return true;
}
+std::string
+ZenCmdBase::FormatResponse(const cpr::Response& Response)
+{
+ if (Response.error.code != cpr::ErrorCode::OK)
+ {
+ if (Response.error.message.empty())
+ {
+ return fmt::format("Request '{}' failed, error code {}", Response.url.str(), static_cast<int>(Response.error.code));
+ }
+ return fmt::format("Request '{}' failed. Reason: '{}' ({})",
+ Response.url.str(),
+ Response.error.message,
+ static_cast<int>(Response.error.code));
+ }
+
+ std::string Content;
+ if (auto It = Response.header.find("Content-Type"); It != Response.header.end())
+ {
+ zen::HttpContentType ContentType = zen::ParseContentType(It->second);
+ if (ContentType == zen::HttpContentType::kText)
+ {
+ Content = fmt::format("'{}'", Response.text);
+ }
+ else if (ContentType == zen::HttpContentType::kJSON)
+ {
+ Content = fmt::format("\n{}", Response.text);
+ }
+ else if (!Response.text.empty())
+ {
+ Content = fmt::format("[{}]", MapContentTypeToString(ContentType));
+ }
+ }
+
+ std::string_view ResponseString = zen::ReasonStringForHttpResultCode(
+ Response.status_code == static_cast<long>(zen::HttpResponseCode::NoContent) ? static_cast<long>(zen::HttpResponseCode::OK)
+ : Response.status_code);
+ if (Content.empty())
+ {
+ return std::string(ResponseString);
+ }
+ return fmt::format("{}: {}", ResponseString, Content);
+}
+
+int
+ZenCmdBase::GetReturnCode(const cpr::Response& Response)
+{
+ if (zen::IsHttpSuccessCode(Response.status_code))
+ {
+ return 0;
+ }
+ if (Response.error.code != cpr::ErrorCode::OK)
+ {
+ return static_cast<int>(Response.error.code);
+ }
+ return 1;
+}
+
#if ZEN_WITH_TESTS
class RunTestsCommand : public ZenCmdBase
@@ -159,6 +221,8 @@ main(int argc, char** argv)
CacheInfoCommand CacheInfoCmd;
DropProjectCommand ProjectDropCmd;
ProjectInfoCommand ProjectInfoCmd;
+ CreateProjectCommand CreateProjectCmd;
+ CreateOplogCommand CreateOplogCmd;
GcCommand GcCmd;
GcStatusCommand GcStatusCmd;
@@ -191,6 +255,8 @@ main(int argc, char** argv)
{"cache-info", &CacheInfoCmd, "Info on cache, namespace or bucket"},
{"project-drop", &ProjectDropCmd, "Drop project or project oplog"},
{"project-info", &ProjectInfoCmd, "Info on project or project oplog"},
+ {"project-create", &CreateProjectCmd, "Create a project"},
+ {"oplog-create", &CreateOplogCmd, "Create a project oplog"},
{"gc", &GcCmd, "Garbage collect zen storage"},
{"gc-status", &GcStatusCmd, "Garbage collect zen storage status check"},
#if ZEN_WITH_TESTS
diff --git a/zen/zen.h b/zen/zen.h
index 40fb65257..d786b3148 100644
--- a/zen/zen.h
+++ b/zen/zen.h
@@ -2,7 +2,6 @@
#pragma once
-#include <zencore/refcount.h>
#include <zencore/zencore.h>
ZEN_THIRD_PARTY_INCLUDES_START
@@ -13,7 +12,9 @@ ZEN_THIRD_PARTY_INCLUDES_END
# include <zencore/windows.h>
#endif
-#include <filesystem>
+namespace cpr {
+class Response;
+}
struct ZenCliOptions
{
@@ -31,5 +32,7 @@ public:
virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) = 0;
virtual cxxopts::Options& Options() = 0;
- bool ParseOptions(int argc, char** argv);
+ bool ParseOptions(int argc, char** argv);
+ static std::string FormatResponse(const cpr::Response& Response);
+ static int GetReturnCode(const cpr::Response& Response);
};