From 1f94df461cc9ff901b374a42113916377e17b839 Mon Sep 17 00:00:00 2001 From: Antonio Fernandez Alhambra Date: Fri, 6 Feb 2026 17:17:34 +0100 Subject: [PATCH] feat: add support for --set-file flag --- .../java/com/marcnuri/helm/HelmCommand.java | 6 +++--- .../com/marcnuri/helm/InstallCommand.java | 20 ++++++++++++++++++- .../com/marcnuri/helm/TemplateCommand.java | 20 ++++++++++++++++++- .../com/marcnuri/helm/UpgradeCommand.java | 20 ++++++++++++++++++- .../com/marcnuri/helm/HelmInstallTest.java | 18 +++++++++++++++++ .../com/marcnuri/helm/HelmKubernetesTest.java | 18 +++++++++++++++++ .../com/marcnuri/helm/HelmTemplateTest.java | 11 ++++++++++ .../com/marcnuri/helm/jni/InstallOptions.java | 4 ++++ .../marcnuri/helm/jni/TemplateOptions.java | 4 ++++ .../com/marcnuri/helm/jni/UpgradeOptions.java | 4 ++++ native/internal/helm/install.go | 10 ++++++++-- native/internal/helm/template.go | 2 ++ native/internal/helm/upgrade.go | 5 ++++- native/main.go | 6 ++++++ 14 files changed, 139 insertions(+), 9 deletions(-) diff --git a/helm-java/src/main/java/com/marcnuri/helm/HelmCommand.java b/helm-java/src/main/java/com/marcnuri/helm/HelmCommand.java index 6175278..fcfb306 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/HelmCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/HelmCommand.java @@ -55,16 +55,16 @@ Result run(Function function) { return result; } - static String urlEncode(Map values) { + static String urlEncode(Map entries, Function valueMapper) { final StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : values.entrySet()) { + for (Map.Entry entry : entries.entrySet()) { if (sb.length() > 0) { sb.append("&"); } try { sb.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name())) .append("=") - .append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name())); + .append(URLEncoder.encode(valueMapper.apply(entry.getValue()), StandardCharsets.UTF_8.name())); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("Invalid entry: " + entry.getKey() + "=" + entry.getValue(), e); } diff --git a/helm-java/src/main/java/com/marcnuri/helm/InstallCommand.java b/helm-java/src/main/java/com/marcnuri/helm/InstallCommand.java index c3c4a65..ced9efd 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/InstallCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/InstallCommand.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Function; import static com.marcnuri.helm.Release.parseSingle; @@ -56,6 +57,7 @@ public class InstallCommand extends HelmCommand { private boolean wait; private int timeout; private final Map values; + private final Map setFiles; private final List valuesFiles; private Path kubeConfig; private String kubeConfigContents; @@ -77,6 +79,7 @@ public InstallCommand(HelmLib helmLib, Path chart) { super(helmLib); this.chart = toString(chart); this.values = new LinkedHashMap<>(); + this.setFiles = new LinkedHashMap<>(); this.valuesFiles = new ArrayList<>(); } @@ -101,7 +104,8 @@ public Release call() { toInt(skipCrds), toInt(wait), timeout, - urlEncode(values), + urlEncode(values, Function.identity()), + urlEncode(setFiles, HelmCommand::toString), toString(valuesFiles), toString(kubeConfig), kubeConfigContents, @@ -335,6 +339,20 @@ public InstallCommand set(String key, Object value) { return this; } + /** + * Set a value for the chart by reading it from a file. + *

+ * The file contents will be used as the value for the specified key. + * + * @param key the key. + * @param file the path to the file containing the value. + * @return this {@link InstallCommand} instance. + */ + public InstallCommand setFile(String key, Path file) { + this.setFiles.put(key, file); + return this; + } + /** * Adds a values (YAML) file to source values for the chart (can specify multiple). * diff --git a/helm-java/src/main/java/com/marcnuri/helm/TemplateCommand.java b/helm-java/src/main/java/com/marcnuri/helm/TemplateCommand.java index b8f30bf..7ec43e7 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/TemplateCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/TemplateCommand.java @@ -24,6 +24,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; /** * @author Marc Nuri @@ -40,6 +41,7 @@ public class TemplateCommand extends HelmCommand { private boolean dependencyUpdate; private boolean skipCrds; private final Map values; + private final Map setFiles; private final List valuesFiles; private Path certFile; private Path keyFile; @@ -58,6 +60,7 @@ public TemplateCommand(HelmLib helmLib, Path chart) { super(helmLib); this.chart = toString(chart); this.values = new LinkedHashMap<>(); + this.setFiles = new LinkedHashMap<>(); this.valuesFiles = new ArrayList<>(); } @@ -71,7 +74,8 @@ public String call() { kubeVersion, toInt(dependencyUpdate), toInt(skipCrds), - urlEncode(values), + urlEncode(values, Function.identity()), + urlEncode(setFiles, HelmCommand::toString), toString(valuesFiles), toString(certFile), toString(keyFile), @@ -182,6 +186,20 @@ public TemplateCommand set(String key, Object value) { return this; } + /** + * Set a value for the chart by reading it from a file. + *

+ * The file contents will be used as the value for the specified key. + * + * @param key the key. + * @param file the path to the file containing the value. + * @return this {@link TemplateCommand} instance. + */ + public TemplateCommand setFile(String key, Path file) { + this.setFiles.put(key, file); + return this; + } + /** * Adds a values (YAML) file to source values for the chart (can specify multiple). * diff --git a/helm-java/src/main/java/com/marcnuri/helm/UpgradeCommand.java b/helm-java/src/main/java/com/marcnuri/helm/UpgradeCommand.java index dc38e84..13786b7 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/UpgradeCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/UpgradeCommand.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Function; import static com.marcnuri.helm.Release.parseSingle; @@ -60,6 +61,7 @@ public class UpgradeCommand extends HelmCommand { private boolean wait; private int timeout; private final Map values; + private final Map setFiles; private final List valuesFiles; private Path kubeConfig; private String kubeConfigContents; @@ -81,6 +83,7 @@ public UpgradeCommand(HelmLib helmLib, Path chart) { super(helmLib); this.chart = toString(chart); this.values = new LinkedHashMap<>(); + this.setFiles = new LinkedHashMap<>(); this.valuesFiles = new ArrayList<>(); } @@ -109,7 +112,8 @@ public Release call() { toInt(skipCrds), toInt(wait), timeout, - urlEncode(values), + urlEncode(values, Function.identity()), + urlEncode(setFiles, HelmCommand::toString), toString(valuesFiles), toString(kubeConfig), kubeConfigContents, @@ -387,6 +391,20 @@ public UpgradeCommand set(String key, Object value) { return this; } + /** + * Set a value for the chart by reading it from a file. + *

+ * The file contents will be used as the value for the specified key. + * + * @param key the key. + * @param file the path to the file containing the value. + * @return this {@link UpgradeCommand} instance. + */ + public UpgradeCommand setFile(String key, Path file) { + this.setFiles.put(key, file); + return this; + } + /** * Adds a values (YAML) file to source values for the chart (can specify multiple). * diff --git a/helm-java/src/test/java/com/marcnuri/helm/HelmInstallTest.java b/helm-java/src/test/java/com/marcnuri/helm/HelmInstallTest.java index d677103..acca563 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmInstallTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmInstallTest.java @@ -217,6 +217,24 @@ void withValuesFile() throws IOException { ); } + @Test + void withSetFile() throws IOException { + final Path configFile = Files.write(tempDir.resolve("config.txt"), + "foobar".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + final Release result = helm.install() + .clientOnly() + .debug() + .withName("test") + .setFile("configData", configFile) + .call(); + assertThat(result) + .extracting(Release::getOutput).asString() + .contains( + "NAME: test\n", + "configData: foobar" + ); + } + @Test void withDisableOpenApiValidation() { final Release result = helm.install() diff --git a/helm-java/src/test/java/com/marcnuri/helm/HelmKubernetesTest.java b/helm-java/src/test/java/com/marcnuri/helm/HelmKubernetesTest.java index a2ed6ae..0156f16 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmKubernetesTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmKubernetesTest.java @@ -646,6 +646,24 @@ void skipCrdsWithoutCrdsInChart() { .returns("2", Release::getRevision) .returns("deployed", Release::getStatus); } + + @Test + void withSetFile(@TempDir Path tempDir) throws IOException { + final Path configFile = Files.write(tempDir.resolve("upgrade-config.txt"), + "foobar".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + helm.install().withName("upgrade-with-set-file").withKubeConfig(kubeConfigFile).call(); + final Release result = helm.upgrade() + .withKubeConfig(kubeConfigFile) + .withName("upgrade-with-set-file") + .setFile("configData", configFile) + .debug() + .call(); + assertThat(result) + .returns("2", Release::getRevision) + .returns("deployed", Release::getStatus) + .extracting(Release::getOutput).asString() + .contains("configData: foobar"); + } } @Nested diff --git a/helm-java/src/test/java/com/marcnuri/helm/HelmTemplateTest.java b/helm-java/src/test/java/com/marcnuri/helm/HelmTemplateTest.java index fad53cf..32f87cc 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmTemplateTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmTemplateTest.java @@ -109,6 +109,17 @@ void withInvalidValuesAndDebug() { .hasMessageContaining("name: release-name-local-chart-test"); } + @Test + void withSetFile() throws IOException { + final Path replicaFile = Files.write(tempDir.resolve("replica-count.txt"), + "42".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + final String result = helm.template() + .setFile("replicaCount", replicaFile) + .call(); + assertThat(result) + .contains("replicas: 42"); + } + @Test void withKubeVersion() { final String result = helm.template() diff --git a/lib/api/src/main/java/com/marcnuri/helm/jni/InstallOptions.java b/lib/api/src/main/java/com/marcnuri/helm/jni/InstallOptions.java index 98942f7..034c56c 100644 --- a/lib/api/src/main/java/com/marcnuri/helm/jni/InstallOptions.java +++ b/lib/api/src/main/java/com/marcnuri/helm/jni/InstallOptions.java @@ -45,6 +45,7 @@ "wait", "timeout", "values", + "setFiles", "valuesFiles", "kubeConfig", "kubeConfigContents", @@ -79,6 +80,7 @@ public class InstallOptions extends Structure { public int wait; public int timeout; public String values; + public String setFiles; public String valuesFiles; public String kubeConfig; public String kubeConfigContents; @@ -112,6 +114,7 @@ public InstallOptions( int wait, int timeout, String values, + String setFiles, String valuesFiles, String kubeConfig, String kubeConfigContents, @@ -144,6 +147,7 @@ public InstallOptions( this.wait = wait; this.timeout = timeout; this.values = values; + this.setFiles = setFiles; this.valuesFiles = valuesFiles; this.kubeConfig = kubeConfig; this.kubeConfigContents = kubeConfigContents; diff --git a/lib/api/src/main/java/com/marcnuri/helm/jni/TemplateOptions.java b/lib/api/src/main/java/com/marcnuri/helm/jni/TemplateOptions.java index 1eec4f1..ce68fae 100644 --- a/lib/api/src/main/java/com/marcnuri/helm/jni/TemplateOptions.java +++ b/lib/api/src/main/java/com/marcnuri/helm/jni/TemplateOptions.java @@ -32,6 +32,7 @@ "dependencyUpdate", "skipCRDs", "values", + "setFiles", "valuesFiles", "certFile", "keyFile", @@ -51,6 +52,7 @@ public class TemplateOptions extends Structure { public int dependencyUpdate; public int skipCRDs; public String values; + public String setFiles; public String valuesFiles; public String certFile; public String keyFile; @@ -70,6 +72,7 @@ public TemplateOptions( int dependencyUpdate, int skipCRDs, String values, + String setFiles, String valuesFiles, String certFile, String keyFile, @@ -88,6 +91,7 @@ public TemplateOptions( this.dependencyUpdate = dependencyUpdate; this.skipCRDs = skipCRDs; this.values = values; + this.setFiles = setFiles; this.valuesFiles = valuesFiles; this.certFile = certFile; this.keyFile = keyFile; diff --git a/lib/api/src/main/java/com/marcnuri/helm/jni/UpgradeOptions.java b/lib/api/src/main/java/com/marcnuri/helm/jni/UpgradeOptions.java index 2133e48..948e288 100644 --- a/lib/api/src/main/java/com/marcnuri/helm/jni/UpgradeOptions.java +++ b/lib/api/src/main/java/com/marcnuri/helm/jni/UpgradeOptions.java @@ -49,6 +49,7 @@ "wait", "timeout", "values", + "setFiles", "valuesFiles", "kubeConfig", "kubeConfigContents", @@ -86,6 +87,7 @@ public class UpgradeOptions extends Structure { public int wait; public int timeout; public String values; + public String setFiles; public String valuesFiles; public String kubeConfig; public String kubeConfigContents; @@ -123,6 +125,7 @@ public UpgradeOptions( int wait, int timeout, String values, + String setFiles, String valuesFiles, String kubeConfig, String kubeConfigContents, @@ -159,6 +162,7 @@ public UpgradeOptions( this.wait = wait; this.timeout = timeout; this.values = values; + this.setFiles = setFiles; this.valuesFiles = valuesFiles; this.kubeConfig = kubeConfig; this.kubeConfigContents = kubeConfigContents; diff --git a/native/internal/helm/install.go b/native/internal/helm/install.go index 693f576..6cafe64 100644 --- a/native/internal/helm/install.go +++ b/native/internal/helm/install.go @@ -59,6 +59,7 @@ type InstallOptions struct { Wait bool Timeout time.Duration Values string + SetFiles string ValuesFiles string KubeConfig string KubeConfigContents string @@ -171,7 +172,7 @@ func install(options *InstallOptions) (*release.Release, *installOutputs, error) return nil, outputs, invalidDryRun } // Values - vals, err := mergeValues(options.Values, options.ValuesFiles) + vals, err := mergeValues(options.Values, options.SetFiles, options.ValuesFiles) if err != nil { return nil, outputs, err } @@ -304,11 +305,15 @@ func parseValuesSet(values string) ([]string, error) { } // mergeValues returns a map[string]interface{} with the provided processed values -func mergeValues(encodedValuesMap, encodedValuesFiles string) (map[string]interface{}, error) { +func mergeValues(encodedValuesMap, encodedSetFiles, encodedValuesFiles string) (map[string]interface{}, error) { valuesSet, err := parseValuesSet(encodedValuesMap) if err != nil { return nil, err } + setFiles, err := parseValuesSet(encodedSetFiles) + if err != nil { + return nil, err + } valueFiles := make([]string, 0) if encodedValuesFiles != "" { for _, valuesFile := range strings.Split(encodedValuesFiles, ",") { @@ -317,6 +322,7 @@ func mergeValues(encodedValuesMap, encodedValuesFiles string) (map[string]interf } return (&values.Options{ Values: valuesSet, + FileValues: setFiles, ValueFiles: valueFiles, }).MergeValues(make(getter.Providers, 0)) } diff --git a/native/internal/helm/template.go b/native/internal/helm/template.go index d372857..57758b6 100644 --- a/native/internal/helm/template.go +++ b/native/internal/helm/template.go @@ -32,6 +32,7 @@ type TemplateOptions struct { DependencyUpdate bool SkipCRDs bool Values string + SetFiles string ValuesFiles string Debug bool RepositoryConfig string @@ -56,6 +57,7 @@ func Template(options *TemplateOptions) (string, error) { DependencyUpdate: options.DependencyUpdate, SkipCRDs: options.SkipCRDs, Values: options.Values, + SetFiles: options.SetFiles, ValuesFiles: options.ValuesFiles, Debug: options.Debug, RepositoryConfig: options.RepositoryConfig, diff --git a/native/internal/helm/upgrade.go b/native/internal/helm/upgrade.go index c2ad450..1bb328d 100644 --- a/native/internal/helm/upgrade.go +++ b/native/internal/helm/upgrade.go @@ -49,6 +49,7 @@ type UpgradeOptions struct { Wait bool Timeout time.Duration Values string + SetFiles string ValuesFiles string KubeConfig string KubeConfigContents string @@ -109,6 +110,8 @@ func Upgrade(options *UpgradeOptions) (string, error) { Wait: options.Wait, Timeout: options.Timeout, Values: options.Values, + SetFiles: options.SetFiles, + ValuesFiles: options.ValuesFiles, KubeConfig: options.KubeConfig, CertOptions: options.CertOptions, Debug: options.Debug, @@ -167,7 +170,7 @@ func Upgrade(options *UpgradeOptions) (string, error) { } ctx := context.Background() // Values - vals, err := mergeValues(options.Values, options.ValuesFiles) + vals, err := mergeValues(options.Values, options.SetFiles, options.ValuesFiles) if err != nil { return "", err } diff --git a/native/main.go b/native/main.go index 6912eee..9e16111 100644 --- a/native/main.go +++ b/native/main.go @@ -60,6 +60,7 @@ struct InstallOptions { int wait; int timeout; char* values; + char* setFiles; char* valuesFiles; char* kubeConfig; char* kubeConfigContents; @@ -174,6 +175,7 @@ struct TemplateOptions { int dependencyUpdate; int skipCRDs; char* values; + char* setFiles; char* valuesFiles; char* certFile; char* keyFile; @@ -231,6 +233,7 @@ struct UpgradeOptions { int wait; int timeout; char* values; + char* setFiles; char* valuesFiles; char* kubeConfig; char* kubeConfigContents; @@ -364,6 +367,7 @@ func Install(options *C.struct_InstallOptions) C.Result { Wait: options.wait == 1, Timeout: timeout, Values: C.GoString(options.values), + SetFiles: C.GoString(options.setFiles), ValuesFiles: C.GoString(options.valuesFiles), KubeConfig: C.GoString(options.kubeConfig), KubeConfigContents: C.GoString(options.kubeConfigContents), @@ -618,6 +622,7 @@ func Template(options *C.struct_TemplateOptions) C.Result { DependencyUpdate: options.dependencyUpdate == 1, SkipCRDs: options.skipCRDs == 1, Values: C.GoString(options.values), + SetFiles: C.GoString(options.setFiles), ValuesFiles: C.GoString(options.valuesFiles), CertOptions: helm.CertOptions{ CertFile: C.GoString(options.certFile), @@ -704,6 +709,7 @@ func Upgrade(options *C.struct_UpgradeOptions) C.Result { Wait: options.wait == 1, Timeout: timeout, Values: C.GoString(options.values), + SetFiles: C.GoString(options.setFiles), ValuesFiles: C.GoString(options.valuesFiles), KubeConfig: C.GoString(options.kubeConfig), KubeConfigContents: C.GoString(options.kubeConfigContents),