Skip to content

Commit

Permalink
props: allow for custom properties (#321)
Browse files Browse the repository at this point in the history
  • Loading branch information
itzg authored Oct 7, 2023
1 parent f1da0e7 commit 58f1ee2
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 33 deletions.
95 changes: 64 additions & 31 deletions src/main/java/me/itzg/helpers/properties/SetPropertiesCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Callable;
Expand All @@ -29,8 +31,13 @@ public class SetPropertiesCommand implements Callable<Integer> {
public static final TypeReference<Map<String, PropertyDefinition>> PROPERTY_DEFINITIONS_TYPE = new TypeReference<Map<String, PropertyDefinition>>() {
};

@Option(names = "--definitions", required = true, description = "JSON file of property names to PropertyDefinition mappings")
Path propertyDefinitions;
@Option(names = "--definitions", description = "JSON file of property names to PropertyDefinition mappings")
Path propertyDefinitionsFile;

@Option(names = {"--custom-property", "--custom-properties", "-p"},
split = "\n", splitSynopsisLabel = "<NL>",
description = "Key=value pairs of custom properties to set")
Map<String,String> customProperties;

@Parameters(arity = "1")
Path propertiesFile;
Expand All @@ -40,13 +47,23 @@ public class SetPropertiesCommand implements Callable<Integer> {

@Override
public Integer call() throws Exception {

if (!Files.exists(propertyDefinitions)) {
throw new InvalidParameterException("Property definitions file does not exist");
if (propertyDefinitionsFile == null && customProperties == null) {
System.err.println("Either definitions or custom properties need to be provided");
return ExitCode.USAGE;
}

final Map<String, PropertyDefinition> propertyDefinitions = ObjectMappers.defaultMapper()
.readValue(this.propertyDefinitions.toFile(), PROPERTY_DEFINITIONS_TYPE);
final Map<String, PropertyDefinition> propertyDefinitions;
if (propertyDefinitionsFile != null) {
if (!Files.exists(propertyDefinitionsFile)) {
throw new InvalidParameterException("Property definitions file does not exist");
}

propertyDefinitions = ObjectMappers.defaultMapper()
.readValue(this.propertyDefinitionsFile.toFile(), PROPERTY_DEFINITIONS_TYPE);
}
else {
propertyDefinitions = Collections.emptyMap();
}

final Properties properties = new Properties();
if (Files.exists(propertiesFile)) {
Expand All @@ -55,11 +72,11 @@ public Integer call() throws Exception {
}
}

final long changes = processProperties(propertyDefinitions, properties);
final long changes = processProperties(propertyDefinitions, properties, customProperties);
if (changes > 0) {
log.info("Created/updated {} propert{} in {}", changes, changes != 1 ? "ies":"y", propertiesFile);

try (OutputStream propsOut = Files.newOutputStream(propertiesFile, StandardOpenOption.TRUNCATE_EXISTING)) {
try (OutputStream propsOut = Files.newOutputStream(propertiesFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) {
properties.store(propsOut, String.format("Updated %s by mc-image-helper", Instant.now()));
}
}
Expand All @@ -70,40 +87,56 @@ public Integer call() throws Exception {
/**
* @return count of added/modified properties
*/
private long processProperties(Map<String, PropertyDefinition> propertyDefinitions, Properties properties) {
return propertyDefinitions.entrySet().stream()
.map(entry -> {
final String name = entry.getKey();
final PropertyDefinition definition = entry.getValue();

if (definition.isRemove()) {
if (properties.containsKey(name)) {
log.debug("Removing {}, which is marked for removal", name);
properties.remove(name);
return true;
}
else {
return false;
}
private long processProperties(Map<String, PropertyDefinition> propertyDefinitions, Properties properties,
Map<String, String> customProperties
) {
long modifiedViaDefinitions = 0;
for (final Entry<String, PropertyDefinition> entry : propertyDefinitions.entrySet()) {
final String name = entry.getKey();
final PropertyDefinition definition = entry.getValue();

if (definition.isRemove()) {
if (properties.containsKey(name)) {
log.debug("Removing {}, which is marked for removal", name);
properties.remove(name);
++modifiedViaDefinitions;
}

}
else {
final String envValue = environmentVariablesProvider.get(definition.getEnv());
if (envValue != null) {
final String expectedValue = mapAndValidateValue(definition, envValue);

final String propValue = properties.getProperty(name);

if (!Objects.equals(expectedValue, propValue)) {
log.debug("Setting property {} to new value '{}'", name, expectedValue);
log.debug("Setting property {} to new value '{}'", name, needsValueRedacted(name) ? "***" : expectedValue);
properties.setProperty(name, expectedValue);
return true;
++modifiedViaDefinitions;
}
}
}
}

long modifiedViaCustom = 0;
if (customProperties != null) {
for (final Entry<String, String> entry : customProperties.entrySet()) {
final String name = entry.getKey();
final String targetValue = entry.getValue();
final String propValue = properties.getProperty(name);
if (!Objects.equals(targetValue, propValue)) {
log.debug("Setting property {} to new value '{}'", name, targetValue);
properties.setProperty(name, targetValue);
++modifiedViaCustom;
}
}
}

return modifiedViaDefinitions + modifiedViaCustom;
}

return false;
})
.filter(modified -> modified)
.count();
private static boolean needsValueRedacted(String name) {
return name.contains("password");
}

private String mapAndValidateValue(PropertyDefinition definition, String value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ void disallowedValue() throws Exception {
@Test
void removesMarkedForRemoval() throws IOException {
final Path hasWhiteList = Files.write(tempDir.resolve("old.properties"), Collections.singletonList("white-list=true"));
final int exitCode = new CommandLine(new SetPropertiesCommand()
)
final int exitCode = new CommandLine(new SetPropertiesCommand())
.execute(
"--definitions", definitionsFile.toString(),
hasWhiteList.toString()
Expand All @@ -128,6 +127,52 @@ void removesMarkedForRemoval() throws IOException {
assertThat(properties).doesNotContainKey("white-list");
}

@Test
void handlesNewCustomProperty() throws IOException {
final Path outputProperties = tempDir.resolve("output.properties");

final int exitCode = new CommandLine(new SetPropertiesCommand())
.execute(
"--custom-property", "key1=value1",
outputProperties.toString()
);

assertThat(exitCode).isEqualTo(ExitCode.OK);

final Properties properties = new Properties();
try (InputStream propsIn = Files.newInputStream(outputProperties)) {
properties.load(propsIn);
}

assertThat(properties)
.containsEntry("key1", "value1");
}

@Test
void handlesModifiedCustomProperties() throws IOException {
final Path outputProperties = tempDir.resolve("output.properties");
Files.write(outputProperties, Collections.singletonList("key1=value1"));

final int exitCode = new CommandLine(new SetPropertiesCommand())
.execute(
"--custom-property", "key1=newValue1",
"--custom-property", "key2=value2",
outputProperties.toString()
);

assertThat(exitCode).isEqualTo(ExitCode.OK);

final Properties properties = new Properties();
try (InputStream propsIn = Files.newInputStream(outputProperties)) {
properties.load(propsIn);
}

assertThat(properties)
.hasSize(2)
.containsEntry("key1", "newValue1")
.containsEntry("key2", "value2");
}

private void assertPropertiesEqualExcept(Properties properties, String... propertiesToIgnore) {
final HashSet<Object> actualKeys = new HashSet<>(properties.keySet());
Arrays.asList(propertiesToIgnore).forEach(actualKeys::remove);
Expand Down

0 comments on commit 58f1ee2

Please sign in to comment.