After upgrading to VCF Operations Orchestrator 9.1.1, JavaScript actions that run OS-level commands through Java classes stop working. If you have old code that calls java.lang.Runtime.exec() to run a shell command, it will now fail. In this post I’ll show what the error looks like, why it happens, and how to run the same commands, including curl API calls, from a Python action instead. Everything below was tested on my lab running Orchestrator 9.1.1.0 build 25643527.
Issue summary
Any JavaScript action or scriptable task that runs an OS command like this is affected:
var runtime = java.lang.Runtime.getRuntime();
var process = runtime.exec("uname -n");
On 9.1.1 this fails straight away. On my lab the error was:
TypeError: getRuntime is not a function, it is object.
Depending on your build, you may see this message instead:
TypeError: Cannot find function exec in object java.lang.Runtime@xxxx
Both mean the same thing: your script is no longer allowed to start OS processes.
Root cause
Running OS commands from JavaScript has been disabled in VCF Operations Orchestrator 9.1.1 for security reasons. The JavaScript engine no longer gives scripts access to java.lang.Runtime, and the same applies to java.lang.ProcessBuilder, the other common way of starting a process from Java.
The classes aren’t simply missing their methods. They can’t be reached at all, so the engine treats the name as an empty Java package placeholder. That’s why the error says getRuntime “is object”: the call fails before it ever gets to exec. You can see this for yourself with a quick check:
System.log("java.lang.Runtime = " + java.lang.Runtime);
System.log("typeof getRuntime = " + typeof java.lang.Runtime.getRuntime);
On 9.1.1 this logs:
java.lang.Runtime = [JavaPackage java.lang.Runtime]
typeof getRuntime = object
So there’s no workaround inside JavaScript. The command has to run somewhere else.
Solution: run the command from a Python action
Orchestrator’s polyglot runtimes aren’t affected. A Python action can start processes with the standard subprocess module, so the fix is to move the command into a Python action and call that action from your workflow.
To create it:
- Create a new action and set its Runtime to Python.
- Add one input named
commandof typestring. - Set the return type to
string. - Paste the script below and save.
import subprocess
import shlex
def handler(context, inputs):
command = inputs["command"]
print("Running: " + command)
try:
result = subprocess.run(
shlex.split(command),
capture_output=True,
text=True,
timeout=60
)
except FileNotFoundError:
raise Exception("Command not found in the Python runtime: " + shlex.split(command)[0])
print("Exit code: " + str(result.returncode))
if result.stdout:
print("STDOUT:\n" + result.stdout)
if result.stderr:
print("STDERR:\n" + result.stderr)
if result.returncode != 0:
raise Exception("Command failed with exit code " + str(result.returncode) + ": " + result.stderr.strip())
return result.stdout.strip()
A few details in this script matter:
shlex.splitsplits the command the way a shell would, so quoted arguments stay together. A plainsplit(" ")breaks them apart.text=Truereturns the output as normal text. Without it you get bytes, andstr()of those looks likeb'...\n'.- Exit codes are checked. If the command fails, the action fails with the command’s own error message, instead of quietly returning nothing.
- A missing command gets a clear error. Without the
FileNotFoundErrorhandler you get a long Python traceback. timeout=60stops a hung command from blocking the workflow forever. Raise it if you run long jobs.
Testing it
Run the action with command set to uname -n:
INFO Running: uname -n
INFO Exit code: 0
INFO STDOUT:
INFO vco-app-0
Note that I used uname -n rather than hostname. The hostname command isn’t installed in the Python runtime, and trying it gives No such file or directory: 'hostname'.
Now try a command that fails, such as uname -h (not a valid option). The command still runs, and the script turns its error into a clear failure:
INFO Running: uname -h
INFO Exit code: 1
INFO STDERR:
INFO uname: invalid option -- 'h'
INFO Try 'uname --help' for more information.
ERROR Exception: Command failed with exit code 1: uname: invalid option -- 'h'
Where does the command actually run?
The uname -n output, vco-app-0, is the name of Orchestrator’s own Kubernetes pod on the appliance. Your commands run inside that pod’s environment, not on the appliance’s host OS. Keep that in mind: a command like df or ip addr shows the pod’s view, not the virtual machine’s.
The runtime is a Photon OS image with a good set of tools. On my 9.1.1 lab it had over 400 commands, including:
- Network and API:
curl,wget,jq,openssl,ping,ip,ss,getent - Text and files:
grep,sed,awk,cut,sort,tar,gzip,base64,sha256sum - Other runtimes:
bash,pwsh,node,python3
The exact list can differ between builds. To see what’s available on yours, run this as a Python action with no inputs:
import os
def handler(context, inputs):
found = []
for d in ["/bin", "/usr/bin", "/usr/local/bin", "/sbin", "/usr/sbin"]:
if os.path.isdir(d):
names = sorted(os.listdir(d))
print(d + " (" + str(len(names)) + "): " + ", ".join(names))
found.append(d + ": " + str(len(names)) + " commands")
return "\n".join(found)
Running curl with the same approach
A common reason for running OS commands from Orchestrator is calling an API with curl. The same Python action handles this with no code changes. A simple call works directly:
curl -sk https://auto.vmw.lab/vco/api/about
The action doesn’t use a shell, though, so pipes (|), && and redirects won’t work on their own. To use them, wrap the whole command in bash -c "...". Because the script uses shlex.split, everything inside the quotes reaches bash as one argument.
Here’s a real example from my lab. It calls the Orchestrator API with curl and parses the JSON with jq, all in one command:
bash -c "curl -sk https://auto.vmw.lab/vco/api/about | jq -r '\"Version: \" + .version + \" | Build: \" + .\"build-number\"'"
Output:
INFO Running: bash -c "curl -sk https://auto.vmw.lab/vco/api/about | jq -r ..."
INFO Exit code: 0
INFO STDOUT:
INFO Version: 9.1.1.0.25643527 | Build: 25643527
A couple more you can try with the same action:
# Check when an endpoint's TLS certificate expires
bash -c "echo | openssl s_client -connect auto.vmw.lab:443 -servername auto.vmw.lab 2>/dev/null | openssl x509 -noout -subject -enddate"
# Check that several hosts resolve from Orchestrator
bash -c "for h in auto.vmw.lab vc.vmw.lab; do getent hosts $h >/dev/null && echo $h resolves || echo $h NOT resolving; done"
Two cautions when you take this beyond a lab:
-kskips certificate checks. That’s fine for a lab with self-signed certificates, but in production point curl at your CA bundle with--cacertinstead.- Don’t put passwords or tokens in the command string. The action logs the command it runs. Pass secrets as separate SecureString inputs and build the request in Python, or better still, use Python’s own HTTP libraries for authenticated calls.
Summary
- In Orchestrator 9.1.1, JavaScript can no longer reach
java.lang.Runtimeorjava.lang.ProcessBuilder, soRuntime.execfails withgetRuntime is not a functionorCannot find function exec. - Move OS commands into a Python action that uses
subprocess. - Commands run inside the
vco-apppod, which has tools such ascurl,jqandopenssl. - For pipes and other shell features, wrap the command in
bash -c "...".
Thanks to Stanislav Todorov for suggesting the Python approach.
Discover more from Cloud Blogger
Subscribe to get the latest posts sent to your email.










