This is an automated email from the ASF dual-hosted git repository. pinal pushed a commit to branch ATLAS-5218 in repository https://gitbox.apache.org/repos/asf/atlas.git
commit 4c3d09a3cd4e28a35be16f0a122cc09d455df352 Author: Pinal Shah <[email protected]> AuthorDate: Tue Feb 17 20:04:29 2026 +0530 ATLAS-5218: Add command-line utility to run Gremlin queries --- distro/pom.xml | 1 + distro/src/main/assemblies/atlas-gremlin-cli.xml | 60 ++++++ pom.xml | 1 + tools/atlas-gremlin-cli/README | 37 ++++ tools/atlas-gremlin-cli/pom.xml | 55 ++++++ .../atlas-gremlin-cli/scripts/atlas-gremlin-cli.sh | 93 ++++++++++ .../java/org/apache/atlas/tools/GremlinCli.java | 202 +++++++++++++++++++++ .../src/main/resources/atlas-logback.xml | 47 +++++ 8 files changed, 496 insertions(+) diff --git a/distro/pom.xml b/distro/pom.xml index d7e958889..a953562ee 100644 --- a/distro/pom.xml +++ b/distro/pom.xml @@ -277,6 +277,7 @@ atlas.graph.storage.hbase.regions-per-server=1 <descriptor>src/main/assemblies/classification-updater.xml</descriptor> <descriptor>src/main/assemblies/notification-analyzer.xml</descriptor> <descriptor>src/main/assemblies/atlas-trino-extractor.xml</descriptor> + <descriptor>src/main/assemblies/atlas-gremlin-cli.xml</descriptor> </descriptors> <finalName>apache-atlas-${project.version}</finalName> <tarLongFileMode>gnu</tarLongFileMode> 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 000000000..bcd88c913 --- /dev/null +++ b/distro/src/main/assemblies/atlas-gremlin-cli.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> + <formats> + <format>tar.gz</format> + </formats> + <id>atlas-gremlin-cli</id> + <baseDirectory>atlas-gremlin-cli</baseDirectory> + <fileSets> + <fileSet> + <directory>../tools/atlas-gremlin-cli</directory> + <outputDirectory>.</outputDirectory> + <includes> + <include>README*</include> + </includes> + </fileSet> + <fileSet> + <directory>../tools/atlas-gremlin-cli/scripts</directory> + <outputDirectory>.</outputDirectory> + <includes> + <include>*.sh</include> + </includes> + <fileMode>0755</fileMode> + <directoryMode>0755</directoryMode> + </fileSet> + <fileSet> + <directory>../tools/atlas-gremlin-cli/src/main/resources</directory> + <outputDirectory>.</outputDirectory> + <includes> + <include>atlas-logback.xml</include> + </includes> + </fileSet> + <fileSet> + <directory>../tools/atlas-gremlin-cli/target</directory> + <outputDirectory>/lib</outputDirectory> + <includes> + <include>atlas-gremlin-cli-tool-*.jar</include> + </includes> + </fileSet> + </fileSets> + +</assembly> diff --git a/pom.xml b/pom.xml index 079fc3125..f832a09a1 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,7 @@ <module>repository</module> <module>server-api</module> <module>test-tools</module> + <module>tools/atlas-gremlin-cli</module> <module>tools/atlas-index-repair</module> <module>tools/classification-updater</module> <module>tools/notification-analyzer</module> diff --git a/tools/atlas-gremlin-cli/README b/tools/atlas-gremlin-cli/README new file mode 100644 index 000000000..974df2d0c --- /dev/null +++ b/tools/atlas-gremlin-cli/README @@ -0,0 +1,37 @@ +# +# 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 + +Build + mvn -pl tools/atlas-gremlin-cli -am package + +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()" + +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. + + diff --git a/tools/atlas-gremlin-cli/pom.xml b/tools/atlas-gremlin-cli/pom.xml new file mode 100644 index 000000000..2f92f9b1e --- /dev/null +++ b/tools/atlas-gremlin-cli/pom.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.atlas</groupId> + <artifactId>apache-atlas</artifactId> + <version>3.0.0-SNAPSHOT</version> + <relativePath>../../</relativePath> + </parent> + + <artifactId>atlas-gremlin-cli-tool</artifactId> + <packaging>jar</packaging> + <name>Apache Atlas Gremlin CLI Tool</name> + <description>Apache Atlas Gremlin CLI Tool (embedded JanusGraph)</description> + + <dependencies> + + <dependency> + <groupId>commons-cli</groupId> + <artifactId>commons-cli</artifactId> + <version>${commons-cli.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.atlas</groupId> + <artifactId>atlas-graphdb-janus</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + </dependencies> +</project> 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 000000000..e13d69227 --- /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 000000000..bd22e7670 --- /dev/null +++ b/tools/atlas-gremlin-cli/src/main/java/org/apache/atlas/tools/GremlinCli.java @@ -0,0 +1,202 @@ +/** + * 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; + +/** + * Small embedded Gremlin CLI for Atlas' JanusGraph backend. + * + * Requires Atlas configuration directory to be provided via system property: + * -Datlas.conf=/path/to/atlas/conf + * + * Examples: + * -q "g.V().limit(5).valueMap(true).toList()" + * -f /tmp/query.groovy + */ +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"); + + // Ensure AtlasJanusGraphDatabase constructor runs (it registers GraphSON/Janus registries) + // even though we primarily use getGraphInstance(). + new AtlasJanusGraphDatabase(); + + JanusGraph graph = AtlasJanusGraphDatabase.getGraphInstance(); + + try { + GraphTraversalSource g = graph.traversal(); + // DefaultImportCustomizer isn't available in some Gremlin distributions used by this project. + // Use the no-arg constructor and bind helpful symbols directly. + 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 user returns a Traversal (e.g. g.V()), materialize it for convenience. + 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) { + // Default behavior is rollback to protect against accidental writes. + 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) { + } + } + + // Build Options in a single place so parsing and help use the same definitions. + 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 000000000..f8637fb27 --- /dev/null +++ b/tools/atlas-gremlin-cli/src/main/resources/atlas-logback.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + ~ 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. + --> + +<configuration> + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <param name="Target" value="System.out"/> + <encoder> + <pattern>%date [%thread] %level{5} [%file:%line] %msg%n</pattern> + </encoder> + <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> + <level>INFO</level> + </filter> + </appender> + + <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${atlas.log.dir}/${atlas.log.file}</file> + <append>true</append> + <encoder> + <pattern>%date [%thread] %level{5} [%file:%line] %msg%n</pattern> + </encoder> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <fileNamePattern>${atlas.log.dir}/${atlas.log.file}-%d</fileNamePattern> + <maxHistory>20</maxHistory> + <cleanHistoryOnStart>true</cleanHistoryOnStart> + </rollingPolicy> + </appender> + + <root level="info"> + <appender-ref ref="FILE"/> + </root> +</configuration>
