You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hugegraph.apache.org by ji...@apache.org on 2026/10/12 02:16:57 UTC

(hugegraph-toolchain) branch master updated: fix(loader): accept response file arguments (#808)

This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph-toolchain.git


The following commit(s) were added to refs/heads/master by this push:
     new 2692b6bf9 fix(loader): accept response file arguments (#808)
2692b6bf9 is described below

commit 2692b6bf9dbf41111a6e5bc04241f1d5236a245e
Author: imbajin <ji...@apache.org>
AuthorDate: Mon Oct 12 10:16:52 2026 +0800

    fix(loader): accept response file arguments (#808)
    
    - rely on required options and validators after expansion
    - cover response files, defaults, and invalid parser input
    - document private argument files and writable mappings
---
 hugegraph-loader/README.md                         | 21 +++++++
 .../hugegraph/loader/executor/LoadOptions.java     |  4 --
 .../loader/test/unit/LoadOptionsTest.java          | 68 +++++++++++++++++++++-
 3 files changed, 86 insertions(+), 7 deletions(-)

diff --git a/hugegraph-loader/README.md b/hugegraph-loader/README.md
index 9b6342b06..e3254db3e 100644
--- a/hugegraph-loader/README.md
+++ b/hugegraph-loader/README.md
@@ -109,6 +109,27 @@ sh bin/hugegraph-loader.sh -g hugegraph -f example/file/struct.json -s example/f
 
 More details is in the [doc](https://hugegraph.apache.org/docs/quickstart/hugegraph-loader/)
 
+#### Arguments from a file
+
+The launcher accepts JCommander's `@file` syntax. Put each option and its value on separate lines:
+
+```text
+--graph
+hugegraph
+--file
+example/file/struct.json
+--schema
+example/file/schema.groovy
+```
+
+```bash
+bash bin/hugegraph-loader.sh @loader.args
+```
+
+Paths in the argument file resolve from the working directory. Protect files containing credentials with mode `0600`;
+the credentials can then stay out of the process arguments. The mapping directory must be writable for progress output,
+so copy a mapping mounted from a read-only Kubernetes ConfigMap to a writable volume before running Loader.
+
 ### 3.2 Load data with docker
 
 > If the `loader` and `server` is in the same docker network (for example, you deploy `loader` and `server` with `docker-compose`), we can set `-h {server_container_name}`. In our example, the container name of `server` is `graph`
diff --git a/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/executor/LoadOptions.java b/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/executor/LoadOptions.java
index eb0ed5efb..7c0042f9d 100644
--- a/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/executor/LoadOptions.java
+++ b/hugegraph-loader/src/main/java/org/apache/hugegraph/loader/executor/LoadOptions.java
@@ -48,7 +48,6 @@ public final class LoadOptions implements Cloneable {
     private static final int CPUS = Runtime.getRuntime().availableProcessors();
     private static final int DEFAULT_MAX_CONNECTIONS = CPUS * 4;
     private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = CPUS * 2;
-    private static final int MINIMUM_REQUIRED_ARGS = 3;
     private static final Set<String> SENSITIVE_PARAMETER_FIELDS =
             ImmutableSet.of("password", "trustStoreToken", "token", "pdToken");
 
@@ -388,9 +387,6 @@ public final class LoadOptions implements Cloneable {
         if (options.help) {
             LoadUtil.exitWithUsage(commander, Constants.EXIT_CODE_NORM);
         }
-        if (args.length < MINIMUM_REQUIRED_ARGS) {
-            LoadUtil.exitWithUsage(commander, Constants.EXIT_CODE_ERROR);
-        }
         // Check options
         // Check option "-f"
         E.checkArgument(!StringUtils.isEmpty(options.file),
diff --git a/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/LoadOptionsTest.java b/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/LoadOptionsTest.java
index 82bc8d13a..aa16276e8 100644
--- a/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/LoadOptionsTest.java
+++ b/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/LoadOptionsTest.java
@@ -48,7 +48,7 @@ public class LoadOptionsTest {
                              "--port", "not-an-integer");
             assertParserExit(false, "--unknown-option");
             assertParserExit(false);
-            assertParserExit(false, "--file", mapping.getPath());
+            assertParserExit(false, "--file");
             assertParserExit(true, "--help");
             assertParserExit(true, "-help");
         } finally {
@@ -57,6 +57,64 @@ public class LoadOptionsTest {
     }
 
     private static void assertParserExit(boolean help, String... args) throws Exception {
+        assertParserExit(help, true, args);
+    }
+
+    @Test
+    public void testRequiredMappingOptionUsesDefaults() throws Exception {
+        File mapping = createTempMapping();
+        try {
+            assertParserExit(true, false, "--file", mapping.getPath());
+            LoadOptions options = LoadOptions.parseOptions(new String[]{"--file", mapping.getPath()});
+            Assert.assertEquals(mapping.getPath(), options.file);
+            Assert.assertEquals("hugegraph", options.graph);
+            Assert.assertEquals("http://localhost", options.host);
+        } finally {
+            mapping.delete();
+        }
+    }
+
+    @Test
+    public void testResponseFileOptions() throws Exception {
+        File mapping = createTempMapping();
+        File arguments = File.createTempFile("loader arguments-", ".args", new File("."));
+        try {
+            Files.write(arguments.toPath(), Arrays.asList("--file", mapping.getPath(),
+                                                         "--graph", "response-file-graph",
+                                                         "--parser-threads", "2"),
+                        StandardCharsets.UTF_8);
+            String[] args = new String[]{"@" + arguments.getPath()};
+            assertParserExit(true, false, args);
+            LoadOptions options = LoadOptions.parseOptions(args);
+            Assert.assertEquals(mapping.getPath(), options.file);
+            Assert.assertEquals("response-file-graph", options.graph);
+            Assert.assertEquals(2, options.parseThreads);
+        } finally {
+            arguments.delete();
+            mapping.delete();
+        }
+    }
+
+    @Test
+    public void testInvalidResponseFileOptionsExitNonzero() throws Exception {
+        File mapping = createTempMapping();
+        File arguments = File.createTempFile("loader-invalid-", ".args", new File("."));
+        try {
+            Files.write(arguments.toPath(), Arrays.asList("--graph", "missing-mapping"),
+                        StandardCharsets.UTF_8);
+            assertParserExit(false, "@" + arguments.getPath());
+            Files.write(arguments.toPath(), Arrays.asList("--file", mapping.getPath(),
+                                                         "--port", "not-an-integer"),
+                        StandardCharsets.UTF_8);
+            assertParserExit(false, "@" + arguments.getPath());
+        } finally {
+            arguments.delete();
+            mapping.delete();
+        }
+    }
+
+    private static void assertParserExit(boolean success, boolean expectUsage,
+                                         String... args) throws Exception {
         List<String> command = new ArrayList<>();
         command.add(new File(System.getProperty("java.home"), "bin/java").getPath());
         command.add("-Xmx128m");
@@ -71,8 +129,12 @@ public class LoadOptionsTest {
         try {
             Assert.assertTrue("Parser process timed out", process.waitFor(20, TimeUnit.SECONDS));
             String usage = new String(Files.readAllBytes(output.toPath()), StandardCharsets.UTF_8);
-            Assert.assertTrue(usage, usage.contains("Usage:") && usage.contains("--file"));
-            if (help) {
+            if (expectUsage) {
+                Assert.assertTrue(usage, usage.contains("Usage:") && usage.contains("--file"));
+            } else {
+                Assert.assertFalse(usage, usage.contains("Usage:"));
+            }
+            if (success) {
                 Assert.assertEquals(0, process.exitValue());
             } else {
                 Assert.assertTrue(usage, process.exitValue() != 0);