diff --git a/distro/pom.xml b/distro/pom.xml
index d7e95888949..a953562ee7b 100644
--- a/distro/pom.xml
+++ b/distro/pom.xml
@@ -277,6 +277,7 @@ atlas.graph.storage.hbase.regions-per-server=1
src/main/assemblies/classification-updater.xml
src/main/assemblies/notification-analyzer.xml
src/main/assemblies/atlas-trino-extractor.xml
+ src/main/assemblies/atlas-gremlin-cli.xml
apache-atlas-${project.version}
gnu
diff --git a/distro/src/main/assemblies/atlas-gremlin-cli.xml b/distro/src/main/assemblies/atlas-gremlin-cli.xml
new file mode 100644
index 00000000000..bcd88c913aa
--- /dev/null
+++ b/distro/src/main/assemblies/atlas-gremlin-cli.xml
@@ -0,0 +1,60 @@
+
+
+
+
+ tar.gz
+
+ atlas-gremlin-cli
+ atlas-gremlin-cli
+
+
+ ../tools/atlas-gremlin-cli
+ .
+
+ README*
+
+
+
+ ../tools/atlas-gremlin-cli/scripts
+ .
+
+ *.sh
+
+ 0755
+ 0755
+
+
+ ../tools/atlas-gremlin-cli/src/main/resources
+ .
+
+ atlas-logback.xml
+
+
+
+ ../tools/atlas-gremlin-cli/target
+ /lib
+
+ atlas-gremlin-cli-tool-*.jar
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index 079fc312506..f832a09a152 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,7 @@
repository
server-api
test-tools
+ tools/atlas-gremlin-cli
tools/atlas-index-repair
tools/classification-updater
tools/notification-analyzer
diff --git a/tools/atlas-gremlin-cli/README b/tools/atlas-gremlin-cli/README
new file mode 100644
index 00000000000..32daa9ecb7a
--- /dev/null
+++ b/tools/atlas-gremlin-cli/README
@@ -0,0 +1,40 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+Introduction
+ atlas-gremlin-cli is a small command-line utility to run Gremlin queries against Atlas' embedded JanusGraph backend.
+
+ It uses Atlas' normal configuration loading, so it requires:
+ -Datlas.conf=/path/to/atlas/conf
+
+Setup
+ - Extract the tarball from distro/target/apache-atlas--atlas-gremlin-cli.tar.gz
+ - Set the ATLAS_CONF environment variable to the path to the Atlas configuration directory.
+ - Set the ATLAS_CLASSPATH environment variable to the path to the dependency jars.
+ - Run the atlas-gremlin-cli.sh script.
+
+Run
+ export ATLAS_CONF=/etc/atlas/conf
+ export ATLAS_CLASSPATH="/path/to/dependency/jars/*"
+ ./atlas-gremlin-cli.sh -q "g.V().limit(5).valueMap(true).toList()"
+ or
+ ./atlas-gremlin-cli.sh -f /path/to/query.groovy
+
+Notes
+ - Default behavior is to rollback the transaction (safe for read-only). Use --commit to persist mutations.
+ - For best output, end queries with .toList() or .next() or similar materialization.
\ No newline at end of file
diff --git a/tools/atlas-gremlin-cli/pom.xml b/tools/atlas-gremlin-cli/pom.xml
new file mode 100644
index 00000000000..2f92f9b1e46
--- /dev/null
+++ b/tools/atlas-gremlin-cli/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ 4.0.0
+
+ org.apache.atlas
+ apache-atlas
+ 3.0.0-SNAPSHOT
+ ../../
+
+
+ atlas-gremlin-cli-tool
+ jar
+ Apache Atlas Gremlin CLI Tool
+ Apache Atlas Gremlin CLI Tool (embedded JanusGraph)
+
+
+
+
+ commons-cli
+ commons-cli
+ ${commons-cli.version}
+ provided
+
+
+
+ org.apache.atlas
+ atlas-graphdb-janus
+ ${project.version}
+ provided
+
+
+
+ org.slf4j
+ slf4j-api
+ provided
+
+
+
diff --git a/tools/atlas-gremlin-cli/scripts/atlas-gremlin-cli.sh b/tools/atlas-gremlin-cli/scripts/atlas-gremlin-cli.sh
new file mode 100755
index 00000000000..e13d69227b9
--- /dev/null
+++ b/tools/atlas-gremlin-cli/scripts/atlas-gremlin-cli.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -euo pipefail
+
+show_usage() {
+ cat <<'USAGE'
+Usage: atlas-gremlin-cli.sh [options] [-- gremlin-cli-args]
+
+Environment variables expected (set by user or system):
+ ATLAS_CONF Path to Atlas config directory (required unless -h)
+ ATLAS_CLASSPATH Optional classpath from an Atlas server (appended before tool jars)
+
+Examples:
+ ATLAS_CONF=/etc/atlas/conf ./atlas-gremlin-cli.sh -q "g.V().limit(5)"
+ ./atlas-gremlin-cli.sh --help
+USAGE
+}
+
+# quick -h/--help handling
+for arg in "$@"; do
+ if [[ "$arg" == "-h" || "$arg" == "--help" ]]; then
+ show_usage
+ exit 0
+ fi
+done
+
+# Find java binary
+if [ -n "${JAVA_HOME:-}" ]; then
+ JAVA_BIN="${JAVA_HOME}/bin/java"
+else
+ JAVA_BIN="$(command -v java || true)"
+fi
+
+if [ -z "${JAVA_BIN}" ] || [ ! -x "${JAVA_BIN}" ]; then
+ echo "java not found. Please set JAVA_HOME or ensure java is on PATH." >&2
+ exit 1
+fi
+
+# Require ATLAS_CONF by default (keeps behaviour same as original unless user requests help)
+if [ -z "${ATLAS_CONF:-}" ]; then
+ echo "ATLAS_CONF is not set. Example: export ATLAS_CONF=/etc/atlas/conf" >&2
+ echo "This script will set: -Datlas.conf=\$ATLAS_CONF" >&2
+ exit 2
+fi
+
+LOGFILE_DIR="${LOGFILE_DIR:-/tmp/}"
+LOGFILE_NAME="${LOGFILE_NAME:-atlas-gremlin-cli.log}"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# In the distro tarball, the CLI jar is under ./lib next to this script.
+if [[ ! -d "${SCRIPT_DIR}/lib" ]]; then
+ echo "Could not locate ./lib next to this script. Are you running from the atlas-gremlin-cli tarball?" >&2
+ exit 3
+fi
+TOOL_CP="${SCRIPT_DIR}/lib/*"
+
+if [[ -n "${ATLAS_CLASSPATH:-}" ]]; then
+ CLASSPATH="${ATLAS_CLASSPATH}:${TOOL_CP}"
+else
+ CLASSPATH="${TOOL_CP}"
+fi
+
+LOGBACK_CFG="${SCRIPT_DIR}/atlas-logback.xml"
+if [[ ! -f "${LOGBACK_CFG}" ]]; then
+ echo "Could not locate ${LOGBACK_CFG}. Are you running from the atlas-gremlin-cli tarball?" >&2
+ exit 4
+fi
+exec "${JAVA_BIN}" \
+ -cp "${CLASSPATH}" \
+ -Dlogback.configurationFile="${LOGBACK_CFG}" \
+ -Datlas.log.dir="${LOGFILE_DIR}" \
+ -Datlas.log.file="${LOGFILE_NAME}" \
+ -Datlas.conf="${ATLAS_CONF}" \
+ org.apache.atlas.tools.GremlinCli "$@"
+
diff --git a/tools/atlas-gremlin-cli/src/main/java/org/apache/atlas/tools/GremlinCli.java b/tools/atlas-gremlin-cli/src/main/java/org/apache/atlas/tools/GremlinCli.java
new file mode 100644
index 00000000000..98bee5cbec3
--- /dev/null
+++ b/tools/atlas-gremlin-cli/src/main/java/org/apache/atlas/tools/GremlinCli.java
@@ -0,0 +1,185 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.atlas.tools;
+
+import org.apache.atlas.repository.graphdb.janus.AtlasJanusGraphDatabase;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine;
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.janusgraph.core.JanusGraph;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.script.Bindings;
+import javax.script.ScriptException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class GremlinCli {
+ private static final Logger LOG = LoggerFactory.getLogger(GremlinCli.class);
+
+ public static void main(String[] args) throws Exception {
+ CommandLine cmd = parseArgs(args);
+
+ if (cmd.hasOption("h")) {
+ printHelp();
+ return;
+ }
+
+ final String query = getQuery(cmd);
+ final boolean commit = cmd.hasOption("commit");
+
+ new AtlasJanusGraphDatabase();
+
+ JanusGraph graph = AtlasJanusGraphDatabase.getGraphInstance();
+
+ try {
+ GraphTraversalSource g = graph.traversal();
+ GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
+
+ Bindings bindings = engine.createBindings();
+ bindings.put("graph", graph);
+ bindings.put("g", g);
+ bindings.put("__", __.class);
+ bindings.put("P", P.class);
+
+ Object result = eval(engine, bindings, query);
+
+ if (result instanceof Traversal) {
+ result = ((Traversal, ?>) result).toList();
+ }
+
+ System.out.println(String.valueOf(result));
+
+ finishTx(graph, commit);
+ } catch (ScriptException se) {
+ safeRollback(graph);
+ throw se;
+ } catch (Throwable t) {
+ safeRollback(graph);
+ throw t;
+ } finally {
+ try {
+ graph.close();
+ } catch (Exception e) {
+ LOG.warn("Failed to close graph", e);
+ }
+ }
+ }
+
+ private static Object eval(GremlinGroovyScriptEngine engine, Bindings bindings, String query) throws ScriptException {
+ LOG.info("Executing Gremlin ({} chars)", query != null ? query.length() : 0);
+ return engine.eval(query, bindings);
+ }
+
+ private static void finishTx(JanusGraph graph, boolean commit) {
+ if (!graph.tx().isOpen()) {
+ return;
+ }
+
+ if (commit) {
+ graph.tx().commit();
+ } else {
+ graph.tx().rollback();
+ }
+ }
+
+ private static void safeRollback(JanusGraph graph) {
+ try {
+ if (graph != null && graph.tx().isOpen()) {
+ graph.tx().rollback();
+ }
+ } catch (Exception ignored) {
+ }
+ }
+
+ private static Options buildOptions() {
+ Options options = new Options();
+
+ options.addOption(Option.builder("q")
+ .longOpt("query")
+ .hasArg()
+ .argName("gremlin")
+ .desc("Gremlin-Groovy to evaluate. Example: g.V().limit(5).valueMap(true).toList()")
+ .build());
+
+ options.addOption(Option.builder("f")
+ .longOpt("file")
+ .hasArg()
+ .argName("path")
+ .desc("Read Gremlin-Groovy script from file")
+ .build());
+
+ options.addOption(Option.builder()
+ .longOpt("commit")
+ .desc("Commit the transaction after evaluation (default: rollback)")
+ .build());
+
+ options.addOption(Option.builder("h")
+ .longOpt("help")
+ .desc("Print help")
+ .build());
+
+ return options;
+ }
+
+ private static CommandLine parseArgs(String[] args) throws ParseException {
+ return new DefaultParser().parse(buildOptions(), args);
+ }
+
+ private static void printHelp() {
+ HelpFormatter formatter = new HelpFormatter();
+
+ String header = "\nEmbedded Gremlin CLI for Apache Atlas (JanusGraph).\n\n" +
+ "Requires: -Datlas.conf=/path/to/atlas/conf\n";
+ String footer = "\nExamples:\n" +
+ " -q \"g.V().limit(5).valueMap(true).toList()\"\n" +
+ " -f /tmp/query.groovy\n";
+
+ formatter.printHelp("GremlinCli", header, buildOptions(), footer, true);
+ }
+
+ private static String getQuery(CommandLine cmd) throws IOException {
+ String q = cmd.getOptionValue("q");
+ String f = cmd.getOptionValue("f");
+
+ if ((q == null || q.trim().isEmpty()) && (f == null || f.trim().isEmpty())) {
+ throw new IllegalArgumentException("Missing query. Provide -q/--query or -f/--file. Use -h for help.");
+ }
+
+ if (q != null && f != null) {
+ throw new IllegalArgumentException("Provide only one of -q/--query or -f/--file.");
+ }
+
+ if (q != null) {
+ return q;
+ }
+
+ return new String(Files.readAllBytes(Paths.get(f)), StandardCharsets.UTF_8);
+ }
+}
diff --git a/tools/atlas-gremlin-cli/src/main/resources/atlas-logback.xml b/tools/atlas-gremlin-cli/src/main/resources/atlas-logback.xml
new file mode 100644
index 00000000000..f8637fb2746
--- /dev/null
+++ b/tools/atlas-gremlin-cli/src/main/resources/atlas-logback.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+ %date [%thread] %level{5} [%file:%line] %msg%n
+
+
+ INFO
+
+
+
+
+ ${atlas.log.dir}/${atlas.log.file}
+ true
+
+ %date [%thread] %level{5} [%file:%line] %msg%n
+
+
+ ${atlas.log.dir}/${atlas.log.file}-%d
+ 20
+ true
+
+
+
+
+
+
+