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 + + + + + + +