diff --git a/README.md b/README.md index db99534..9a1d3bf 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,8 @@ Release result = installCommand .withTimeout(int timeout) // Optionally set typed values for the chart (can be repeated) .set("key", "value") + // Optionally set a values file to source values for the chart + .withValuesFile(Paths.get("path", "to", "valuesFile")) // Optionally specify the path to the kubeconfig file to use for CLI requests .withKubeConfig(Paths.get("path", "to", "kubeconfig")) // Optionally specify an SSL certificate file to identify the registry client 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 7aa4330..649f800 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/InstallCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/InstallCommand.java @@ -45,6 +45,7 @@ public class InstallCommand extends HelmCommand { private boolean wait; private int timeout; private final Map values; + private Path valuesFile; private Path kubeConfig; private Path certFile; private Path keyFile; @@ -86,6 +87,7 @@ public Release call() { toInt(wait), timeout, urlEncode(values), + toString(valuesFile), toString(kubeConfig), toString(certFile), toString(keyFile), @@ -289,6 +291,17 @@ public InstallCommand set(String key, Object value) { return this; } + /** + * Set values file for the chart. + * + * @param valuesFile the path to a values file + * @return this {@link InstallCommand} instance. + */ + public InstallCommand withValuesFile(Path valuesFile) { + this.valuesFile = valuesFile; + return this; + } + /** * Set the path ./kube/config file to use. * 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 233fc42..c9d765d 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/UpgradeCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/UpgradeCommand.java @@ -49,6 +49,7 @@ public class UpgradeCommand extends HelmCommand { private boolean wait; private int timeout; private final Map values; + private Path valuesFile; private Path kubeConfig; private Path certFile; private Path keyFile; @@ -94,6 +95,7 @@ public Release call() { toInt(wait), timeout, urlEncode(values), + toString(valuesFile), toString(kubeConfig), toString(certFile), toString(keyFile), @@ -341,6 +343,17 @@ public UpgradeCommand set(String key, Object value) { return this; } + /** + * Set values file for the chart. + * + * @param valuesFile the path to a values file + * @return this {@link InstallCommand} instance. + */ + public UpgradeCommand withValuesFile(Path valuesFile) { + this.valuesFile = valuesFile; + return this; + } + /** * Set the path ./kube/config file to use. * 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 2cbdef2..5323244 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmInstallTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmInstallTest.java @@ -184,6 +184,31 @@ void withValues() { ); } + @Test + void withValuesFile(@TempDir Path tempDir) throws IOException { + final Path valuesFile = Files.write(tempDir.resolve("newValues.yaml"), + ( + "nix: baz\n" + + "foo: bar" + ).getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE); + + final Release result = helm.install() + .clientOnly() + .debug() + .withName("test") + .set("foo", "notBar") // set values override values file. + .withValuesFile(valuesFile) + .call(); + assertThat(result) + .extracting(Release::getOutput).asString() + .contains( + "NAME: test\n", + "foo: notBar", + "nix: baz" + ); + } + @Test void withDisableOpenApiValidation() { final Release result = helm.install() 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 8689179..99b78b3 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 @@ -36,6 +36,7 @@ "wait", "timeout", "values", + "valuesFile", "kubeConfig", "certFile", "keyFile", @@ -66,6 +67,7 @@ public class InstallOptions extends Structure { public int wait; public int timeout; public String values; + public String valuesFile; public String kubeConfig; public String certFile; public String keyFile; @@ -95,6 +97,7 @@ public InstallOptions( int wait, int timeout, String values, + String valuesFile, String kubeConfig, String certFile, String keyFile, @@ -123,6 +126,7 @@ public InstallOptions( this.wait = wait; this.timeout = timeout; this.values = values; + this.valuesFile = valuesFile; this.kubeConfig = kubeConfig; 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 4492611..a0cc299 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 @@ -40,6 +40,7 @@ "wait", "timeout", "values", + "valuesFile", "kubeConfig", "certFile", "keyFile", @@ -73,6 +74,7 @@ public class UpgradeOptions extends Structure { public int wait; public int timeout; public String values; + public String valuesFile; public String kubeConfig; public String certFile; public String keyFile; @@ -106,6 +108,7 @@ public UpgradeOptions( int wait, int timeout, String values, + String valuesFile, String kubeConfig, String certFile, String keyFile, @@ -138,6 +141,7 @@ public UpgradeOptions( this.wait = wait; this.timeout = timeout; this.values = values; + this.valuesFile = valuesFile; this.kubeConfig = kubeConfig; this.certFile = certFile; this.keyFile = keyFile; diff --git a/native/internal/helm/install.go b/native/internal/helm/install.go index 69d0a16..d1497e2 100644 --- a/native/internal/helm/install.go +++ b/native/internal/helm/install.go @@ -24,9 +24,11 @@ import ( "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/strvals" + "maps" "net/url" "os" "os/signal" @@ -54,6 +56,7 @@ type InstallOptions struct { Wait bool Timeout time.Duration Values string + ValuesFile string KubeConfig string Debug bool // For testing purposes only, prevents connecting to Kubernetes (happens even with DryRun=true and DryRunOption=client) @@ -152,10 +155,21 @@ func install(options *InstallOptions) (*release.Release, *installOutputs, error) return nil, outputs, invalidDryRun } // Values - var values, invalidValues = parseValues(options.Values) + values := make(map[string]interface{}) + // Values from values file + if options.ValuesFile != "" { + fileValues, err := chartutil.ReadValuesFile(options.ValuesFile) + if err != nil { + return nil, outputs, err + } + maps.Copy(values, fileValues) + } + // Values from set values + var paramValues, invalidValues = parseValues(options.Values) if invalidValues != nil { return nil, outputs, invalidValues } + maps.Copy(values, paramValues) // Create context that handles SIGINT, SIGTERM ctx := context.Background() diff --git a/native/internal/helm/install_test.go b/native/internal/helm/install_test.go index dc7f75d..0695a95 100644 --- a/native/internal/helm/install_test.go +++ b/native/internal/helm/install_test.go @@ -119,6 +119,33 @@ func TestInstallValues(t *testing.T) { } } + +func TestInstallWithValuesFile(t *testing.T) { + tmpDir := t.TempDir() + create, _ := Create(&CreateOptions{ + Name: "test", + Dir: tmpDir, + }) + valuesFilePath := path.Join(tmpDir, "valuesFile.yaml") + valuesBytes := []byte("nix: baz\n") + err := os.WriteFile(valuesFilePath, valuesBytes, 0666) + out, err := Install(&InstallOptions{ + Chart: create, + Name: "test", + ValuesFile: valuesFilePath, + Debug: true, + ClientOnly: true, + }) + if err != nil { + t.Errorf("Expected install to succeed, got %s", err) + return + } + if !strings.Contains(out, "USER-SUPPLIED VALUES:") || !strings.Contains(out, "nix: baz") { + t.Errorf("Expected install to contain specific values from valuesFile, got %s", out) + return + } +} + func TestInstallDependencyUpdate(t *testing.T) { chart, _ := Create(&CreateOptions{ Name: "test", diff --git a/native/internal/helm/upgrade.go b/native/internal/helm/upgrade.go index f4786b7..43b08e2 100644 --- a/native/internal/helm/upgrade.go +++ b/native/internal/helm/upgrade.go @@ -20,7 +20,9 @@ import ( "bytes" "context" "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/storage/driver" + "maps" "time" ) @@ -47,6 +49,7 @@ type UpgradeOptions struct { Wait bool Timeout time.Duration Values string + ValuesFile string KubeConfig string Debug bool // For testing purposes only, prevents connecting to Kubernetes (happens even with DryRun=true and DryRunOption=client) @@ -156,10 +159,21 @@ func Upgrade(options *UpgradeOptions) (string, error) { } ctx := context.Background() // Values - var values, invalidValues = parseValues(options.Values) + values := make(map[string]interface{}) + // values from values file + if options.ValuesFile != "" { + fileValues, err := chartutil.ReadValuesFile(options.ValuesFile) + if err != nil { + return "", err + } + maps.Copy(values, fileValues) + } + // Values from set values + var paramValues, invalidValues = parseValues(options.Values) if invalidValues != nil { return "", invalidValues } + maps.Copy(values, paramValues) // Run release, err := client.RunWithContext(ctx, options.Name, chartRequested, values) // Generate report diff --git a/native/main.go b/native/main.go index 01d316e..e22558e 100644 --- a/native/main.go +++ b/native/main.go @@ -58,6 +58,7 @@ struct InstallOptions { int wait; int timeout; char* values; + char* valuesFile; char* kubeConfig; char* certFile; char* keyFile; @@ -219,6 +220,7 @@ struct UpgradeOptions { int wait; int timeout; char* values; + char* valuesFile; char* kubeConfig; char* certFile; char* keyFile; @@ -348,6 +350,7 @@ func Install(options *C.struct_InstallOptions) C.Result { Wait: options.wait == 1, Timeout: timeout, Values: C.GoString(options.values), + ValuesFile: C.GoString(options.valuesFile), KubeConfig: C.GoString(options.kubeConfig), CertOptions: helm.CertOptions{ CertFile: C.GoString(options.certFile), @@ -678,6 +681,7 @@ func Upgrade(options *C.struct_UpgradeOptions) C.Result { Wait: options.wait == 1, Timeout: timeout, Values: C.GoString(options.values), + ValuesFile: C.GoString(options.valuesFile), KubeConfig: C.GoString(options.kubeConfig), CertOptions: helm.CertOptions{ CertFile: C.GoString(options.certFile),