Skip to content

Commit

Permalink
Make stdin conditional
Browse files Browse the repository at this point in the history
  • Loading branch information
agentgt committed Jan 13, 2025
1 parent 1b2b578 commit 90fa57e
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 18 deletions.
28 changes: 28 additions & 0 deletions doc/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,34 @@ <h4 id="uri-schemas-stdin">URI schema: <code>stdin</code></h4>
<p>The above example is particularly useful for passwords, similar to
<a href="https://docs.docker.com/reference/cli/docker/login/#password-stdin" target="_blank">how Docker handles passwords from stdin</a>.</p>


Stdin is retrieved from {@link io.jstach.ezkv.kvs.KeyValuesEnvironment#getStandardInput()}.
Because checking stdin will block an application one must enable reading from it which is done
by either:
<ol>
<li> Checking resource parameter of {@value io.jstach.ezkv.kvs.KeyValuesResource#SCHEMA_STDIN_PARAM} is <code>true</code>.
</li>
<li>Setting {@value io.jstach.ezkv.kvs.KeyValuesResource#SCHEMA_STDIN_MAIN_ARG_PARAM} to
{@linkplain io.jstach.ezkv.kvs.KeyValuesEnvironment#getMainArgs() command line argument} to check if present.
</li>
<li>
Relying on the default which will check "<code>--</code>" + {@link io.jstach.ezkv.kvs.KeyValuesResource#name()}
command line argument is present.
</li>
</ol>

{@snippet lang=properties :
# will be enabled if --dbpassword is passed on the command line
# since dbpassword is the resource name.
_load_dbpassword=stdin:///db.password?_flag=sensitive,optional
# specify that the "--password" command line arg is required
_load_stdin=stdin:///db.password?_param_stdin_arg=--password&_flag=sensitive
# alternatively one can do:
_load_stdin=stdin:///?_p_stdin=${some_other_variable}
# which will be enabled if some_other_variable evalutes to 'true'
}


<h4 id="uri-schema-profile">URI schema: <code>profile.</code></h4>
<p><strong>Note:</strong> This schema may be renamed to <code>profiles</code> in the future.</p>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -179,6 +178,33 @@ protected KeyValues load(LoaderContext context, KeyValuesResource resource) thro
STDIN(KeyValuesResource.SCHEMA_STDIN) {
@Override
protected KeyValues load(LoaderContext context, KeyValuesResource resource) throws IOException {

boolean enabled;

String stdinEnabled = resource.parameters().getValue(KeyValuesResource.SCHEMA_STDIN_PARAM);

if (stdinEnabled != null) {
enabled = Boolean.parseBoolean(stdinEnabled);
}
else {
String mainArgName = resource.parameters().getValue(KeyValuesResource.SCHEMA_STDIN_MAIN_ARG_PARAM);

if (mainArgName == null) {
mainArgName = "--" + resource.name();
}
String argName = mainArgName;

enabled = Arrays.asList(context.environment().getMainArgs())
.stream()
.filter(s -> s.equals(argName))
.findFirst()
.isPresent();
}

if (!enabled) {
throw new FileNotFoundException("stdin not enabled.");
}

var builder = KeyValues.builder(resource);

BiConsumer<String, String> consumer = builder::add;
Expand Down
52 changes: 40 additions & 12 deletions ezkv-kvs/src/main/java/io/jstach/ezkv/kvs/KeyValuesEnvironment.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
Expand All @@ -34,6 +37,7 @@
*/
public interface KeyValuesEnvironment {

// TODO remove
/**
* If the loader builder is not passed any resources this resource will be used.
* @return default resource is <code>classpath:/boot.properties</code>
Expand All @@ -47,7 +51,32 @@ default KeyValuesResource defaultResource() {
* @return an array of main method arguments
*/
default @NonNull String[] getMainArgs() {
return new @NonNull String[] {};
var logger = getLogger();
logger.warn("Main Args were requested but not provided. Using fallback 'sun.java.command'");
String value = getSystemProperties().getProperty("sun.java.command");
if (value == null) {
throw new IllegalStateException("Cannot get main args from 'sun.java.command' as it was not provided");
}
return fallbackMainArgs(value);
}

private static @NonNull String[] fallbackMainArgs(String sunJavaCommandValue) {

// Use fallback by extracting from system property
String command = sunJavaCommandValue;
if (!command.isEmpty()) {
// Split command into components (main class/jar is the first token)
List<String> components = new ArrayList<>(Arrays.asList(command.split("\\s+")));
// Remove the first element (main class or jar)
if (!components.isEmpty()) {
components.remove(0);
}
// Return remaining as the main args
return components.toArray(new @NonNull String[0]);
}

// Return an empty array if no arguments are available
return new String[0];
}

/**
Expand Down Expand Up @@ -356,16 +385,15 @@ public void warn(String message) {

}

final class DefaultKeyValuesEnvironment implements KeyValuesEnvironment {

// private static final ThreadLocal<Logger> threadLocal = new
// ThreadLocal<>();
//
// static Logger localLogger() {
// var logger = threadLocal.get();
// if (logger == null) {
// return NoOpLogger.NOPLOGGER;
// }
// }
record DefaultKeyValuesEnvironment(@NonNull String @Nullable [] mainArgs) implements KeyValuesEnvironment {

@Override
public @NonNull String[] getMainArgs() {
var args = mainArgs;
if (args == null) {
return KeyValuesEnvironment.super.getMainArgs();
}
return args;
}

}
38 changes: 35 additions & 3 deletions ezkv-kvs/src/main/java/io/jstach/ezkv/kvs/KeyValuesResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -486,17 +486,49 @@ public sealed interface KeyValuesResource extends NamedKeyValuesSource, KeyValue
* </p>
* <code>stdin:///</code> can also bind the input content, parsed as a UTF-8 string,
* to the key provided in the URI path.
* <p>
* Stdin is retrieved from {@link KeyValuesEnvironment#getStandardInput()}.
* Because checking stdin will block an application one must enable reading from it which is done
* by either:
* <ol>
* <li> Checking resource parameter of {@value #SCHEMA_STDIN_PARAM} is <code>true</code>.
* </li>
* <li>Setting {@value #SCHEMA_STDIN_MAIN_ARG_PARAM} to
* {@linkplain KeyValuesEnvironment#getMainArgs() command line argument} to check if present.
* </li>
* <li>
* Relying on the default which will check <code>--</code> {@link KeyValuesResource#name()}
* command line argument is present.
* </li>
* </ol>
*
* {@snippet lang=properties :
* # will be enabled if --stdin is passed on the command line
* # since stdin in is the resource name.
* _load_stdin=stdin:///db.password?_flag=sensitive,optional
* # alternatively one can do:
* _load_stdin=stdin:///?_p_stdin=${some_other_variable}
* }
*
* Stdin is retrieved from {@link KeyValuesEnvironment#getStandardInput()}.
*
* @see #SCHEMA_STDIN_PARAM
* @see #SCHEMA_STDIN_MAIN_ARG_PARAM
* @see KeyValuesEnvironment#getMainArgs()
*/
// @formatter:on
public static final String SCHEMA_STDIN = "stdin";

/**
* Resource parameter to enable stdin.
* @see #SCHEMA_STDIN
*/
public static final String SCHEMA_STDIN_PARAM = "stdin";

/**
* Resource parameter to decide what command line argument should be present to allow
* reading from stdin.
* @see #SCHEMA_STDIN
*/
public static final String SCHEMA_STDIN_MAIN_ARG_PARAM = "stdin_arg";

/**
* Will load multiple resources based on a CSV of profiles where the profile name
* replaces part of the URI.
Expand Down
21 changes: 19 additions & 2 deletions ezkv-kvs/src/main/java/io/jstach/ezkv/kvs/KeyValuesSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Function;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import io.jstach.ezkv.kvs.KeyValuesServiceProvider.KeyValuesFilter;
Expand Down Expand Up @@ -106,13 +108,28 @@ default void close() {
/**
* Returns a default implementation of {@code KeyValuesSystem} configured with
* standard settings and a ServiceLoader based on {@link KeyValuesServiceProvider} and
* its classloader.
* its classloader. If possible prefer {@link #defaults(String[])} to provide command
* line arguments.
* @return the default {@code KeyValuesSystem} instance
*/
public static KeyValuesSystem defaults() {
return builder().useServiceLoader().build();
}

/**
* Returns a default implementation of {@code KeyValuesSystem} configured with
* standard settings and a ServiceLoader based on {@link KeyValuesServiceProvider} and
* its classloader.
* @param mainArgs arguments that come from
* <code>public static void main(String [] args)</code>.
* @return the default {@code KeyValuesSystem} instance
*/
public static KeyValuesSystem defaults(@NonNull String[] mainArgs) {
Objects.requireNonNull(mainArgs);
var env = new DefaultKeyValuesEnvironment(mainArgs);
return builder().environment(env).useServiceLoader().build();
}

/**
* Creates and returns a new {@link Builder} for constructing customized
* {@code KeyValuesSystem} instances.
Expand Down Expand Up @@ -240,7 +257,7 @@ public Builder useServiceLoader() {
public KeyValuesSystem build() {
var environment = this.environment;
if (environment == null) {
environment = new DefaultKeyValuesEnvironment();
environment = new DefaultKeyValuesEnvironment(null);
}
/*
* We copy as we are about to use the service loader to add more entries and
Expand Down
23 changes: 23 additions & 0 deletions ezkv-kvs/src/test/java/io/jstach/ezkv/kvs/KeyValuesSystemTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URI;
import java.nio.file.Path;
Expand All @@ -15,6 +17,7 @@
import java.util.Properties;
import java.util.stream.Collectors;

import org.jspecify.annotations.NonNull;
import org.junit.jupiter.api.Test;

import io.jstach.ezkv.kvs.KeyValuesEnvironment.Logger;
Expand Down Expand Up @@ -45,6 +48,10 @@ void testLoader() throws FileNotFoundException, IOException {

var logger = new TestLogger();

String stdin = """
stdin_password=guest
""";

var environment = new KeyValuesEnvironment() {
@Override
public Properties getSystemProperties() {
Expand All @@ -56,6 +63,16 @@ public Logger getLogger() {
return logger;
}

@Override
public InputStream getStandardInput() {
return new ByteArrayInputStream(stdin.getBytes());
}

@Override
public @NonNull String[] getMainArgs() {
return new String[] { "--passwords" };
}

};
var system = KeyValuesResource.builder(URI.create("system:///"))
.name("system")
Expand Down Expand Up @@ -93,6 +110,7 @@ public Logger getLogger() {
.add(system)
.add("classpath:/test-props/testLoader.properties")
.add("classpaths:/classpathstar.properties")
.add("stdin:///?_p_stdin_arg=--passwords&_mime=properties&_flag=sensitive")
.add("extra", KeyValues.builder().add(map.entrySet()).build())
.load();

Expand All @@ -113,6 +131,7 @@ public Logger getLogger() {
sensitive_ab=REDACTED
mypassword=REDACTED
classpathstar=ezkv
stdin_password=REDACTED
fromMap1=1
fromMap2=2
]
Expand All @@ -135,6 +154,7 @@ public Logger getLogger() {
sensitive_ab=ab
mypassword=1.2.3.4.5
classpathstar=ezkv
stdin_password=guest
fromMap1=1
fromMap2=2
""";
Expand Down Expand Up @@ -162,6 +182,7 @@ public Logger getLogger() {
KeyValue[key='sensitive_ab', raw='REDACTED', expanded='REDACTED', source=Source[uri=classpath:/test-props/testLoader-sensitive.properties, reference=[key='_load_luggage', in='classpath:/test-props/testLoader.properties'], index=2]]
KeyValue[key='mypassword', raw='REDACTED', expanded='REDACTED', source=Source[uri=classpath:/test-props/testLoader-sensitive.properties, reference=[key='_load_luggage', in='classpath:/test-props/testLoader.properties'], index=3]]
KeyValue[key='classpathstar', raw='ezkv', expanded='ezkv', source=Source[uri=file:{{CWD}}/target/test-classes/classpathstar.properties, reference=[key='_load_root30', in='classpaths:/classpathstar.properties'], index=1]]
KeyValue[key='stdin_password', raw='REDACTED', expanded='REDACTED', source=Source[uri=stdin:///, index=1]]
KeyValue[key='fromMap1', raw='1', expanded='1', source=Source[uri=null:///extra, index=0]]
KeyValue[key='fromMap2', raw='2', expanded='2', source=Source[uri=null:///extra, index=0]]
"""
Expand Down Expand Up @@ -204,6 +225,8 @@ public Logger getLogger() {
[INFO ] Loaded uri='classpaths:/classpathstar.properties'
[DEBUG] Loading uri='file:{{CWD}}/target/test-classes/classpathstar.properties' flags=[NO_LOAD_CHILDREN] specified with key: '_load_root30' in uri='classpaths:/classpathstar.properties'
[INFO ] Loaded uri='file:{{CWD}}/target/test-classes/classpathstar.properties' flags=[NO_LOAD_CHILDREN]
[DEBUG] Loading uri='stdin:///' flags=[SENSITIVE]
[INFO ] Loaded uri='stdin:///' flags=[SENSITIVE]
"""
.replace("{{CWD}}", cwd);
assertEquals(expected, actual);
Expand Down

0 comments on commit 90fa57e

Please sign in to comment.