From 69413b7e848d5c65e155683328bb4eca34c79451 Mon Sep 17 00:00:00 2001 From: Antonio Fernandez Alhambra Date: Mon, 1 Dec 2025 16:50:11 +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 6092a4e..658df1f 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; @@ -53,6 +54,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; @@ -74,6 +76,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<>(); } @@ -96,7 +99,8 @@ public Release call() { dryRunOption == null ? null : dryRunOption.name().toLowerCase(Locale.ROOT), toInt(wait), timeout, - urlEncode(values), + urlEncode(values, Function.identity()), + urlEncode(setFiles, HelmCommand::toString), toString(valuesFiles), toString(kubeConfig), kubeConfigContents, @@ -302,6 +306,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 678ef87..e517dd6 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 @@ -37,6 +38,7 @@ public class TemplateCommand extends HelmCommand { private String namespace; private boolean dependencyUpdate; private final Map values; + private final Map setFiles; private final List valuesFiles; private Path certFile; private Path keyFile; @@ -55,6 +57,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<>(); } @@ -66,7 +69,8 @@ public String call() { chart, namespace, toInt(dependencyUpdate), - urlEncode(values), + urlEncode(values, Function.identity()), + urlEncode(setFiles, HelmCommand::toString), toString(valuesFiles), toString(certFile), toString(keyFile), @@ -149,6 +153,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 21c8ccf..bd50eee 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; @@ -57,6 +58,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; @@ -78,6 +80,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<>(); } @@ -104,7 +107,8 @@ public Release call() { dryRunOption == null ? null : dryRunOption.name().toLowerCase(Locale.ROOT), toInt(wait), timeout, - urlEncode(values), + urlEncode(values, Function.identity()), + urlEncode(setFiles, HelmCommand::toString), toString(valuesFiles), toString(kubeConfig), kubeConfigContents, @@ -354,6 +358,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 b02bb73..903902b 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmInstallTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmInstallTest.java @@ -216,6 +216,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 ee7853c..fa624cd 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmKubernetesTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmKubernetesTest.java @@ -555,6 +555,24 @@ void withWaitAndCustomTimeout() { "beginning wait for 3 resources with timeout of 5m30s" ); } + + @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 adde5cd..c6be0f0 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmTemplateTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmTemplateTest.java @@ -107,6 +107,17 @@ void withInvalidValuesAndDebug() { .hasMessageContaining("# Source: local-chart-test") .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"); + } } @Nested 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 70e0b02..8c90da8 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 @@ -42,6 +42,7 @@ "wait", "timeout", "values", + "setFiles", "valuesFiles", "kubeConfig", "kubeConfigContents", @@ -74,6 +75,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; @@ -105,6 +107,7 @@ public InstallOptions( int wait, int timeout, String values, + String setFiles, String valuesFiles, String kubeConfig, String kubeConfigContents, @@ -135,6 +138,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 a7206ac..f37793a 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 @@ -29,6 +29,7 @@ "namespace", "dependencyUpdate", "values", + "setFiles", "valuesFiles", "certFile", "keyFile", @@ -46,6 +47,7 @@ public class TemplateOptions extends Structure { public String namespace; public int dependencyUpdate; public String values; + public String setFiles; public String valuesFiles; public String certFile; public String keyFile; @@ -63,6 +65,7 @@ public TemplateOptions( String namespace, int dependencyUpdate, String values, + String setFiles, String valuesFiles, String certFile, String keyFile, @@ -79,6 +82,7 @@ public TemplateOptions( this.namespace = namespace; this.dependencyUpdate = dependencyUpdate; 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 b6743f8..9f0c5c3 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 @@ -46,6 +46,7 @@ "wait", "timeout", "values", + "setFiles", "valuesFiles", "kubeConfig", "kubeConfigContents", @@ -81,6 +82,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; @@ -116,6 +118,7 @@ public UpgradeOptions( int wait, int timeout, String values, + String setFiles, String valuesFiles, String kubeConfig, String kubeConfigContents, @@ -150,6 +153,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 f5fcd45..6b42115 100644 --- a/native/internal/helm/install.go +++ b/native/internal/helm/install.go @@ -56,6 +56,7 @@ type InstallOptions struct { Wait bool Timeout time.Duration Values string + SetFiles string ValuesFiles string KubeConfig string KubeConfigContents string @@ -161,7 +162,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 } @@ -294,11 +295,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, ",") { @@ -307,6 +312,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 d551d88..f35fd0f 100644 --- a/native/internal/helm/template.go +++ b/native/internal/helm/template.go @@ -30,6 +30,7 @@ type TemplateOptions struct { Namespace string DependencyUpdate bool Values string + SetFiles string ValuesFiles string Debug bool RepositoryConfig string @@ -52,6 +53,7 @@ func Template(options *TemplateOptions) (string, error) { Namespace: options.Namespace, DependencyUpdate: options.DependencyUpdate, 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 a918ab9..392e80b 100644 --- a/native/internal/helm/upgrade.go +++ b/native/internal/helm/upgrade.go @@ -47,6 +47,7 @@ type UpgradeOptions struct { Wait bool Timeout time.Duration Values string + SetFiles string ValuesFiles string KubeConfig string KubeConfigContents string @@ -105,6 +106,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, @@ -162,7 +165,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 0027dfa..5cd2ff9 100644 --- a/native/main.go +++ b/native/main.go @@ -58,6 +58,7 @@ struct InstallOptions { int wait; int timeout; char* values; + char* setFiles; char* valuesFiles; char* kubeConfig; char* kubeConfigContents; @@ -170,6 +171,7 @@ struct TemplateOptions { char* namespace; int dependencyUpdate; char* values; + char* setFiles; char* valuesFiles; char* certFile; char* keyFile; @@ -225,6 +227,7 @@ struct UpgradeOptions { int wait; int timeout; char* values; + char* setFiles; char* valuesFiles; char* kubeConfig; char* kubeConfigContents; @@ -356,6 +359,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), @@ -608,6 +612,7 @@ func Template(options *C.struct_TemplateOptions) C.Result { Namespace: C.GoString(options.namespace), DependencyUpdate: options.dependencyUpdate == 1, Values: C.GoString(options.values), + SetFiles: C.GoString(options.setFiles), ValuesFiles: C.GoString(options.valuesFiles), CertOptions: helm.CertOptions{ CertFile: C.GoString(options.certFile), @@ -692,6 +697,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),