• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KittensBasket / dasboot / 15243739334

26 May 2025 12:43AM UTC coverage: 65.724% (-8.9%) from 74.584%
15243739334

push

github

web-flow
Fix mega bug with protobuf-parser and sending wrapper. (#57)

* fix mega buf

* comment tests

129 of 282 branches covered (45.74%)

Branch coverage included in aggregate %.

8 of 44 new or added lines in 4 files covered. (18.18%)

89 existing lines in 3 files now uncovered.

1196 of 1734 relevant lines covered (68.97%)

8.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.5
/dasboot/cli/cli.cpp
1
#include <dasboot/cli/cli.hpp>
2

3
namespace NCli {
4
    using std::string;
5

6
    TParser::TParser(const string& description)
7
    : App(description)
24✔
8
    {}
24✔
9

10
    string TParser::BuildFullName(const string& shortName, const string& longName) {
440✔
11
        return shortName + "," + longName;
440✔
12
    }
440✔
13

14
    void TParser::AddGlobalFlag(const string& shortName, const string& longName, bool& flag, const string& description) {
24✔
15
        App.add_flag(BuildFullName(shortName, longName), flag, description);
24✔
16
    }
24✔
17

18
    void TParser::AddGlobalOption(const string& shortName, const string& longName, TValue& value, const string& description) {
×
19
        App.add_option(BuildFullName(shortName, longName), value, description);
×
20
    }
×
21

22
    void TParser::AddGlobalOption(const string& shortName, const string& longName, TValue& value, const string& description, CLI::Validator&& validator) {
×
23
        App.add_option(BuildFullName(shortName, longName), value, description)->transform(std::move(validator));
×
24
    }
×
25

26
    void TParser::AddGlobalCommand(const string& commandName, const string& description){
208✔
27
        Commands[commandName] = App.add_subcommand(commandName, description);
208✔
28
    }
208✔
29

30
    void TParser::AddLocalFlag(const string& commandName, const string& shortName, const string& longName, bool& flag, const string& description) {
93✔
31
        Commands[commandName]->add_option(BuildFullName(shortName, longName), flag, description);
93✔
32
    }
93✔
33

34
    void TParser::AddLocalOption(const string& commandName, const string& shortName, const string& longName, TValue& value, const string& description) {
323✔
35
        Commands[commandName]->add_option(BuildFullName(shortName, longName), value, description);
323✔
36
    }
323✔
37

38
    void TParser::AddLocalOption(const string& commandName, const string& shortName, const string& longName, TValue& value, const string& description, CLI::Validator&& validator) {
×
39
        Commands[commandName]->add_option(BuildFullName(shortName, longName), value, description)->transform(validator);
×
40
    }
×
41

42
    string TParser::FindCalledCommand() {
×
43
        for (const auto& [command, interface] : Commands) {
×
44
            if (interface->parsed() && (!interface->get_help_ptr()->as<bool>())) {
×
45
                return command;
×
46
            }
×
47
        }
×
48
        return "";
×
49
    }
×
50
    
51
    int TParser::Parse(int argc, char* argv[]) {
22✔
52
        CLI11_PARSE(App, argc, argv);
22✔
53
        return 0;
22✔
54
    }
22✔
55

56
    string TParser::GetHelp() const {
2✔
57
        return App.help();
2✔
58
    }
2✔
59

60
    namespace {
61
        // Dasboot description
62
        static const string DasbootDescription = "A small containerization utility, written in C/C++. Made as team pet project.";
63
    
64
        // Common option descriptions
65
        static const string ContainerNameDescription = "Container name";
66
        static const string ContainerIdDescription = "Container ID";
67
        static const string BuildFromFileDescription = "Create a container from a DasbootFile";
68
        static const string ShowAllContainersDescription = "List all containers, including stopped ones.";
69
        static const string DetachFlagDescription = "Detached mode: run command in the background";
70
        static const string NoStdinFlagDescription = "Do not attach STDIN";
71
        static const string ExecFileDescription = "Path to ExecFile";
72
        static const string InteractiveContainerDescription = "Run container in interactive mode"; //maybe edit
73

74
        // Command descriptions
75
        static const string VersionDescription = "Print version information and quit";
76
        static const string InfoDescription = "Display system-wide information";
77
        static const string BuildDescription = "Create a container";
78
        static const string RunDescription = "Run container in interactive mode";
79
        static const string StartDescription = "Launch a container by name or ID depending on specified options";
80
        static const string StopDescription = "Terminate a running container";
81
        static const string PsDescription = "Display all available containers.";
82
        static const string RmDescription = "Delete a container by name or ID depending on specified options";
83
        static const string ExecDescription = "Run one or multiple commands inside an already running container";
84
        static const string AttachDescription = "Connect a terminal to a running container by its ID or name for interactive management and output monitoring";
85
    } // anonymous namespace
86

87
    void TParser::RegisterCommands(TMainSettings& mainSettings) {
23✔
88
        AddGlobalFlag("-v", "--version", mainSettings.Version.PrintVersion, VersionDescription);
23✔
89

90
        AddGlobalCommand("info", InfoDescription);
23✔
91

92
        AddGlobalCommand("build", BuildDescription);
23✔
93
        AddLocalOption("build", "-n", "--name", mainSettings.BuildOptions.Name, ContainerNameDescription);
23✔
94
        AddLocalOption("build", "-f", "--file", mainSettings.BuildOptions.PathToDasbootFile, BuildFromFileDescription);
23✔
95

96
        AddGlobalCommand("run", RunDescription);
23✔
97
        AddLocalOption("run", "-n", "--name", mainSettings.RunOptions.Name, ContainerNameDescription);
23✔
98

99
        AddGlobalCommand("start", StartDescription);
23✔
100
        AddLocalOption("start", "-n", "--name",  mainSettings.StartOptions.Name, ContainerNameDescription);
23✔
101
        AddLocalOption("start", "-i", "--id",  mainSettings.StartOptions.Id, ContainerIdDescription);
23✔
102

103
        AddGlobalCommand("stop", StopDescription);
23✔
104
        AddLocalOption("stop", "-n", "--name", mainSettings.StopOptions.Name, ContainerNameDescription);
23✔
105
        AddLocalOption("stop", "-i", "--id", mainSettings.StopOptions.Id, ContainerIdDescription);
23✔
106

107
        AddGlobalCommand("ps", PsDescription);
23✔
108
        AddLocalFlag("ps", "-a", "--all", mainSettings.PsOptions.ShowAll, ShowAllContainersDescription);
23✔
109

110
        AddGlobalCommand("rm", RmDescription);
23✔
111
        AddLocalOption("rm", "-n", "--name", mainSettings.RmOptions.Name, ContainerNameDescription);
23✔
112
        AddLocalOption("rm", "-i", "--id", mainSettings.RmOptions.Id, ContainerIdDescription);
23✔
113

114
        AddGlobalCommand("exec", ExecDescription);
23✔
115
        AddLocalOption("exec", "-n", "--name", mainSettings.ExecOptions.Name, ContainerNameDescription);
23✔
116
        AddLocalOption("exec", "-x", "--id", mainSettings.ExecOptions.Id, ContainerIdDescription);
23✔
117
        AddLocalFlag("exec", "-i", "--interactive", mainSettings.ExecOptions.IsInteractive, InteractiveContainerDescription);
23✔
118
        AddLocalOption("exec", "-f", "--file", mainSettings.ExecOptions.ExecFile, ExecFileDescription);
23✔
119
        AddLocalFlag("exec", "-d", "--detach", mainSettings.ExecOptions.Detach, DetachFlagDescription);
23✔
120

121
        AddGlobalCommand("attach", AttachDescription);
23✔
122
        AddLocalOption("attach", "-n", "--name", mainSettings.AttachOptions.Name, ContainerNameDescription);
23✔
123
        AddLocalOption("attach", "-i", "--id", mainSettings.AttachOptions.Id, ContainerIdDescription);
23✔
124
        AddLocalFlag("attach", "", "--no-stdin", mainSettings.AttachOptions.NoStdin, NoStdinFlagDescription);
23✔
125
    }
23✔
126

127
    TSender::TSender(const string& address)
NEW
128
        : Controller(address)
×
NEW
129
    {}
×
130

UNCOV
131
    void TSender::SendMainSettings(const TMainSettings& mainSettings, const string& command) {
×
UNCOV
132
        if (mainSettings.Version.PrintVersion) {
×
133
            //print version -- function in controller
134
        }
×
135

UNCOV
136
        else if (command == "info") {
×
137
            //controller handle call
138
        }
×
139

UNCOV
140
        else if (command == "build") {
×
UNCOV
141
            NMessages::TBuildOptions ProtoBuildOptions;
×
UNCOV
142
            ProtoBuildOptions = TConverter::ConvertBuildOptions(mainSettings.BuildOptions, ProtoBuildOptions);
×
UNCOV
143
            Controller.Build(ProtoBuildOptions);
×
UNCOV
144
        } 
×
145

UNCOV
146
        else if (command == "run") {
×
UNCOV
147
            NMessages::TRunOptions ProtoRunOptions;
×
UNCOV
148
            ProtoRunOptions = TConverter::ConvertRunOptions(mainSettings.RunOptions, ProtoRunOptions);
×
UNCOV
149
            Controller.Run(ProtoRunOptions);
×
UNCOV
150
        } 
×
151

UNCOV
152
        else if (command == "start") {
×
UNCOV
153
            NMessages::TStartOptions ProtoStartOptions;
×
UNCOV
154
            ProtoStartOptions = TConverter::ConvertStartOptions(mainSettings.StartOptions, ProtoStartOptions);
×
UNCOV
155
            Controller.Start(ProtoStartOptions);
×
UNCOV
156
        }
×
157

UNCOV
158
        else if (command == "stop") {
×
UNCOV
159
            NMessages::TStopOptions ProtoStopOptions;
×
UNCOV
160
            ProtoStopOptions = TConverter::ConvertStopOptions(mainSettings.StopOptions, ProtoStopOptions);
×
UNCOV
161
            Controller.Stop(ProtoStopOptions);
×
UNCOV
162
        }
×
163

UNCOV
164
        else if (command == "ps") {
×
UNCOV
165
            NMessages::TPsOptions ProtoPsOptions;
×
UNCOV
166
            ProtoPsOptions = TConverter::ConvertPsOptions(mainSettings.PsOptions, ProtoPsOptions);
×
UNCOV
167
            Controller.Ps(ProtoPsOptions);
×
UNCOV
168
        }
×
169

UNCOV
170
        else if (command == "rm") {
×
UNCOV
171
            NMessages::TRmOptions ProtoRmOptions;
×
UNCOV
172
            ProtoRmOptions = TConverter::ConvertRmOptions(mainSettings.RmOptions, ProtoRmOptions);
×
UNCOV
173
            Controller.Rm(ProtoRmOptions);
×
UNCOV
174
        }
×
175

UNCOV
176
        else if (command == "exec") {
×
UNCOV
177
            NMessages::TExecOptions ProtoExecOptions;
×
UNCOV
178
            ProtoExecOptions = TConverter::ConvertExecOptions(mainSettings.ExecOptions, ProtoExecOptions);
×
UNCOV
179
            Controller.Exec(ProtoExecOptions);
×
UNCOV
180
        }
×
181

182
        else if (command == "attach") {
×
183
            NMessages::TAttachOptions ProtoAttachOptions;
×
184
            ProtoAttachOptions = TConverter::ConvertAttachOptions(mainSettings.AttachOptions, ProtoAttachOptions);
×
185
            //controller handle call
186
        }
×
187

188
        else {
×
189
            throw std::runtime_error("Unknown command");
×
190
        }
×
191

192
        
UNCOV
193
    }
×
194

195
    string GetReadFileResult(const string& path) {
8✔
196
        auto [result, status] = NOs::ReadFile(path);
8✔
197
        auto [statusCode, errorMsg] = status;
8✔
198
        if (statusCode == NCommon::TStatus::ECode::Failed) {
8!
199
            throw std::runtime_error("Error: " + errorMsg);
×
200
        }
×
201
        return result;
8✔
202
    }
8✔
203
    
204
    string TConverter::GetFilename(const string& path) {
4✔
205
        size_t pos = path.find_last_of("/\\");
4✔
206
        return (pos != string::npos) ? path.substr(pos + 1) : path;
4!
207
    }
4✔
208

209
    string TConverter::ReadDasbootFile(const string& path) {
2✔
210
        std::ifstream DasbootFile(path);
2✔
211
        nlohmann::json jsonDasbootFile, resultJson;
2✔
212
        std::vector<string> CopyFile, CopyFileNames;
2✔
213

214
        try {
2✔
215
            jsonDasbootFile = nlohmann::json::parse(DasbootFile);
2✔
216
        } catch (const nlohmann::json::parse_error& e) {
2✔
217
            throw std::runtime_error("JSON parse error: " + string(e.what()));
×
218
        } catch (const std::exception& e) {
×
219
            throw std::runtime_error("Error reading JSON: " + string(e.what()));
×
220
        }
×
221

222
        if (jsonDasbootFile.contains("network")) {
2!
223
            if (!jsonDasbootFile["network"].is_boolean()) {
2!
224
                throw std::runtime_error("Field 'network' must be boolean (true or false)");
×
225
            }
×
226
            else {
2✔
227
                resultJson["network"] = jsonDasbootFile["network"];
2✔
228
            }
2✔
229
        }
2✔
230

231
        if (jsonDasbootFile.contains("script_file") && !jsonDasbootFile["script_file"].is_null()) {
2!
232
            if (jsonDasbootFile["script_file"].is_string()) {
2!
233
                string result = GetReadFileResult(jsonDasbootFile["script_file"]);
2✔
234
                resultJson["script_code"] = result;
2✔
235
            } 
2✔
236
        }
2✔
237

238
        if (jsonDasbootFile.contains("copy_file") && !jsonDasbootFile["copy_file"].is_null()) {
2!
239
            if (jsonDasbootFile["copy_file"].is_string()) {
1!
240
                string result = GetReadFileResult(jsonDasbootFile["copy_file"]);
1✔
241
                CopyFile.push_back(result);
1✔
242
                CopyFileNames.push_back(GetFilename(jsonDasbootFile["copy_file"]));
1✔
243
            } 
1✔
244
            else if (jsonDasbootFile["copy_file"].is_array()) {
×
245
                for (const auto& CodePath : jsonDasbootFile["copy_file"]) {
×
246
                    if (CodePath.is_string()) {
×
247
                        string result = GetReadFileResult(CodePath);
×
248
                        CopyFile.push_back(result);
×
249
                        CopyFileNames.push_back(GetFilename(CodePath));
×
250
                    } else {
×
251
                        throw std::runtime_error("Error: non-string element in copy_file array");
×
252
                    }
×
253
                }
×
254
            }
×
255
            resultJson["copy_file"] = CopyFile;
1✔
256
            resultJson["copy_file_names"] = CopyFileNames;
1✔
257
        }
1✔
258

259
        return resultJson.dump();
2✔
260
    }
2✔
261

262
    string TConverter::ReadExecFile(const string& path, bool isInteractive) {
2✔
263
        std::ifstream ExecFile(path);
2✔
264
        nlohmann::json jsonExecFile, resultJson;
2✔
265
        string pathToScript, pathToCopyFile;
2✔
266
        std::vector<string> CopyFile, CopyFileNames;
2✔
267
        
268
        try {
2✔
269
            jsonExecFile = nlohmann::json::parse(ExecFile);
2✔
270
        } catch (const nlohmann::json::parse_error& e) {
2✔
271
            throw std::runtime_error("JSON parse error: " + string(e.what()));
×
272
        } catch (const std::exception& e) {
×
273
            throw std::runtime_error("Error reading JSON: " + string(e.what()));
×
274
        }
×
275

276
        if (jsonExecFile.contains("network")) {
2!
277
            if (!jsonExecFile["network"].is_boolean()) {
2!
278
                throw std::runtime_error("Field 'network' must be boolean (true or false)");
×
279
            }
×
280
            else {
2✔
281
                resultJson["network"] = jsonExecFile["network"];
2✔
282
            }
2✔
283
        }
2✔
284

285
        if (jsonExecFile.contains("copy_file")) {
2!
286
            if (jsonExecFile["copy_file"].is_string()) {
2✔
287
                string result = GetReadFileResult(jsonExecFile["copy_file"]);
1✔
288
                CopyFile.push_back(result);
1✔
289
                CopyFileNames.push_back(GetFilename(jsonExecFile["copy_file"]));
1✔
290
            } 
1✔
291
            else if (jsonExecFile["copy_file"].is_array()) {
1!
292
                for (const auto& CodePath : jsonExecFile["copy_file"]) {
2✔
293
                    if (CodePath.is_string()) {
2!
294
                        string result = GetReadFileResult(CodePath);
2✔
295
                        CopyFile.push_back(result);
2✔
296
                        CopyFileNames.push_back(GetFilename(CodePath));
2✔
297
                    } else {
2✔
298
                        throw std::runtime_error("Error: non-string element in copy_file array");
×
299
                    }
×
300
                }
2✔
301
            }
1✔
302
            resultJson["copy_file"] = CopyFile;
2✔
303
            resultJson["copy_file_names"] = CopyFileNames;
2✔
304

305
        }
2✔
306

307
        if (jsonExecFile.contains("script_file")) {
2!
308
            if (!isInteractive) {
2!
309
                string result = GetReadFileResult(jsonExecFile["script_file"]);
2✔
310
                resultJson["script_code"] = result;
2✔
311
            } 
2✔
312
            else {
×
313
                throw std::runtime_error("When --interactive flag is used, 'script_file' field must not be present");
×
314
            }
×
315
        } else if (!isInteractive) {
2!
316
            throw std::runtime_error("There must be 'script_file' field");
×
317
        }
×
318

319
        return resultJson.dump();
2✔
320
    }
2✔
321
    
322
    NMessages::TBuildOptions TConverter::ConvertBuildOptions(const NCli::TBuildOptions& options, NMessages::TBuildOptions& protoOptions) {
2✔
323
        if (options.Name.has_value()) {
2✔
324
            protoOptions.set_name(options.Name.value());
1✔
325
        }
1✔
326

327
        if (options.PathToDasbootFile.has_value()) {
2!
328
            string DasbootFile = ReadDasbootFile(options.PathToDasbootFile.value());
2✔
329
            protoOptions.set_dasboot_file(DasbootFile);
2✔
330
        }
2✔
331
        else {
×
332
            throw std::runtime_error("Path to DasbootFile must be specified");
×
333
        }
×
334
        return protoOptions;
2✔
335
    }
2✔
336

337
    NMessages::TRunOptions TConverter::ConvertRunOptions(const NCli::TRunOptions& options, NMessages::TRunOptions& protoOptions) {
3✔
338
        if (options.Name.has_value()) {
3✔
339
            protoOptions.set_name(options.Name.value());
2✔
340
        }
2✔
341

342
        return protoOptions;
3✔
343
    }
3✔
344

345
    NMessages::TStartOptions TConverter::ConvertStartOptions(const NCli::TStartOptions& options, NMessages::TStartOptions& protoOptions) {
3✔
346
        if (options.Name.has_value()) {
3✔
347
            protoOptions.set_name(options.Name.value());
2✔
348
        }
2✔
349

350
        if (options.Id.has_value()) {
3✔
351
            protoOptions.set_id(options.Id.value());
1✔
352
        }
1✔
353

354
        return protoOptions;
3✔
355
    }
3✔
356
    
357
    NMessages::TStopOptions TConverter::ConvertStopOptions(const NCli::TStopOptions& options, NMessages::TStopOptions& protoOptions) {
4✔
358
        if (options.Name.has_value()) {
4✔
359
            protoOptions.set_name(options.Name.value());
2✔
360
        }
2✔
361

362
        if (options.Id.has_value()) {
4✔
363
            protoOptions.set_id(options.Id.value());
2✔
364
        }
2✔
365

366
        return protoOptions;
4✔
367
    }
4✔
368
    
369
    NMessages::TPsOptions TConverter::ConvertPsOptions(const NCli::TPsOptions& options, NMessages::TPsOptions& protoOptions) {
2✔
370
        if (options.ShowAll) {
2✔
371
            protoOptions.set_show_all(options.ShowAll);
1✔
372
        }
1✔
373

374
        return protoOptions;
2✔
375
    }
2✔
376
    
377
    NMessages::TRmOptions TConverter::ConvertRmOptions(const NCli::TRmOptions& options, NMessages::TRmOptions& protoOptions) {
4✔
378
        if (options.Name.has_value()) {
4✔
379
            protoOptions.set_name(options.Name.value());
2✔
380
        }
2✔
381

382
        if (options.Id.has_value()) {
4✔
383
            protoOptions.set_id(options.Id.value());
2✔
384
        }
2✔
385

386
        return protoOptions;
4✔
387
    }
4✔
388
    
389
    NMessages::TExecOptions TConverter::ConvertExecOptions(const NCli::TExecOptions& options, NMessages::TExecOptions& protoOptions) {
3✔
390
        if (options.Name.has_value()) {
3!
UNCOV
391
            protoOptions.set_name(options.Name.value());
×
UNCOV
392
        }
×
393

394
        if (options.Id.has_value()) {
3!
UNCOV
395
            protoOptions.set_id(options.Id.value());
×
UNCOV
396
        }
×
397

398
        if (options.IsInteractive) {
3✔
399
            if (options.ExecFile.has_value()) {
1!
400
                string result = ReadExecFile(options.ExecFile.value(), true);
×
401
                protoOptions.set_exec_file(result);
×
402
            }
×
403
            protoOptions.set_is_interactive(options.IsInteractive);
1✔
404
        }
1✔
405
        else if (options.ExecFile.has_value()) {
2!
406
            string result = ReadExecFile(options.ExecFile.value(), false);
2✔
407
            protoOptions.set_exec_file(result);
2✔
408
        }
2✔
409
        else {
×
410
            throw std::runtime_error("You forgot flag --interactive or ExecFile");
×
411
        }
×
412

413
        if (options.Detach) {
3!
UNCOV
414
            protoOptions.set_detach(options.Detach);
×
UNCOV
415
        }
×
416

417
        return protoOptions;
3✔
418
    }
3✔
419
    
420
    NMessages::TAttachOptions TConverter::ConvertAttachOptions(const NCli::TAttachOptions& options, NMessages::TAttachOptions& protoOptions) {
1✔
421
        if (options.Name.has_value()) {
1!
422
            protoOptions.set_name(options.Name.value());
1✔
423
        }
1✔
424

425
        if (options.Id.has_value()) {
1!
426
            protoOptions.set_id(options.Id.value());
1✔
427
        }
1✔
428

429
        if (options.NoStdin) {
1!
430
            protoOptions.set_nostdin(options.NoStdin);
1✔
431
        }
1✔
432

433
        return protoOptions;
1✔
434
    }
1✔
435

436
    std::unique_ptr<TParser> MakeDasbootParser(TMainSettings& settings) {
23✔
437
        std::unique_ptr<TParser> parser(new TParser{DasbootDescription});
23✔
438
        parser->RegisterCommands(settings);
23✔
439
        return parser;
23✔
440
    }
23✔
441
}; // namespace NCli
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc