1#![doc = include_str!("../README.md")]
2use std::{
3 fs::{create_dir, File},
4 io::{Read, Write},
5 path::{Path, PathBuf},
6 process::Command as SysCommand,
7};
8
9use anyhow::{bail, Context};
10use clap::{Args, Parser, Subcommand};
11use tempfile::tempdir;
12
13pub const DEFAULT_LUA_INIT: &str = include_str!("default_init.lua");
14
15#[derive(Parser)]
17#[command(author, version, about, long_about = None)]
18#[command(propagate_version = true)]
19pub struct Cli {
20 #[command(subcommand)]
21 command: Command,
22}
23
24impl Cli {
25 pub fn process_command(self) -> anyhow::Result<()> {
29 match self.command {
30 Command::Run(args) => process_run_command(args),
31 }
32 }
33}
34
35#[derive(Subcommand, Clone)]
36enum Command {
37 Run(RunArgs),
38}
39
40#[derive(Args, Clone)]
41struct RunArgs {
42 #[arg(
43 short,
44 long,
45 help = r#"A path to the package you want to run. Package must export specified entrypoint. Example: "/path/to/package.so""#,
46 value_name = "FILE"
47 )]
48 path: PathBuf,
49
50 #[arg(
51 short,
52 long,
53 help = r#"An entrypoint you want to execute. Package must export function with the same name you specify here. Example: "some_entrypoint""#
54 )]
55 entrypoint: String,
56
57 #[arg(
58 short,
59 long,
60 help = r#"An initializing script that tarantool-runner would use instead of built-in script. Use it whenever you need to override specific options, like use vinyl instead of memtx."#
61 )]
62 init_script: Option<PathBuf>,
63
64 #[arg(
65 last = true,
66 help = r#"An OPTIONAL input which is propagated to the executed entrypoint. It is given to your entrypoint in string format."#
67 )]
68 input: Option<String>,
69}
70
71impl RunArgs {
72 fn init_script_content(&self) -> anyhow::Result<Option<String>> {
74 self.init_script
75 .as_ref()
76 .map(|file| {
77 let mut file = File::open(file)?;
78 let mut result = String::new();
79 file.read_to_string(&mut result)?;
80 Ok(result)
81 })
82 .transpose()
83 }
84}
85
86fn process_run_command(args: RunArgs) -> anyhow::Result<()> {
87 let base_dir =
88 tempdir().with_context(|| "failed to create base dir for storing runtime data")?;
89
90 let current_dir = Path::new(".");
91 let package_location = args.path.parent().unwrap_or(current_dir);
92
93 let package_name = args
94 .path
95 .file_stem()
96 .with_context(|| "path is malformed - it doesn't points to a file")?;
97
98 let tnt_init_file = base_dir.path().join("init.lua");
99 let tnt_tmpdir = base_dir.path().join("tmp");
100
101 let maybe_init_script = args.init_script_content()?;
102 let init_content = maybe_init_script.as_deref().unwrap_or(DEFAULT_LUA_INIT);
103
104 let mut file = File::create(&tnt_init_file)?;
105 file.write_all(init_content.as_bytes())?;
106
107 create_dir(tnt_tmpdir.as_path())
108 .with_context(|| "failed to create tmpdir for tarantool runtime")?;
109
110 let status = SysCommand::new("tarantool")
111 .arg(tnt_init_file)
112 .env("TARANTOOL_RUNNER_PACKAGE_FULLPATH", &args.path)
114 .env("TARANTOOL_RUNNER_PACKAGE_NAME", package_name)
116 .env("TARANTOOL_RUNNER_PACKAGE_LOCATION", package_location)
118 .env("TARANTOOL_RUNNER_PACKAGE_ENTRYPOINT", &args.entrypoint)
120 .env("TARANTOOL_RUNNER_BASEDIR", base_dir.path())
122 .env("TARANTOOL_RUNNER_TMPDIR", tnt_tmpdir)
124 .env(
126 "TARANTOOL_RUNNER_INPUT",
127 args.input.as_deref().unwrap_or(""),
128 )
129 .status()
130 .with_context(|| "failed to get status code from tarantool process")?;
131
132 if !status.success() {
133 bail!("exit code of tarantool process is not success: {status:?}")
134 }
135
136 Ok(())
137}