Skip to content

Commit

Permalink
feat: enhance Helm Install to support multiple set and values paramet…
Browse files Browse the repository at this point in the history
…ers (#367)

Signed-off-by: Jeromy Cannon <jeromy@swirldslabs.com>
  • Loading branch information
jeromy-cannon committed Oct 4, 2023
1 parent 5dd625a commit 05444df
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,30 +218,29 @@ public <T> T responseAs(final Class<T> responseClass, final Duration timeout) {
}

if (exitCode() != 0) {
throw new HelmExecutionException(exitCode());
throw new HelmExecutionException(
exitCode(),
StreamUtils.streamToString(suppressExceptions(this::standardOutput)),
StreamUtils.streamToString(suppressExceptions(this::standardError)));
}

final String standardOutput = StreamUtils.streamToString(suppressExceptions(this::standardOutput));
final String standardError = StreamUtils.streamToString(suppressExceptions(this::standardError));

LOGGER.atDebug()
.setMessage(
"ResponseAs exiting with exitCode: {}\n\tResponseClass: {}\n\tstandardOutput: {}\n\tstandardError: {}")
.addArgument(this::exitCode)
.addArgument(responseClass.getName())
.addArgument(standardOutput)
.addArgument(standardError)
.log();
LOGGER.debug(
"ResponseAs exiting with exitCode: {}TODO\n\tResponseClass: {}\n\tstandardOutput: {}\n\tstandardError: {}",
exitCode(),
responseClass.getName(),
standardOutput,
standardError);

try {
return OBJECT_MAPPER.readValue(standardOutput, responseClass);
} catch (final Exception e) {
LOGGER.atWarn()
.setMessage("ResponseAs failed to deserialize response into class: {}\n\tresponse: {}")
.addArgument(responseClass.getName())
.addArgument(standardOutput)
.setCause(e)
.log();
LOGGER.warn(
String.format(
"ResponseAs failed to deserialize response into class: %s%n\tresponse: %s",
responseClass.getName(), standardOutput),
e);

throw new HelmParserException(String.format(MSG_DESERIALIZATION_ERROR, responseClass.getName()), e);
}
Expand Down Expand Up @@ -291,28 +290,24 @@ public <T> List<T> responseAsList(final Class<T> responseClass, final Duration t
final String standardOutput = StreamUtils.streamToString(suppressExceptions(this::standardOutput));
final String standardError = StreamUtils.streamToString(suppressExceptions(this::standardError));

LOGGER.atDebug()
.setMessage(
"ResponseAsList exiting with exitCode: {}\n\tResponseClass: {}\n\tstandardOutput: {}\n\tstandardError: {}")
.addArgument(this::exitCode)
.addArgument(responseClass.getName())
.addArgument(standardOutput)
.addArgument(standardError)
.log();
LOGGER.debug(
"ResponseAsList exiting with exitCode: {}\n\tResponseClass: {}\n\tstandardOutput: {}\n\tstandardError: {}",
exitCode(),
responseClass.getName(),
standardOutput,
standardError);

try {
return OBJECT_MAPPER
.readerFor(responseClass)
.<T>readValues(standardOutput)
.readAll();
} catch (final Exception e) {
LOGGER.atWarn()
.setMessage(
"ResponseAsList failed to deserialize the output into a list of the specified class: {}\n\tresponse: {}")
.addArgument(responseClass.getName())
.addArgument(standardOutput)
.setCause(e)
.log();
LOGGER.warn(
String.format(
"ResponseAsList failed to deserialize the output into a list of the specified class: %s%n\tresponse: %s",
responseClass.getName(), standardOutput),
e);

throw new HelmParserException(String.format(MSG_LIST_DESERIALIZATION_ERROR, responseClass.getName()), e);
}
Expand Down Expand Up @@ -349,20 +344,18 @@ public void call(final Duration timeout) {
final String standardOutput = StreamUtils.streamToString(suppressExceptions(this::standardOutput));
final String standardError = StreamUtils.streamToString(suppressExceptions(this::standardError));

LOGGER.atDebug()
.setMessage("Call exiting with exitCode: {}\n\tstandardOutput: {}\n\tstandardError: {}")
.addArgument(this::exitCode)
.addArgument(standardOutput)
.addArgument(standardError)
.log();
LOGGER.debug(
"Call exiting with exitCode: {}\n\tstandardOutput: {}\n\tstandardError: {}",
exitCode(),
standardOutput,
standardError);

if (exitCode() != 0) {
LOGGER.atWarn()
.setMessage("Call failed with exitCode: {}\n\tstandardOutput: {}\n\tstandardError: {}")
.addArgument(this::exitCode)
.addArgument(standardOutput)
.addArgument(standardError)
.log();
LOGGER.warn(
"Call failed with exitCode: {}\n\tstandardOutput: {}\n\tstandardError: {}",
exitCode(),
standardOutput,
standardError);

throw new HelmExecutionException(exitCode(), standardError, standardOutput);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

package com.hedera.fullstack.helm.client.execution;

import com.hedera.fullstack.base.api.collections.KeyValuePair;
import com.hedera.fullstack.helm.client.HelmConfigurationException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
Expand All @@ -28,7 +30,8 @@
*/
public final class HelmExecutionBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(HelmExecutionBuilder.class);

public static final String NAME_MUST_NOT_BE_NULL = "name must not be null";
public static final String VALUE_MUST_NOT_BE_NULL = "value must not be null";
/**
* The path to the helm executable.
*/
Expand All @@ -42,7 +45,12 @@ public final class HelmExecutionBuilder {
/**
* The arguments to be passed to the helm command.
*/
private final Map<String, String> arguments;
private final HashMap<String, String> arguments;

/**
* The list of options and a list of their one or more values.
*/
private final List<KeyValuePair<String, List<String>>> optionsWithMultipleValues;

/**
* The flags to be passed to the helm command.
Expand Down Expand Up @@ -73,10 +81,15 @@ public HelmExecutionBuilder(final Path helmExecutable) {
this.helmExecutable = Objects.requireNonNull(helmExecutable, "helmExecutable must not be null");
this.subcommands = new ArrayList<>();
this.arguments = new HashMap<>();
this.optionsWithMultipleValues = new ArrayList<>();
this.positionals = new ArrayList<>();
this.flags = new ArrayList<>();
this.environmentVariables = new HashMap<>();
this.workingDirectory = this.helmExecutable.getParent();

String workingDirectoryString = System.getenv("PWD");
this.workingDirectory = (workingDirectoryString == null || workingDirectoryString.isBlank())
? this.helmExecutable.getParent()
: new File(workingDirectoryString).toPath();
}

/**
Expand All @@ -100,12 +113,28 @@ public HelmExecutionBuilder subcommands(final String... commands) {
* @throws NullPointerException if either {@code name} or {@code value} is {@code null}.
*/
public HelmExecutionBuilder argument(final String name, final String value) {
Objects.requireNonNull(name, "name must not be null");
Objects.requireNonNull(value, "value must not be null");
Objects.requireNonNull(name, NAME_MUST_NOT_BE_NULL);
Objects.requireNonNull(value, VALUE_MUST_NOT_BE_NULL);
this.arguments.put(name, value);
return this;
}

/**
* Adds an option with a provided list of values to the helm command. This is used for options that have can have
* multiple values for a single option. (e.g. --set and --values)
*
* @param name the name of the option.
* @param value the list of values for the given option.
* @return this builder.
* @throws NullPointerException if either {@code name} or {@code value} is {@code null}.
*/
public HelmExecutionBuilder optionsWithMultipleValues(final String name, final List<String> value) {
Objects.requireNonNull(name, NAME_MUST_NOT_BE_NULL);
Objects.requireNonNull(value, VALUE_MUST_NOT_BE_NULL);
this.optionsWithMultipleValues.add(new KeyValuePair<>(name, value));
return this;
}

/**
* Adds a positional argument to the helm command.
*
Expand All @@ -114,7 +143,7 @@ public HelmExecutionBuilder argument(final String name, final String value) {
* @throws NullPointerException if {@code value} is {@code null}.
*/
public HelmExecutionBuilder positional(final String value) {
Objects.requireNonNull(value, "value must not be null");
Objects.requireNonNull(value, VALUE_MUST_NOT_BE_NULL);
this.positionals.add(value);
return this;
}
Expand All @@ -128,20 +157,20 @@ public HelmExecutionBuilder positional(final String value) {
* @throws NullPointerException if either {@code name} or {@code value} is {@code null}.
*/
public HelmExecutionBuilder environmentVariable(final String name, final String value) {
Objects.requireNonNull(name, "name must not be null");
Objects.requireNonNull(value, "value must not be null");
Objects.requireNonNull(name, NAME_MUST_NOT_BE_NULL);
Objects.requireNonNull(value, VALUE_MUST_NOT_BE_NULL);
this.environmentVariables.put(name, value);
return this;
}

/**
* Sets the working directory for the helm process.
* Sets the Path of the working directory for the helm process.
*
* @param workingDirectory the working directory.
* @param workingDirectoryPath the Path of the working directory.
* @return this builder.
*/
public HelmExecutionBuilder workingDirectory(final Path workingDirectory) {
this.workingDirectory = Objects.requireNonNull(workingDirectory, "workingDirectory must not be null");
public HelmExecutionBuilder workingDirectory(final Path workingDirectoryPath) {
this.workingDirectory = Objects.requireNonNull(workingDirectoryPath, "workingDirectoryPath must not be null");
return this;
}

Expand Down Expand Up @@ -191,18 +220,23 @@ private String[] buildCommand() {
command.addAll(subcommands);
command.addAll(flags);

for (final Map.Entry<String, String> entry : arguments.entrySet()) {
command.add(String.format("--%s", entry.getKey()));
command.add(entry.getValue());
}
arguments.forEach((key, value) -> {
command.add(String.format("--%s", key));
command.add(value);
});

optionsWithMultipleValues.forEach(entry -> entry.value().forEach(value -> {
command.add(String.format("--%s", entry.key()));
command.add(value);
}));

command.addAll(positionals);

String[] commandArray = command.toArray(new String[0]);
LOGGER.atDebug()
.setMessage("Helm command: {}")
.addArgument(String.join(" ", Arrays.copyOfRange(commandArray, 1, commandArray.length)))
.log();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Helm command: {}", String.join(" ", Arrays.copyOfRange(commandArray, 1, commandArray.length)));
}

return commandArray;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,12 @@

package com.hedera.fullstack.helm.client.model;

import java.util.Objects;

/**
* Represents a chart and is used to interact with the Helm install and uninstall commands.
* @param repoName the name of repository which contains the Helm chart.
* @param name the name of the Helm chart.
*/
public record Chart(String name, String repoName) {

public Chart {
Objects.requireNonNull(repoName, "repoName must not be null");
}

public Chart(String name) {
this(name, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.hedera.fullstack.helm.client.execution.HelmExecutionBuilder;
import com.hedera.fullstack.helm.client.model.Options;
import java.util.List;

/**
* The options to be supplied to the helm install command.
Expand All @@ -32,6 +33,7 @@
* @param passCredentials - pass credentials to all domains.
* @param password - chart repository password where to locate the requested chart.
* @param repo - chart repository url where to locate the requested chart.
* @param set - set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
* @param skipCrds - if set, no CRDs will be installed. By default, CRDs are installed if not already present.
* @param timeout - time to wait for any individual Kubernetes operation (like Jobs for hooks) (default 5m0s).
* @param username - chart repository username where to locate the requested chart.
Expand All @@ -54,10 +56,11 @@ public record InstallChartOptions(
boolean passCredentials,
String password,
String repo,
List<String> set,
boolean skipCrds,
String timeout,
String username,
String values,
List<String> values,
boolean verify,
String version,
boolean waitFor)
Expand Down Expand Up @@ -94,6 +97,10 @@ public void apply(final HelmExecutionBuilder builder) {
builder.argument("repo", repo());
}

if (set() != null) {
builder.optionsWithMultipleValues("set", set());
}

if (timeout() != null) {
builder.argument("timeout", timeout());
}
Expand All @@ -103,7 +110,7 @@ public void apply(final HelmExecutionBuilder builder) {
}

if (values() != null) {
builder.argument("values", values());
builder.optionsWithMultipleValues("values", values());
}

if (version() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.hedera.fullstack.helm.client.model.install;

import java.util.List;

/**
* The builder for the {@link InstallChartOptions}.
*/
Expand All @@ -29,10 +31,11 @@ public final class InstallChartOptionsBuilder {
private boolean passCredentials;
private String password;
private String repo;
private List<String> set;
private boolean skipCrds;
private String timeout;
private String username;
private String values;
private List<String> values;
private boolean verify;
private String version;
private boolean waitFor;
Expand Down Expand Up @@ -149,6 +152,17 @@ public InstallChartOptionsBuilder repo(String repo) {
return this;
}

/**
* set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
*
* @param valueOverride set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
* @return the current InstallChartOptionsBuilder.
*/
public InstallChartOptionsBuilder set(List<String> valueOverride) {
this.set = valueOverride;
return this;
}

/**
* if set, no CRDs will be installed. By default, CRDs are installed if not already present.
*
Expand Down Expand Up @@ -188,7 +202,7 @@ public InstallChartOptionsBuilder username(String username) {
* @param values specify values in a YAML file or a URL (can specify multiple).
* @return the current InstallChartOptionsBuilder.
*/
public InstallChartOptionsBuilder values(String values) {
public InstallChartOptionsBuilder values(List<String> values) {
this.values = values;
return this;
}
Expand Down Expand Up @@ -247,6 +261,7 @@ public InstallChartOptions build() {
passCredentials,
password,
repo,
set,
skipCrds,
timeout,
username,
Expand Down
Loading

0 comments on commit 05444df

Please sign in to comment.