tarantool_runner/
lib.rs

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/// An application that `tarantool-runner` runs(if used as a binary - see `README`) and exports for reuse.
16#[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    /// Processes parsed command.
26    ///
27    /// Could fail only in case the underlying command fails.
28    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    /// Returns content of the init script file, supplied by user.
73    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        // Fullpath points literally to the given package.
113        .env("TARANTOOL_RUNNER_PACKAGE_FULLPATH", &args.path)
114        // Package name without extension - supplied so it can be used with require.
115        .env("TARANTOOL_RUNNER_PACKAGE_NAME", package_name)
116        // Package location is the dir where package could be found.
117        .env("TARANTOOL_RUNNER_PACKAGE_LOCATION", package_location)
118        // Package entrypoint is the function user would like to execute.
119        .env("TARANTOOL_RUNNER_PACKAGE_ENTRYPOINT", &args.entrypoint)
120        // Base dir is the runtime directory of `tarantool-runner`. There goes initialization scripts, package symlink, etc.
121        .env("TARANTOOL_RUNNER_BASEDIR", base_dir.path())
122        // Tmpdir is the directory tarantool instance must use to store its temporary files.
123        .env("TARANTOOL_RUNNER_TMPDIR", tnt_tmpdir)
124        // An input to the entrypoint.
125        .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}