This is an automated email from the ASF dual-hosted git repository.
ddanielr pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/2.1 by this push:
new f4cb86cd67 Added modifyPropertiesFromFile Method in ConfigCommand
(#4096)
f4cb86cd67 is described below
commit f4cb86cd67d7ae728fde63f13a47fa45c0f7c425
Author: rsingh433 <[email protected]>
AuthorDate: Fri Aug 15 09:58:29 2025 -0400
Added modifyPropertiesFromFile Method in ConfigCommand (#4096)
Added modifyPropertiesFromFile Method in ConfigCommand
Updates the createTable command to accept a property file for init
properties.
---------
Co-authored-by: Daniel Roberts ddanielr <[email protected]>
---
shell/pom.xml | 4 ++
.../java/org/apache/accumulo/shell/ShellUtil.java | 39 ++++++++++++++
.../accumulo/shell/commands/ConfigCommand.java | 30 ++++++++++-
.../shell/commands/CreateTableCommand.java | 17 +++++-
.../org/apache/accumulo/test/shell/ShellIT.java | 60 +++++++++++++++++++++-
5 files changed, 145 insertions(+), 5 deletions(-)
diff --git a/shell/pom.xml b/shell/pom.xml
index 6cd66a8297..04c2b6eacc 100644
--- a/shell/pom.xml
+++ b/shell/pom.xml
@@ -71,6 +71,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-configuration2</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
diff --git a/shell/src/main/java/org/apache/accumulo/shell/ShellUtil.java
b/shell/src/main/java/org/apache/accumulo/shell/ShellUtil.java
index dd995e0c71..5afcbe66cf 100644
--- a/shell/src/main/java/org/apache/accumulo/shell/ShellUtil.java
+++ b/shell/src/main/java/org/apache/accumulo/shell/ShellUtil.java
@@ -21,19 +21,26 @@ package org.apache.accumulo.shell;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File;
+import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.stream.Collectors;
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.util.Pair;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.hadoop.io.Text;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -74,6 +81,38 @@ public class ShellUtil {
} else {
return Collections.emptyMap();
}
+ }
+
+ public static Map<String,String> readPropertiesFromFile(String filename)
+ throws IOException, AccumuloException {
+ PropertiesConfiguration config = new PropertiesConfiguration();
+ try (FileReader out = new FileReader(filename, UTF_8)) {
+ config.read(out);
+ } catch (ConfigurationException e) {
+ Shell.log.error("Property file {} contains invalid configuration. Please
verify file format",
+ filename, e);
+ }
+ Iterator<String> keysIterator = config.getKeys();
+ Map<String,String> propertiesMap = new HashMap<>();
+ boolean foundErrors = false;
+ while (keysIterator.hasNext()) {
+ String key = keysIterator.next();
+ String value = config.getString(key);
+ if (!Property.isValidPropertyKey(key)) {
+ Shell.log.error("Property: \"{}\" is invalid", key);
+ foundErrors = true;
+ } else if (!Property.isValidProperty(key, value)) {
+ Shell.log.error("Property: \"{}\" has an invalid value: \"{}\"", key,
value);
+ foundErrors = true;
+ } else {
+ propertiesMap.put(key, value);
+ }
+ }
+ if (foundErrors) {
+ Shell.log.error("Property file {} contains invalid properties",
filename);
+ throw new AccumuloException("InvalidPropertyFile: " + filename);
+ }
+ return propertiesMap;
}
}
diff --git
a/shell/src/main/java/org/apache/accumulo/shell/commands/ConfigCommand.java
b/shell/src/main/java/org/apache/accumulo/shell/commands/ConfigCommand.java
index 30749f143e..7311db5422 100644
--- a/shell/src/main/java/org/apache/accumulo/shell/commands/ConfigCommand.java
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/ConfigCommand.java
@@ -19,6 +19,7 @@
package org.apache.accumulo.shell.commands;
import static
org.apache.accumulo.core.client.security.SecurityErrorCode.PERMISSION_DENIED;
+import static org.apache.accumulo.shell.ShellUtil.readPropertiesFromFile;
import java.io.IOException;
import java.util.ArrayList;
@@ -28,6 +29,7 @@ import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
+import java.util.function.Consumer;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
@@ -63,6 +65,7 @@ public class ConfigCommand extends Command {
private Option outputFileOpt;
private Option namespaceOpt;
private Option showExpOpt;
+ private Option propFileOpt;
private int COL1 = 10;
private int COL2 = 7;
@@ -100,6 +103,10 @@ public class ConfigCommand extends Command {
&&
!shellState.getAccumuloClient().namespaceOperations().exists(namespace)) {
throw new NamespaceNotFoundException(null, namespace, null);
}
+ String filename = cl.getOptionValue(propFileOpt.getOpt());
+ if (filename != null) {
+ modifyPropertiesFromFile(cl, shellState, filename);
+ }
if (cl.hasOption(deleteOpt.getOpt())) {
// delete property from table, namespace, or system
String property = cl.getOptionValue(deleteOpt.getOpt());
@@ -379,6 +386,25 @@ public class ConfigCommand extends Command {
return 0;
}
+ private void modifyPropertiesFromFile(CommandLine cl, Shell shellState,
String filename)
+ throws AccumuloException, AccumuloSecurityException, IOException,
NamespaceNotFoundException {
+ Map<String,String> propertiesMap = readPropertiesFromFile(filename);
+
+ Consumer<Map<String,String>> propertyModifier = currProps -> {
+ currProps.putAll(propertiesMap);
+ };
+
+ if (cl.hasOption(tableOpt.getOpt())) {
+ shellState.getAccumuloClient().tableOperations()
+ .modifyProperties(cl.getOptionValue(tableOpt.getOpt()),
propertyModifier);
+ } else if (cl.hasOption(namespaceOpt.getOpt())) {
+ shellState.getAccumuloClient().namespaceOperations()
+ .modifyProperties(cl.getOptionValue(namespaceOpt.getOpt()),
propertyModifier);
+ } else {
+
shellState.getAccumuloClient().instanceOperations().modifyProperties(propertyModifier);
+ }
+ }
+
private boolean matchTheFilterText(CommandLine cl, String key, String value)
{
if (cl.hasOption(filterOpt.getOpt()) &&
!key.contains(cl.getOptionValue(filterOpt.getOpt()))) {
return true;
@@ -437,7 +463,7 @@ public class ConfigCommand extends Command {
outputFileOpt = new Option("o", "output", true, "local file to write the
scan output to");
namespaceOpt = new Option(ShellOptions.namespaceOption, "namespace", true,
"namespace to display/set/delete properties for");
-
+ propFileOpt = new Option("pf", "propFile", true, "file containing
properties to set");
tableOpt.setArgName("table");
deleteOpt.setArgName("property");
setOpt.setArgName("property=value");
@@ -445,11 +471,13 @@ public class ConfigCommand extends Command {
filterWithValuesOpt.setArgName("string");
outputFileOpt.setArgName("file");
namespaceOpt.setArgName("namespace");
+ propFileOpt.setArgName("filename");
og.addOption(deleteOpt);
og.addOption(setOpt);
og.addOption(filterOpt);
og.addOption(filterWithValuesOpt);
+ og.addOption(propFileOpt);
tgroup.addOption(tableOpt);
tgroup.addOption(namespaceOpt);
diff --git
a/shell/src/main/java/org/apache/accumulo/shell/commands/CreateTableCommand.java
b/shell/src/main/java/org/apache/accumulo/shell/commands/CreateTableCommand.java
index 9847a8a54c..633b419973 100644
---
a/shell/src/main/java/org/apache/accumulo/shell/commands/CreateTableCommand.java
+++
b/shell/src/main/java/org/apache/accumulo/shell/commands/CreateTableCommand.java
@@ -19,6 +19,7 @@
package org.apache.accumulo.shell.commands;
import static org.apache.accumulo.core.util.Validators.NEW_TABLE_NAME;
+import static org.apache.accumulo.shell.ShellUtil.readPropertiesFromFile;
import java.io.IOException;
import java.util.Arrays;
@@ -66,6 +67,7 @@ public class CreateTableCommand extends Command {
private Option base64Opt;
private Option createTableOptFormatter;
private Option createTableOptInitProp;
+ private Option createTableOptInitPropFile;
private Option createTableOptLocalityProps;
private Option createTableOptIteratorProps;
private Option createTableOptOffline;
@@ -120,8 +122,15 @@ public class CreateTableCommand extends Command {
timeType = TimeType.LOGICAL;
}
- Map<String,String> initProperties =
- new HashMap<>(ShellUtil.parseMapOpt(cl, createTableOptInitProp));
+ Map<String,String> initProperties;
+ String filename = cl.getOptionValue(createTableOptInitPropFile.getOpt());
+ if (filename != null) {
+ initProperties = readPropertiesFromFile(filename);
+ } else {
+ initProperties = new HashMap<>();
+ }
+
+ initProperties.putAll(ShellUtil.parseMapOpt(cl, createTableOptInitProp));
// Set iterator if supplied
if (cl.hasOption(createTableOptIteratorProps.getOpt())) {
@@ -327,11 +336,14 @@ public class CreateTableCommand extends Command {
createTableOptFormatter = new Option("f", "formatter", true, "default
formatter to set");
createTableOptInitProp = new Option("prop", "init-properties", true,
"comma-separated user-defined initial key=value pairs");
+ createTableOptInitPropFile =
+ new Option("pf", "propFile", true, "user-defined initial properties
file");
createTableOptCopyConfig.setArgName("table");
createTableOptCopySplits.setArgName("table");
createTableOptSplit.setArgName("filename");
createTableOptFormatter.setArgName("className");
createTableOptInitProp.setArgName("properties");
+ createTableOptInitPropFile.setArgName("properties-file");
createTableOptLocalityProps =
new Option("l", "locality", true, "create locality groups at table
creation");
@@ -368,6 +380,7 @@ public class CreateTableCommand extends Command {
o.addOption(createTableOptEVC);
o.addOption(createTableOptFormatter);
o.addOption(createTableOptInitProp);
+ o.addOption(createTableOptInitPropFile);
o.addOption(createTableOptLocalityProps);
o.addOption(createTableOptIteratorProps);
o.addOption(createTableOptOffline);
diff --git a/test/src/main/java/org/apache/accumulo/test/shell/ShellIT.java
b/test/src/main/java/org/apache/accumulo/test/shell/ShellIT.java
index 27b659a37c..d8fc412d90 100644
--- a/test/src/main/java/org/apache/accumulo/test/shell/ShellIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/shell/ShellIT.java
@@ -18,6 +18,7 @@
*/
package org.apache.accumulo.test.shell;
+import static
org.apache.accumulo.core.conf.Property.TSERV_COMPACTION_WARN_TIME;
import static org.apache.accumulo.harness.AccumuloITBase.MINI_CLUSTER_ONLY;
import static org.apache.accumulo.harness.AccumuloITBase.SUNNY_DAY;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -27,6 +28,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.nio.file.Files;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -40,7 +42,9 @@ import java.util.TimeZone;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.conf.PropertyType;
+import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.harness.SharedMiniClusterBase;
+import org.apache.accumulo.server.conf.TableConfiguration;
import org.apache.accumulo.shell.Shell;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
@@ -53,6 +57,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -120,10 +125,10 @@ public class ShellIT extends SharedMiniClusterBase {
private LineReader reader;
private Terminal terminal;
- void execExpectList(String cmd, boolean expecteGoodExit, List<String>
expectedStrings)
+ void execExpectList(String cmd, boolean expectGoodExit, List<String>
expectedStrings)
throws IOException {
exec(cmd);
- if (expecteGoodExit) {
+ if (expectGoodExit) {
assertGoodExit("", true);
} else {
assertBadExit("", true);
@@ -640,6 +645,57 @@ public class ShellIT extends SharedMiniClusterBase {
assertGoodExit("Unknown command", false);
}
+ @TempDir
+ private static File tempDir;
+
+ @Test
+ public void propFileNotFoundTest() throws IOException {
+ String fileName = new File(tempDir, "propFile.shellit").getAbsolutePath();
+
+ Shell.log.debug("Starting prop file not found test
--------------------------");
+ exec("config --propFile " + fileName, false,
+ "FileNotFoundException: " + fileName + " (No such file or directory)");
+ }
+
+ @Test
+ public void setpropsViaFile() throws Exception {
+ File file = File.createTempFile("propFile", ".conf", tempDir);
+ PrintWriter writer = new PrintWriter(file.getAbsolutePath());
+ writer.println(TSERV_COMPACTION_WARN_TIME.getKey() + "=11m");
+ writer.close();
+ exec("config --propFile " + file.getAbsolutePath(), true);
+ }
+
+ @Test
+ public void createTableWithPropsFile() throws Exception {
+ File file = File.createTempFile("propFile", ".conf", tempDir);
+ PrintWriter writer = new PrintWriter(file.getAbsolutePath());
+ writer.println("table.custom.test1=true");
+ writer.println("table.custom.test2=false");
+ writer.close();
+ exec("createtable test --propFile " + file.getAbsolutePath()
+ + " -prop table.custom.test3=optional", true);
+
+ assertTrue(shell.getAccumuloClient().tableOperations().exists("test"));
+ Map<String,String> tableIds =
shell.getAccumuloClient().tableOperations().tableIdMap();
+
+ TableConfiguration tableConf =
+
getCluster().getServerContext().getTableConfiguration(TableId.of(tableIds.get("test")));
+ assertEquals("true", tableConf.get("table.custom.test1"));
+ assertEquals("false", tableConf.get("table.custom.test2"));
+ assertEquals("optional", tableConf.get("table.custom.test3"));
+ }
+
+ @Test
+ public void invalidPropFileTest() throws Exception {
+ File file = File.createTempFile("invalidPropFile", ".conf", tempDir);
+ PrintWriter writer = new PrintWriter(file.getAbsolutePath());
+ writer.println("this is not a valid property file");
+ writer.close();
+ exec("config --propFile " + file.getAbsolutePath(), false,
+ "InvalidPropertyFile: " + file.getAbsolutePath());
+ }
+
@Test
public void setIterTest() throws IOException {
Shell.log.debug("Starting setiter test --------------------------");