Skip to content

Commit

Permalink
[crypt][properties] option to keep properties comments in output files
Browse files Browse the repository at this point in the history
  • Loading branch information
rmannibucau committed Jul 3, 2023
1 parent 6baca33 commit 2f76dd9
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ public void run() {
}

private String decapitalize(final String replace) {
if (replace == null || replace.isBlank()) {
log.warning("Empty documentation value");
return "";
}
return Character.toLowerCase(replace.charAt(0)) + replace.substring(1);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
// and reuseable without maven (codec-core module)
// so we just wrap codec-core here
public abstract class BaseCryptMojo extends AbstractMojo {
/**
* Master password for the enryption (AES/CBC/PKCS5Padding).
*/
@Parameter(property = "yupiik.crypt.masterPassword", required = true)
protected String masterPassword;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.yupiik.maven.mojo;

import io.yupiik.maven.properties.LightProperties;
import io.yupiik.tools.codec.Codec;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
Expand Down Expand Up @@ -43,15 +44,34 @@
import static java.util.stream.Collectors.toMap;

public abstract class BaseCryptPropertiesMojo extends BaseCryptMojo {
/**
* Input properties file path.
*/
@Parameter(property = "yupiik.crypt-properties.input", required = true)
protected File input;

/**
* Target location of the encrypted properties file.
*/
@Parameter(property = "yupiik.crypt-properties.output", required = true)
protected File output;

/**
* Should properties structure be preserved (comments, order) or not.
* This can break properties structure in some rare cases, if so disable this.
*/
@Parameter(property = "yupiik.crypt-properties.preserveComments", defaultValue = "true")
protected boolean preserveComments;

/**
* List of keys to encrypt the value for.
*/
@Parameter(property = "yupiik.crypt-properties.includedKeys")
protected List<String> includedKeys;

/**
* List of keys to not encrypt the value for.
*/
@Parameter(property = "yupiik.crypt-properties.excludedKeys")
protected List<String> excludedKeys;

Expand All @@ -63,22 +83,23 @@ public void execute() throws MojoExecutionException, MojoFailureException {
}

try {
final var data = new Properties();
final var input = new LightProperties(getLog());
try (final var read = Files.newBufferedReader(from)) {
data.load(read);
input.load(read, !preserveComments);
}
final var inputProps = input.toWorkProperties();

final var keyFilter = createKeyFilter();

final var untouched = new Properties();
untouched.putAll(data.stringPropertyNames().stream()
untouched.putAll(inputProps.stringPropertyNames().stream()
.filter(Predicate.not(keyFilter))
.collect(toMap(identity(), data::getProperty)));
.collect(toMap(identity(), inputProps::getProperty)));

final var transformedSource = new Properties();
transformedSource.putAll(data.stringPropertyNames().stream()
transformedSource.putAll(inputProps.stringPropertyNames().stream()
.filter(keyFilter)
.collect(toMap(identity(), data::getProperty)));
.collect(toMap(identity(), inputProps::getProperty)));

final var transformed = new Properties() { // sorted...depends jvm version so override most of them
@Override
Expand Down Expand Up @@ -109,26 +130,15 @@ public Set<Map.Entry<Object, Object>> entrySet() {
if (to.getParent() != null) {
Files.createDirectories(to.getParent());
}
try (final var write = new BufferedWriter(Files.newBufferedWriter(to)) {
private int remainingNewLinesToSkip = 2;

@Override
public void write(final String str) throws IOException {
if (remainingNewLinesToSkip == 0) {
super.write(str);
}
final var outputWriter = Files.newBufferedWriter(to);
if (preserveComments) {
try (outputWriter) {
input.write(transformed, outputWriter);
}

@Override
public void newLine() throws IOException {
if (remainingNewLinesToSkip > 0) {
remainingNewLinesToSkip--;
} else {
super.newLine();
}
} else {
try (final var write = new LightProperties.SimplePropertiesWriter(outputWriter)) {
transformed.store(write, "# generated by " + getClass().getSimpleName() + " yupiik-tools-maven-plugin");
}
}) {
transformed.store(write, "# generated by " + getClass().getSimpleName() + " yupiik-tools-maven-plugin");
}
} catch (final IOException ioe) {
throw new MojoFailureException(ioe.getMessage(), ioe);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

/**
* Enables to crypt a value and log it.
*/
@Mojo(name = "crypt-value", threadSafe = true)
public class CryptMojo extends BaseCryptMojo {
/**
* Value to crypt.
*/
@Parameter(property = "yupiik.crypt.value", required = true)
private String value;

/**
* Should the encrypted value be printed using stdout or maven logger.
*/
@Parameter(property = "yupiik.crypt.useStdout", defaultValue = "false")
private boolean useStdout;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;

/**
* Enables to crypt a properties file.
*/
@Mojo(name = "crypt-properties", threadSafe = true)
public class CryptPropertiesMojo extends BaseCryptPropertiesMojo {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

/**
* Enables to decrypt a value.
*/
@Mojo(name = "decrypt-value", threadSafe = true)
public class DecryptMojo extends BaseCryptMojo {
/**
* Value to decrypt.
*/
@Parameter(property = "yupiik.decrypt.value", required = true)
private String value;

/**
* Should the clear value be printed using maven logger or directly in stdout.
*/
@Parameter(property = "yupiik.decrypt.useStdout", defaultValue = "false")
private boolean useStdout;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;

/**
* Enables to decrypt an encrypted properties file.
*/
@Mojo(name = "decrypt-properties", threadSafe = true)
public class DecryptPropertiesMojo extends BaseCryptPropertiesMojo {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright (c) 2020 - 2023 - Yupiik SAS - https://www.yupiik.com
* Licensed 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 io.yupiik.maven.properties;

import lombok.Data;
import org.apache.maven.plugin.logging.Log;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collector;

/**
* This class intends to be able to load properties but keep the original layout as much as possible
* and rewrite it with the comments in place.
*/
public class LightProperties {
private final Log log;
private final List<Line> lines = new ArrayList<>();
private final Properties properties = new Properties();

public LightProperties(final Log log) {
this.log = log;
}

public void load(final BufferedReader reader, final boolean usePlainProperties) throws IOException {
if (usePlainProperties) {
properties.load(reader);
} else {
doLoad(reader);
}
}

public Properties toWorkProperties() {
return lines.isEmpty() ? properties : lines.stream()
.filter(it -> it.key != null)
.collect(Collector.of(Properties::new, (p, l) -> p.setProperty(l.key, l.value), (p1, p2) -> {
p1.putAll(p2);
return p1;
}));
}

public void write(final Properties transformed, final Writer outputWriter) throws IOException {
for (final var line : lines) {
if (line.key == null) {
outputWriter.write(line.comment + '\n');
} else {
final var newValue = transformed.getProperty(line.key);
if (newValue == null) {
log.warn("Missing value for '" + line.key + "'");
continue; // ignore
}

// ensure we encode right
final var tmp = new Properties();
tmp.setProperty(line.key, newValue);
final var writer = new StringWriter();
try (final var out = new SimplePropertiesWriter(writer)) {
tmp.store(out, "ignored");
}
outputWriter.write((line.comment != null && !line.comment.isBlank() ? line.comment + '\n' : "") + writer.toString().strip() + '\n');
}
}
}

private void doLoad(final BufferedReader reader) {
final var iterator = reader.lines().iterator();
final var comments = new ArrayList<String>();
while (iterator.hasNext()) {
final var line = iterator.next();
if (line.startsWith("#")) {
comments.add(line);
} else if (line.strip().isBlank()) {
if (!comments.isEmpty()) {
lines.add(new Line(String.join("\n", comments), null, null, null));
comments.clear();
}
lines.add(new Line(line, null, null, null));
} else {
boolean found = false;
for (int i = 0; i < line.length(); i++) {
switch (line.charAt(i)) {
case '\\':
i++; // escaped so don't check next char, can't be a separator
break;
case ':':
case '=':
var value = line.substring(i + 1);
if (value.endsWith("\\") && iterator.hasNext()) {
value += '\n' + iterator.next();
}
lines.add(new Line(
String.join("\n", comments),
line.substring(0, i).strip(),
Character.toString(line.charAt(i)),
value.strip()));
comments.clear();
found = true;
break;
default:
}
if (found) {
break;
}
}
if (!found) {
lines.add(new Line(line, null, null, null));
}
}
}
if (!comments.isEmpty()) {
lines.add(new Line(String.join("\n", comments), null, null, null));
}
}

@Data
private static class Line {
private final String comment;
private final String key;
private final String separator;
private final String value;
}

public static class SimplePropertiesWriter extends BufferedWriter {
private int remainingNewLinesToSkip = 2;

public SimplePropertiesWriter(final Writer outputWriter) {
super(outputWriter);
}

@Override
public void write(final String str) throws IOException {
if (remainingNewLinesToSkip == 0) {
super.write(str);
}
}

@Override
public void newLine() throws IOException {
if (remainingNewLinesToSkip > 0) {
remainingNewLinesToSkip--;
} else {
super.newLine();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void roundTrip(@TempDir final Path work) throws IOException, MojoExecutionExcept
this.masterPassword = master;
this.input = source.toFile();
this.output = target.toFile();
this.preserveComments = true;
}
}.execute();

Expand All @@ -61,6 +62,7 @@ void roundTrip(@TempDir final Path work) throws IOException, MojoExecutionExcept
this.masterPassword = master;
this.input = source.toFile();
this.output = decrypted.toFile();
this.preserveComments = true;
}
}.execute();

Expand Down Expand Up @@ -100,6 +102,7 @@ private void doIncludeExcludes(final Path work, final List<String> includes, fin
this.output = target.toFile();
this.includedKeys = includes;
this.excludedKeys = excludes;
this.preserveComments = true;
}
}.execute();

Expand Down

0 comments on commit 2f76dd9

Please sign in to comment.