From: Justin Cinkelj <justin.cink...@xlab.si>
Committer: Nadav Har'El <n...@scylladb.com>
Branch: master

command line: expand environ vars for runscript and REST commands

When command is run via CLI (run.py -e "CMD"), the CMD string can contain
$KEY, and KEY will be replaced with corresponding value from environ.
The patch implements this also for commands run via REST or via runscript.

Fixes #868

Signed-off-by: Justin Cinkelj <justin.cink...@xlab.si>
Message-Id: <20170329125439.1316-1-justin.cink...@xlab.si>

---
diff --git a/core/commands.cc b/core/commands.cc
--- a/core/commands.cc
+++ b/core/commands.cc
@@ -92,6 +92,32 @@ parse_command_line_min(const std::string line, bool &ok)
 }

 /*
+Everything after first $ is assumed to be env var.
+So with AA=aa, "$AA" -> "aa", and "X$AA" => "Xaa"
+More than one $ is not supported, and "${AA}" is also not.
+*/
+void expand_environ_vars(std::vector<std::vector<std::string>>& result)
+{
+    std::vector<std::vector<std::string>>::iterator cmd_iter;
+    std::vector<std::string>::iterator line_iter;
+    for (cmd_iter=result.begin(); cmd_iter!=result.end(); cmd_iter++) {
+ for (line_iter=cmd_iter->begin(); line_iter!=cmd_iter->end(); line_iter++) {
+            size_t pos;
+ if ((pos = line_iter->find_first_of('$')) != std::string::npos) {
+                std::string new_word = line_iter->substr(0, pos);
+                std::string key = line_iter->substr(pos+1);
+                auto tmp = getenv(key.c_str());
+ //debug(" new_word=%s, key=%s, tmp=%s\n", new_word.c_str(), key.c_str(), tmp);
+                if (tmp) {
+                    new_word += tmp;
+                }
+                *line_iter = new_word;
+            }
+        }
+    }
+}
+
+/*
 In each runscript line, first N args starting with - are options.
 Parse options and remove them from result.

@@ -193,6 +219,9 @@ runscript_expand(const std::vector<std::string>& cmd, bool &ok, bool &is_runscri
                 ok = false;
                 return result2;
             }
+            // Replace env vars found inside script.
+ // Options, script command and script command parameters can be set via env vars.
+            expand_environ_vars(result3);
             // process and remove options from command
             runscript_process_options(result3);
             result2.insert(result2.end(), result3.begin(), result3.end());
@@ -210,6 +239,8 @@ parse_command_line(const std::string line,  bool &ok)
 {
     std::vector<std::vector<std::string> > result, result2;
     result = parse_command_line_min(line, ok);
+    // First replace environ variables in input command line.
+    expand_environ_vars(result);

     /*
     If command starts with runscript, we need to read actual command to
diff --git a/loader.cc b/loader.cc
--- a/loader.cc
+++ b/loader.cc
@@ -316,20 +316,7 @@ std::vector<std::vector<std::string> > prepare_commands(int ac, char** av)

     // concatenate everything
     for (auto i = 0; i < ac; i++) {
-        std::string arg("");
-        char* env = strchr(av[i],'$');
-        if (av[i] && env) {
-            *env = '\0';
-            env++;
-            auto tmp = getenv(env);
-            arg = av[i];
-            if (tmp) {
-                arg += tmp;
-            }
-        } else {
-            arg = av[i];
-        }
-        line += arg + " ";
+        line += std::string(av[i]) + " ";
     }

     commands = osv::parse_command_line(line, ok);
diff --git a/tests/tst-commands.cc b/tests/tst-commands.cc
--- a/tests/tst-commands.cc
+++ b/tests/tst-commands.cc
@@ -672,6 +672,91 @@ static bool test_runscript_with_env()
         return false;
     }

+    unsetenv("ASDF");
+
+    return true;
+}
+
+static bool test_runscript_with_env_in_script()
+{
+    std::ofstream of1("/myscript", std::ios::out | std::ios::binary);
+ of1 << "--env=ASDF=ttrt --env=PORT=$THEPORT /$THEPROG pp1a $PRM1b pp1c $PRM1d\n";
+    of1.close();
+
+    std::vector<std::vector<std::string> > result;
+    std::vector<std::string> cmd = { "/prog1" };
+    size_t expected_size[] = {6};
+    bool ok;
+
+    // those two are set during command parsing
+    if (NULL != getenv("ASDF")) {
+        return false;
+    }
+    if (NULL != getenv("PORT")) {
+        return false;
+    }
+    // those are required during command parsing
+    if (0 != setenv("THEPORT", "4321", 1)) {
+        return false;
+    }
+    if (0 != setenv("THEPROG", "prog1", 1)) {
+        return false;
+    }
+    if (0 != setenv("PRM1b", "pp1b", 1)) {
+        return false;
+    }
+    if (0 != setenv("PRM1d", "pp1d", 1)) {
+        return false;
+    }
+
+    result = osv::parse_command_line(
+        std::string("runscript \"/myscript\";  "),
+        ok);
+
+    if (!ok) {
+        return false;
+    }
+
+    if (result.size() != 1) {
+        return false;
+    }
+
+    for (size_t i = 0; i < result.size(); i++) {
+        if (result[i].size() != expected_size[i]) {
+            return false;
+        }
+        if (result[i][0] != cmd[i]) {
+            return false;
+        }
+    }
+
+    if (result[0][1] != std::string("pp1a")) {
+        return false;
+    }
+    if (result[0][2] != std::string("pp1b")) {
+        return false;
+    }
+    if (result[0][3] != std::string("pp1c")) {
+        return false;
+    }
+    if (result[0][4] != std::string("pp1d")) {
+        return false;
+    }
+
+    if (std::string("ttrt") != getenv("ASDF")) {
+        return false;
+    }
+    if (std::string("4321") != getenv("PORT")) {
+        return false;
+    }
+
+    unsetenv("ASDF");
+    unsetenv("PORT");
+    unsetenv("THEPORT");
+    unsetenv("THEPROG");
+    unsetenv("PRM1b");
+    unsetenv("PRM1d ");
+
     return true;
 }

@@ -703,6 +788,8 @@ int main(int argc, char *argv[])
"runscript multiple lines, multiple commands per line, with args and quotes");
     report(test_runscript_with_env(),
            "runscript with --env");
+    report(test_runscript_with_env_in_script(),
+           "runscript with --env in script");
     printf("SUMMARY: %d tests, %d failures\n", tests, fails);
     return 0;
 }

--
You received this message because you are subscribed to the Google Groups "OSv 
Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to osv-dev+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to