diff --git a/README.md b/README.md index d8100929..5db4a7cf 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ Release result = installCommand .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 set the contents of the kubeconfig file as a string (takes precedence over the path) + .withKubeConfigContents("apiVersion: v1\nkind: Config\nclusters:\n...") // Optionally specify an SSL certificate file to identify the registry client .withCertFile(Paths.get("path", "to", "cert")) // Optionally specify an SSL key file to identify the registry client @@ -199,6 +201,8 @@ List releases = Helm.list() .withNamespace("namespace") // Optionally specify the path to the kubeconfig file to use for CLI requests .withKubeConfig(Paths.get("path", "to", "kubeconfig")) + // Optionally set the contents of the kubeconfig file as a string (takes precedence over the path) + .withKubeConfigContents("apiVersion: v1\nkind: Config\nclusters:\n...") // Optionally show all releases without any filter applied .all() // Optionally show releases across all namespaces @@ -628,6 +632,8 @@ Release result = Helm.test("chart/reference") .withNamespace("namespace") // Optionally specify the path to the kubeconfig file to use for CLI requests .withKubeConfig(Paths.get("path", "to", "kubeconfig")) + // Optionally set the contents of the kubeconfig file as a string (takes precedence over the path) + .withKubeConfigContents("apiVersion: v1\nkind: Config\nclusters:\n...") // Optionally enable verbose output .debug() .call(); @@ -655,6 +661,8 @@ String result = Helm.uninstall("chart/reference") .withNamespace("namespace") // Optionally specify the path to the kubeconfig file to use for CLI requests .withKubeConfig(Paths.get("path", "to", "kubeconfig")) + // Optionally set the contents of the kubeconfig file as a string (takes precedence over the path) + .withKubeConfigContents("apiVersion: v1\nkind: Config\nclusters:\n...") // Optionally enable verbose output .debug() .call(); @@ -721,6 +729,8 @@ Release result = upgradeCommand .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 set the contents of the kubeconfig file as a string (takes precedence over the path) + .withKubeConfigContents("apiVersion: v1\nkind: Config\nclusters:\n...") // Optionally specify an SSL certificate file to identify the registry client .withCertFile(Paths.get("path", "to", "cert")) // Optionally specify an SSL key 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 233c58eb..6092a4e5 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/InstallCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/InstallCommand.java @@ -32,6 +32,7 @@ * @author Marc Nuri * @author Miriam Schmidt * @author Kevin J. Mckernan + * @author Christian Gebhard */ public class InstallCommand extends HelmCommand { @@ -54,6 +55,7 @@ public class InstallCommand extends HelmCommand { private final Map values; private final List valuesFiles; private Path kubeConfig; + private String kubeConfigContents; private Path certFile; private Path keyFile; private Path caFile; @@ -97,6 +99,7 @@ public Release call() { urlEncode(values), toString(valuesFiles), toString(kubeConfig), + kubeConfigContents, toString(certFile), toString(keyFile), toString(caFile), @@ -321,6 +324,17 @@ public InstallCommand withKubeConfig(Path kubeConfig) { return this; } + /** + * Set the kube config to use + * + * @param kubeConfigContents the contents of the kube config file. + * @return this {@link TestCommand} instance. + */ + public InstallCommand withKubeConfigContents(String kubeConfigContents) { + this.kubeConfigContents = kubeConfigContents; + return this; + } + /** * Identify registry client using this SSL certificate file. * diff --git a/helm-java/src/main/java/com/marcnuri/helm/ListCommand.java b/helm-java/src/main/java/com/marcnuri/helm/ListCommand.java index 4c26710f..c3e07afb 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/ListCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/ListCommand.java @@ -26,6 +26,7 @@ /** * @author Marc Nuri + * @author Christian Gebhard */ public class ListCommand extends HelmCommand> { @@ -39,6 +40,7 @@ public class ListCommand extends HelmCommand> { private boolean uninstalling; private String namespace; private Path kubeConfig; + private String kubeConfigContents; public ListCommand(HelmLib helmLib) { super(helmLib); @@ -56,7 +58,8 @@ public List call() { toInt(uninstalled), toInt(uninstalling), namespace, - toString(kubeConfig) + toString(kubeConfig), + kubeConfigContents )))); } @@ -161,4 +164,15 @@ public ListCommand withKubeConfig(Path kubeConfig) { this.kubeConfig = kubeConfig; return this; } + + /** + * Set the kube config to use + * + * @param kubeConfigContents the contents of the kube config file. + * @return this {@link ListCommand} instance. + */ + public ListCommand withKubeConfigContents(String kubeConfigContents) { + this.kubeConfigContents = kubeConfigContents; + return this; + } } diff --git a/helm-java/src/main/java/com/marcnuri/helm/TestCommand.java b/helm-java/src/main/java/com/marcnuri/helm/TestCommand.java index efc81a7d..172e8e0c 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/TestCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/TestCommand.java @@ -25,6 +25,7 @@ /** * @author Marc Nuri + * @author Christian Gebhard */ public class TestCommand extends HelmCommand { @@ -32,6 +33,7 @@ public class TestCommand extends HelmCommand { private int timeout; private String namespace; private Path kubeConfig; + private String kubeConfigContents; private boolean debug; public TestCommand(HelmLib helmLib, String releaseName) { @@ -46,6 +48,7 @@ public Release call() { timeout, namespace, toString(kubeConfig), + kubeConfigContents, toInt(debug) )))); } @@ -83,6 +86,17 @@ public TestCommand withKubeConfig(Path kubeConfig) { return this; } + /** + * Set the kube config to use + * + * @param kubeConfigContents the contents of the kube config file. + * @return this {@link TestCommand} instance. + */ + public TestCommand withKubeConfigContents(String kubeConfigContents) { + this.kubeConfigContents = kubeConfigContents; + return this; + } + /** * Enable verbose output. *

diff --git a/helm-java/src/main/java/com/marcnuri/helm/UninstallCommand.java b/helm-java/src/main/java/com/marcnuri/helm/UninstallCommand.java index 124ff8d7..8d0c9720 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/UninstallCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/UninstallCommand.java @@ -24,6 +24,7 @@ /** * @author Marc Nuri + * @author Christian Gebhard */ public class UninstallCommand extends HelmCommand { @@ -39,6 +40,7 @@ public enum Cascade { private Cascade cascade; private String namespace; private Path kubeConfig; + private String kubeConfigContents; private boolean debug; public UninstallCommand(HelmLib helmLib, String releaseName) { @@ -57,6 +59,7 @@ public String call() { cascade == null ? null : cascade.name().toLowerCase(Locale.ROOT), namespace, toString(kubeConfig), + kubeConfigContents, toInt(debug) ))).out; } @@ -135,6 +138,17 @@ public UninstallCommand withKubeConfig(Path kubeConfig) { return this; } + /** + * Set the kube config to use + * + * @param kubeConfigContents the contents of the kube config file. + * @return this {@link UninstallCommand} instance. + */ + public UninstallCommand withKubeConfigContents(String kubeConfigContents) { + this.kubeConfigContents = kubeConfigContents; + return this; + } + /** * Enable verbose output. *

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 59d2b806..21c8ccff 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/UpgradeCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/UpgradeCommand.java @@ -32,6 +32,7 @@ * @author Marc Nuri * @author Miriam Schmidt * @author Kevin J. Mckernan + * @author Christian Gebhard */ public class UpgradeCommand extends HelmCommand { @@ -58,6 +59,7 @@ public class UpgradeCommand extends HelmCommand { private final Map values; private final List valuesFiles; private Path kubeConfig; + private String kubeConfigContents; private Path certFile; private Path keyFile; private Path caFile; @@ -105,6 +107,7 @@ public Release call() { urlEncode(values), toString(valuesFiles), toString(kubeConfig), + kubeConfigContents, toString(certFile), toString(keyFile), toString(caFile), @@ -373,6 +376,17 @@ public UpgradeCommand withKubeConfig(Path kubeConfig) { return this; } + /** + * Set the kube config to use + * + * @param kubeConfigContents the contents of the kube config file. + * @return this {@link UpgradeCommand} instance. + */ + public UpgradeCommand withKubeConfigContents(String kubeConfigContents) { + this.kubeConfigContents = kubeConfigContents; + return this; + } + /** * Identify registry client using this SSL certificate file. * 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 07742e2b..ee7853cb 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmKubernetesTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmKubernetesTest.java @@ -47,7 +47,8 @@ class HelmKubernetesTest { static KindContainer kindContainer; - static Path kubeConfig; + static String kubeConfigContents; + static Path kubeConfigFile; private Helm helm; @@ -55,8 +56,9 @@ class HelmKubernetesTest { static void setUpKubernetes(@TempDir Path tempDir) throws IOException { kindContainer = new KindContainer<>(KindContainerVersion.VERSION_1_31_0); kindContainer.start(); - kubeConfig = tempDir.resolve("config.yaml"); - Files.write(kubeConfig, kindContainer.getKubeconfig().getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + kubeConfigContents = kindContainer.getKubeconfig(); + kubeConfigFile = tempDir.resolve("config.yaml"); + Files.write(kubeConfigFile,kubeConfigContents.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); } @AfterAll @@ -77,7 +79,7 @@ class Valid { @Test void withName() { final Release result = helm.install() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-install-with-name") .call(); assertThat(result) @@ -93,7 +95,7 @@ void withName() { @Test void withDebug() { final Release result = helm.install() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-install-with-with-debug") .debug() .call(); @@ -109,7 +111,7 @@ void withDebug() { @Test void withWaitReady() { final Release result = helm.install() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-install-with-wait-ready") .set("fullnameOverride", "helm-install-with-wait-ready") .set("image.repository", "ghcr.io/linuxserver/nginx") @@ -127,7 +129,7 @@ void withWaitReady() { @Test void withWaitReadyAndCustomTimeout() { final Release result = helm.install() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-install-with-wait-ready-and-custom-timeout") .set("fullnameOverride", "helm-install-with-wait-ready-and-custom-timeout") .set("image.repository", "ghcr.io/linuxserver/nginx") @@ -146,7 +148,7 @@ void withWaitReadyAndCustomTimeout() { @Test void withNamespaceAndCreateNamespace() { final Release result = helm.install() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("created-namespace") .withNamespace("to-be-created") .createNamespace() @@ -170,7 +172,7 @@ class Invalid { @Test void missingNamespace() { final InstallCommand install = helm.install() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("missing-namespace") .withNamespace("non-existent"); assertThatThrownBy(install::call) @@ -181,7 +183,7 @@ void missingNamespace() { @Test void lowTimeout() { final InstallCommand installCommand = helm.install() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-install-with-wait-ready-and-low-timeout") .set("fullnameOverride", "helm-install-with-wait-ready-and-low-timeout") .set("image.repository", "ghcr.io/linuxserver/nginx") @@ -216,11 +218,11 @@ void setUp(@TempDir Path tempDir) throws IOException { @Test void withoutAtomic() { - InstallCommand installCommand = failingHelm.install().withKubeConfig(kubeConfig).withName("test-fail"); + InstallCommand installCommand = failingHelm.install().withKubeConfig(kubeConfigFile).withName("test-fail"); assertThatThrownBy(installCommand::call).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Pod \"test-fail-test-failing\" is invalid: spec.containers: Required value"); - assertThat(Helm.list().withKubeConfig(kubeConfig).call()).filteredOn(r -> r.getName().equals("test-fail")) + assertThat(Helm.list().withKubeConfig(kubeConfigFile).call()).filteredOn(r -> r.getName().equals("test-fail")) .singleElement() .returns("failed", Release::getStatus); } @@ -228,11 +230,11 @@ void withoutAtomic() { @Test void withAtomic() { InstallCommand atomicInstallCommand = - failingHelm.install().withKubeConfig(kubeConfig).withName("test-rollback").atomic(); + failingHelm.install().withKubeConfig(kubeConfigFile).withName("test-rollback").atomic(); assertThatThrownBy(atomicInstallCommand::call).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Pod \"test-rollback-test-failing\" is invalid: spec.containers: Required value"); - assertThat(Helm.list().withKubeConfig(kubeConfig).call()).filteredOn(r -> r.getName().equals("test-rollback")) + assertThat(Helm.list().withKubeConfig(kubeConfigFile).call()).filteredOn(r -> r.getName().equals("test-rollback")) .isEmpty(); } } @@ -243,20 +245,20 @@ class HelmList { @BeforeEach void setUp() { - helm.install().withKubeConfig(kubeConfig).withName("list-default").call(); - helm.install().withKubeConfig(kubeConfig) + helm.install().withKubeConfig(kubeConfigFile).withName("list-default").call(); + helm.install().withKubeConfig(kubeConfigFile) .withName("list-namespace").withNamespace("list-namespace").createNamespace().call(); } @AfterEach() void tearDown() { - Helm.uninstall("list-default").withKubeConfig(kubeConfig).call(); - Helm.uninstall("list-namespace").withKubeConfig(kubeConfig).withNamespace("list-namespace").call(); + Helm.uninstall("list-default").withKubeConfig(kubeConfigFile).call(); + Helm.uninstall("list-namespace").withKubeConfig(kubeConfigFile).withNamespace("list-namespace").call(); } @Test void listsCurrentNamespace() { - final List result = Helm.list().withKubeConfig(kubeConfig).call(); + final List result = Helm.list().withKubeConfig(kubeConfigFile).call(); assertThat(result) .filteredOn(r -> r.getName().equals("list-default")) .singleElement() @@ -271,9 +273,26 @@ void listsCurrentNamespace() { .matches(d -> d.toLocalDate().equals(LocalDate.now())); } + @Test + void listsCurrentNamespaceWithKubeConfigContents() { + final List result = Helm.list().withKubeConfigContents(kubeConfigContents).call(); + assertThat(result) + .filteredOn(r -> r.getName().equals("list-default")) + .singleElement() + .returns("list-default", Release::getName) + .returns(null, Release::getNamespace) + .returns("deployed", Release::getStatus) + .returns("1", Release::getRevision) + .returns("test-0.1.0", Release::getChart) + .returns("1.16.0", Release::getAppVersion) + .returns("", Release::getOutput) + .extracting(Release::getLastDeployed) + .matches(d -> d.toLocalDate().equals(LocalDate.now())); + } + @Test void listsSpecificNamespace() { - final List result = Helm.list().withKubeConfig(kubeConfig) + final List result = Helm.list().withKubeConfig(kubeConfigFile) .withNamespace("list-namespace") .call(); assertThat(result) @@ -284,7 +303,7 @@ void listsSpecificNamespace() { @Test void listsAllNamespaces() { - final List result = Helm.list().withKubeConfig(kubeConfig) + final List result = Helm.list().withKubeConfig(kubeConfigFile) .allNamespaces() .call(); assertThat(result) @@ -303,7 +322,7 @@ class Valid { @Test void withValidRelease() { helm.install() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-test-valid-release") .set("fullnameOverride", "helm-test-valid-release") .set("image.repository", "ghcr.io/linuxserver/nginx") @@ -312,7 +331,7 @@ void withValidRelease() { .waitReady() .call(); final Release result = Helm.test("helm-test-valid-release") - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .call(); assertThat(result) .hasFieldOrPropertyWithValue("name", "helm-test-valid-release") @@ -332,7 +351,7 @@ class Invalid { @Test void missingRelease() { final TestCommand testCommand = Helm.test("i-was-never-created") - .withKubeConfig(kubeConfig); + .withKubeConfig(kubeConfigFile); assertThatThrownBy(testCommand::call) .message() .isEqualTo("release: not found"); @@ -340,9 +359,9 @@ void missingRelease() { @Test void lowTimeout() { - helm.install().withKubeConfig(kubeConfig).withName("test-low-timeout").call(); + helm.install().withKubeConfig(kubeConfigFile).withName("test-low-timeout").call(); final TestCommand testCommand = Helm.test("test-low-timeout") - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withTimeout(1); assertThatThrownBy(testCommand::call) .message() @@ -359,8 +378,8 @@ class Valid { @Test void withValidRelease() { - helm.install().withKubeConfig(kubeConfig).withName("uninstall").call(); - final String out = Helm.uninstall("uninstall").withKubeConfig(kubeConfig).call(); + helm.install().withKubeConfig(kubeConfigFile).withName("uninstall").call(); + final String out = Helm.uninstall("uninstall").withKubeConfig(kubeConfigFile).call(); assertThat(out).contains( "release \"uninstall\" uninstalled\n" ); @@ -368,10 +387,10 @@ void withValidRelease() { @Test void withNamespace() { - helm.install().withKubeConfig(kubeConfig).withName("uninstall-with-namespace") + helm.install().withKubeConfig(kubeConfigFile).withName("uninstall-with-namespace") .withNamespace("uninstall-with-namespace").createNamespace().call(); final String out = Helm.uninstall("uninstall-with-namespace") - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withNamespace("uninstall-with-namespace") .call(); assertThat(out).contains( @@ -381,9 +400,9 @@ void withNamespace() { @Test void withValidReleaseAndDebug() { - helm.install().withKubeConfig(kubeConfig).withName("uninstall-with-debug").call(); + helm.install().withKubeConfig(kubeConfigFile).withName("uninstall-with-debug").call(); final String out = Helm.uninstall("uninstall-with-debug") - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .debug() .call(); assertThat(out).contains( @@ -399,9 +418,9 @@ void withValidReleaseAndDebug() { @Test void withValidReleaseAndDryRunAndDebug() { - helm.install().withKubeConfig(kubeConfig).withName("uninstall-with-dry-run").call(); + helm.install().withKubeConfig(kubeConfigFile).withName("uninstall-with-dry-run").call(); final String out = Helm.uninstall("uninstall-with-dry-run") - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .dryRun() .debug() .call(); @@ -419,9 +438,9 @@ void withValidReleaseAndDryRunAndDebug() { @Test void withValidReleaseAndNoHooksAndDebug() { - helm.install().withKubeConfig(kubeConfig).withName("uninstall-with-no-hooks-debug").call(); + helm.install().withKubeConfig(kubeConfigFile).withName("uninstall-with-no-hooks-debug").call(); final String out = Helm.uninstall("uninstall-with-no-hooks-debug") - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .debug() .withCascade(UninstallCommand.Cascade.FOREGROUND) .keepHistory() @@ -445,7 +464,7 @@ void withValidReleaseAndNoHooksAndDebug() { @Test void missingReleaseWithIgnoreNotFound() { final String out = Helm.uninstall("i-was-never-created") - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .ignoreNotFound() .call(); assertThat(out).contains( @@ -460,7 +479,7 @@ class Invalid { @Test void missingRelease() { final UninstallCommand uninstall = Helm.uninstall("i-was-never-created") - .withKubeConfig(kubeConfig); + .withKubeConfig(kubeConfigFile); assertThatThrownBy(uninstall::call) .message() .isEqualTo("uninstall: Release not loaded: i-was-never-created: release: not found"); @@ -477,7 +496,7 @@ class Valid { @Test void withInstall() { final Release result = helm.upgrade() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .install() .withName("upgrade-with-install") .call(); @@ -488,9 +507,9 @@ void withInstall() { @Test void withPriorInstall() { - helm.install().withName("upgrade-prior-install").withKubeConfig(kubeConfig).call(); + helm.install().withName("upgrade-prior-install").withKubeConfig(kubeConfigFile).call(); final Release result = helm.upgrade() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("upgrade-prior-install") .call(); assertThat(result) @@ -500,9 +519,9 @@ void withPriorInstall() { @Test void withWaitReady() { - helm.install().withName("helm-upgrade-with-wait-ready").withKubeConfig(kubeConfig).call(); + helm.install().withName("helm-upgrade-with-wait-ready").withKubeConfig(kubeConfigFile).call(); final Release result = helm.upgrade() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-upgrade-with-wait-ready") .set("fullnameOverride", "helm-upgrade-with-wait-ready") .set("image.repository", "ghcr.io/linuxserver/nginx") @@ -519,9 +538,9 @@ void withWaitReady() { @Test void withWaitAndCustomTimeout() { - helm.install().withName("helm-upgrade-with-wait-ready-and-custom-timeout").withKubeConfig(kubeConfig).call(); + helm.install().withName("helm-upgrade-with-wait-ready-and-custom-timeout").withKubeConfig(kubeConfigFile).call(); final Release result = helm.upgrade() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-upgrade-with-wait-ready-and-custom-timeout") .set("fullnameOverride", "helm-upgrade-with-wait-ready-and-custom-timeout") .set("image.repository", "ghcr.io/linuxserver/nginx") @@ -544,7 +563,7 @@ class Invalid { void missingRelease() { final UpgradeCommand upgrade = helm.upgrade() .withName("upgrade-missing-release") - .withKubeConfig(kubeConfig); + .withKubeConfig(kubeConfigFile); assertThatThrownBy(upgrade::call) .message() .isEqualTo("\"upgrade-missing-release\" has no deployed releases"); @@ -552,9 +571,9 @@ void missingRelease() { @Test void lowTimeout() { - helm.install().withName("helm-upgrade-with-wait-ready-and-low-timeout").withKubeConfig(kubeConfig).call(); + helm.install().withName("helm-upgrade-with-wait-ready-and-low-timeout").withKubeConfig(kubeConfigFile).call(); final UpgradeCommand upgrade = helm.upgrade() - .withKubeConfig(kubeConfig) + .withKubeConfig(kubeConfigFile) .withName("helm-upgrade-with-wait-ready-and-low-timeout") .set("fullnameOverride", "helm-upgrade-with-wait-ready-and-low-timeout") .set("image.repository", "ghcr.io/linuxserver/nginx") 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 c8f89af1..70e0b02b 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 @@ -22,6 +22,7 @@ * @author Marc Nuri * @author Miriam Schmidt * @author Kevin J. Mckernan + * @author Christian Gebhard */ @Structure.FieldOrder({ "name", @@ -43,6 +44,7 @@ "values", "valuesFiles", "kubeConfig", + "kubeConfigContents", "certFile", "keyFile", "caFile", @@ -74,6 +76,7 @@ public class InstallOptions extends Structure { public String values; public String valuesFiles; public String kubeConfig; + public String kubeConfigContents; public String certFile; public String keyFile; public String caFile; @@ -104,6 +107,7 @@ public InstallOptions( String values, String valuesFiles, String kubeConfig, + String kubeConfigContents, String certFile, String keyFile, String caFile, @@ -133,6 +137,7 @@ public InstallOptions( this.values = values; this.valuesFiles = valuesFiles; this.kubeConfig = kubeConfig; + this.kubeConfigContents = kubeConfigContents; this.certFile = certFile; this.keyFile = keyFile; this.caFile = caFile; diff --git a/lib/api/src/main/java/com/marcnuri/helm/jni/ListOptions.java b/lib/api/src/main/java/com/marcnuri/helm/jni/ListOptions.java index 6ae9c726..16d36a8c 100644 --- a/lib/api/src/main/java/com/marcnuri/helm/jni/ListOptions.java +++ b/lib/api/src/main/java/com/marcnuri/helm/jni/ListOptions.java @@ -20,6 +20,7 @@ /** * @author Marc Nuri + * @author Christian Gebhard */ @Structure.FieldOrder({ "all", @@ -31,7 +32,8 @@ "uninstalled", "uninstalling", "namespace", - "kubeConfig" + "kubeConfig", + "kubeConfigContents" }) public class ListOptions extends Structure { public int all; @@ -44,8 +46,9 @@ public class ListOptions extends Structure { public int uninstalling; public String namespace; public String kubeConfig; + public String kubeConfigContents; - public ListOptions(int all, int allNamespaces, int deployed, int failed, int pending, int superseded, int uninstalled, int uninstalling, String namespace, String kubeConfig) { + public ListOptions(int all, int allNamespaces, int deployed, int failed, int pending, int superseded, int uninstalled, int uninstalling, String namespace, String kubeConfig, String kubeConfigContents) { this.all = all; this.allNamespaces = allNamespaces; this.deployed = deployed; @@ -56,5 +59,6 @@ public ListOptions(int all, int allNamespaces, int deployed, int failed, int pen this.uninstalling = uninstalling; this.namespace = namespace; this.kubeConfig = kubeConfig; + this.kubeConfigContents = kubeConfigContents; } } diff --git a/lib/api/src/main/java/com/marcnuri/helm/jni/TestOptions.java b/lib/api/src/main/java/com/marcnuri/helm/jni/TestOptions.java index bef0be4e..3a4f5c67 100644 --- a/lib/api/src/main/java/com/marcnuri/helm/jni/TestOptions.java +++ b/lib/api/src/main/java/com/marcnuri/helm/jni/TestOptions.java @@ -20,12 +20,14 @@ /** * @author Marc Nuri + * @author Christian Gebhard */ @Structure.FieldOrder({ "releaseName", "timeout", "namespace", "kubeConfig", + "kubeConfigContents", "debug" }) public class TestOptions extends Structure { @@ -33,6 +35,7 @@ public class TestOptions extends Structure { public int timeout; public String namespace; public String kubeConfig; + public String kubeConfigContents; public int debug; public TestOptions( @@ -40,12 +43,14 @@ public TestOptions( int timeout, String namespace, String kubeConfig, + String kubeConfigContents, int debug ) { this.releaseName = releaseName; this.timeout = timeout; this.namespace = namespace; this.kubeConfig = kubeConfig; + this.kubeConfigContents = kubeConfigContents; this.debug = debug; } diff --git a/lib/api/src/main/java/com/marcnuri/helm/jni/UninstallOptions.java b/lib/api/src/main/java/com/marcnuri/helm/jni/UninstallOptions.java index ae5d6ebf..9596bf86 100644 --- a/lib/api/src/main/java/com/marcnuri/helm/jni/UninstallOptions.java +++ b/lib/api/src/main/java/com/marcnuri/helm/jni/UninstallOptions.java @@ -20,6 +20,7 @@ /** * @author Marc Nuri + * @author Christian Gebhard */ @Structure.FieldOrder({ "releaseName", @@ -30,6 +31,7 @@ "cascade", "namespace", "kubeConfig", + "kubeConfigContents", "debug" }) public class UninstallOptions extends Structure { @@ -41,6 +43,7 @@ public class UninstallOptions extends Structure { public String cascade; public String namespace; public String kubeConfig; + public String kubeConfigContents; public int debug; public UninstallOptions( @@ -52,6 +55,7 @@ public UninstallOptions( String cascade, String namespace, String kubeConfig, + String kubeConfigContents, int debug ) { this.releaseName = releaseName; @@ -62,6 +66,7 @@ public UninstallOptions( this.cascade = cascade; this.namespace = namespace; this.kubeConfig = kubeConfig; + this.kubeConfigContents = kubeConfigContents; this.debug = debug; } } 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 322e0f1b..b6743f83 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 @@ -22,6 +22,7 @@ * @author Marc Nuri * @author Miriam Schmidt * @author Kevin J. Mckernan + * @author Christian Gebhard */ @Structure.FieldOrder({ "name", @@ -47,6 +48,7 @@ "values", "valuesFiles", "kubeConfig", + "kubeConfigContents", "certFile", "keyFile", "caFile", @@ -81,6 +83,7 @@ public class UpgradeOptions extends Structure { public String values; public String valuesFiles; public String kubeConfig; + public String kubeConfigContents; public String certFile; public String keyFile; public String caFile; @@ -115,6 +118,7 @@ public UpgradeOptions( String values, String valuesFiles, String kubeConfig, + String kubeConfigContents, String certFile, String keyFile, String caFile, @@ -148,6 +152,7 @@ public UpgradeOptions( this.values = values; this.valuesFiles = valuesFiles; this.kubeConfig = kubeConfig; + this.kubeConfigContents = kubeConfigContents; this.certFile = certFile; this.keyFile = keyFile; this.caFile = caFile; diff --git a/native/internal/helm/envtest_test.go b/native/internal/helm/envtest_test.go index c8a5459a..b5213e89 100644 --- a/native/internal/helm/envtest_test.go +++ b/native/internal/helm/envtest_test.go @@ -244,6 +244,36 @@ func TestList(t *testing.T) { } } +func TestListUsingKubeConfigContents(t *testing.T) { + cleanUp, kubeConfigFile := setupEnvTest() + defer cleanUp() + kubeConfigContents, _ := os.ReadFile(kubeConfigFile.Name()) + create, _ := Create(&CreateOptions{ + Name: "test-list", + Dir: t.TempDir(), + }) + _, _ = Install(&InstallOptions{ + KubeConfigContents: string(kubeConfigContents), + Chart: create, + Name: "test-list", + }) + out, err := List(&ListOptions{ + KubeConfig: kubeConfigFile.Name(), + }) + if err != nil { + t.Errorf("Expected list to succeed, got %s", err) + return + } + if !strings.Contains(out, "name=test-list") { + t.Errorf("Expected list to include test-list, got %s", out) + return + } + if !strings.Contains(out, "chart=test-list-0.1.0") { + t.Errorf("Expected list to include test-list, got %s", out) + return + } +} + func TestTest(t *testing.T) { cleanUp, kubeConfigFile := setupEnvTest() defer cleanUp() diff --git a/native/internal/helm/helm.go b/native/internal/helm/helm.go index 4a806f41..fd662472 100644 --- a/native/internal/helm/helm.go +++ b/native/internal/helm/helm.go @@ -19,6 +19,14 @@ package helm import ( "bytes" "fmt" + "io" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "os" + "strings" + "time" + "github.com/pkg/errors" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" @@ -27,18 +35,15 @@ import ( "helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/release" - "io" - "os" - "strings" - "time" ) type CfgOptions struct { - RegistryClient *registry.Client - KubeConfig string - Namespace string - AllNamespaces bool - KubeOut io.Writer + RegistryClient *registry.Client + KubeConfig string + KubeConfigContents string + Namespace string + AllNamespaces bool + KubeOut io.Writer } type CertOptions struct { @@ -66,7 +71,19 @@ func NewCfg(options *CfgOptions) *action.Configuration { if options.AllNamespaces { effectiveNamespace = "" } - err := actionConfig.Init(settings.RESTClientGetter(), effectiveNamespace, os.Getenv("HELM_DRIVER"), log) + restClientGetter := settings.RESTClientGetter() + restClientGetter.(*genericclioptions.ConfigFlags).WrapConfigFn = func(original *rest.Config) *rest.Config { + if options.KubeConfigContents != "" { + // TODO: we could actually merge both kubeconfigs + config, err := clientcmd.RESTConfigFromKubeConfig([]byte(options.KubeConfigContents)) + if err != nil { + panic(err) + } + return config + } + return original + } + err := actionConfig.Init(restClientGetter, effectiveNamespace, os.Getenv("HELM_DRIVER"), log) if err != nil { panic(err) } diff --git a/native/internal/helm/helm_test.go b/native/internal/helm/helm_test.go new file mode 100644 index 00000000..11620e52 --- /dev/null +++ b/native/internal/helm/helm_test.go @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Marc Nuri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package helm + +import ( + "testing" +) + +const kubeConfigContentsForTests = ` +apiVersion: v1 +clusters: +- cluster: + server: https://host.example.com + name: development +contexts: +- context: + cluster: development + namespace: the-namespace + user: developer + name: kube-config-test-contents +current-context: kube-config-test-contents +kind: Config +preferences: {} +users: +- name: developer + user: + username: test-user + password: test-password +` + +func TestNewCfg_KubeConfigContents(t *testing.T) { + cfg := NewCfg(&CfgOptions{ + KubeConfigContents: kubeConfigContentsForTests, + }) + restConfig, err := cfg.RESTClientGetter.ToRESTConfig() + if err != nil { + t.Fatalf("Expected converting to succeed, got %s", err) + } + if restConfig.Host != "https://host.example.com" { + t.Fatalf("Expected https://host.example.com, got %s", restConfig.Host) + } + if restConfig.Username != "test-user" { + t.Fatalf("Expected test-user, got %s", restConfig.Username) + } + if restConfig.Password != "test-password" { + t.Fatalf("Expected test-password, got %s", restConfig.Password) + } +} diff --git a/native/internal/helm/install.go b/native/internal/helm/install.go index 2df6cf1c..9a5cf7e1 100644 --- a/native/internal/helm/install.go +++ b/native/internal/helm/install.go @@ -58,6 +58,7 @@ type InstallOptions struct { Values string ValuesFiles string KubeConfig string + KubeConfigContents string Debug bool // For testing purposes only, prevents connecting to Kubernetes (happens even with DryRun=true and DryRunOption=client) ClientOnly bool @@ -97,9 +98,10 @@ func install(options *InstallOptions) (*release.Release, *installOutputs, error) return nil, outputs, err } cfgOptions := &CfgOptions{ - RegistryClient: registryClient, - KubeConfig: options.KubeConfig, - Namespace: options.Namespace, + RegistryClient: registryClient, + KubeConfig: options.KubeConfig, + KubeConfigContents: options.KubeConfigContents, + Namespace: options.Namespace, } if options.Debug { cfgOptions.KubeOut = outputs.kubeOut diff --git a/native/internal/helm/list.go b/native/internal/helm/list.go index b73aded9..0980982f 100644 --- a/native/internal/helm/list.go +++ b/native/internal/helm/list.go @@ -26,23 +26,25 @@ import ( ) type ListOptions struct { - All bool - AllNamespaces bool - Deployed bool - Failed bool - Pending bool - Superseded bool - Uninstalled bool - Uninstalling bool - Namespace string - KubeConfig string + All bool + AllNamespaces bool + Deployed bool + Failed bool + Pending bool + Superseded bool + Uninstalled bool + Uninstalling bool + Namespace string + KubeConfig string + KubeConfigContents string } func List(options *ListOptions) (string, error) { cfg := NewCfg(&CfgOptions{ - KubeConfig: options.KubeConfig, - Namespace: options.Namespace, - AllNamespaces: options.AllNamespaces, + KubeConfig: options.KubeConfig, + KubeConfigContents: options.KubeConfigContents, + Namespace: options.Namespace, + AllNamespaces: options.AllNamespaces, }) client := action.NewList(cfg) client.All = options.All diff --git a/native/internal/helm/test.go b/native/internal/helm/test.go index 75efdab6..582dc0bf 100644 --- a/native/internal/helm/test.go +++ b/native/internal/helm/test.go @@ -22,17 +22,19 @@ import ( ) type TestOptions struct { - ReleaseName string - Timeout time.Duration - Namespace string - KubeConfig string - Debug bool + ReleaseName string + Timeout time.Duration + Namespace string + KubeConfig string + KubeConfigContents string + Debug bool } func Test(options *TestOptions) (string, error) { cfgOptions := &CfgOptions{ - KubeConfig: options.KubeConfig, - Namespace: options.Namespace, + KubeConfig: options.KubeConfig, + KubeConfigContents: options.KubeConfigContents, + Namespace: options.Namespace, } client := action.NewReleaseTesting(NewCfg(cfgOptions)) client.Namespace = options.Namespace diff --git a/native/internal/helm/uninstall.go b/native/internal/helm/uninstall.go index 57a0dff9..3194a667 100644 --- a/native/internal/helm/uninstall.go +++ b/native/internal/helm/uninstall.go @@ -23,21 +23,23 @@ import ( ) type UninstallOptions struct { - ReleaseName string - DryRun bool - NoHooks bool - IgnoreNotFound bool - KeepHistory bool - Cascade string - Namespace string - KubeConfig string - Debug bool + ReleaseName string + DryRun bool + NoHooks bool + IgnoreNotFound bool + KeepHistory bool + Cascade string + Namespace string + KubeConfig string + KubeConfigContents string + Debug bool } func Uninstall(options *UninstallOptions) (string, error) { cfgOptions := &CfgOptions{ - KubeConfig: options.KubeConfig, - Namespace: options.Namespace, + KubeConfig: options.KubeConfig, + KubeConfigContents: options.KubeConfigContents, + Namespace: options.Namespace, } kubeOut := bytes.NewBuffer(make([]byte, 0)) if options.Debug { diff --git a/native/internal/helm/upgrade.go b/native/internal/helm/upgrade.go index 1cb51684..c661345c 100644 --- a/native/internal/helm/upgrade.go +++ b/native/internal/helm/upgrade.go @@ -49,6 +49,7 @@ type UpgradeOptions struct { Values string ValuesFiles string KubeConfig string + KubeConfigContents string Debug bool // For testing purposes only, prevents connecting to Kubernetes (happens even with DryRun=true and DryRunOption=client) ClientOnly bool @@ -69,9 +70,10 @@ func Upgrade(options *UpgradeOptions) (string, error) { } kubeOut := bytes.NewBuffer(make([]byte, 0)) cfgOptions := &CfgOptions{ - RegistryClient: registryClient, - KubeConfig: options.KubeConfig, - Namespace: options.Namespace, + RegistryClient: registryClient, + KubeConfig: options.KubeConfig, + KubeConfigContents: options.KubeConfigContents, + Namespace: options.Namespace, } if options.Debug { cfgOptions.KubeOut = kubeOut diff --git a/native/main.go b/native/main.go index 7f739059..0027dfa0 100644 --- a/native/main.go +++ b/native/main.go @@ -60,6 +60,7 @@ struct InstallOptions { char* values; char* valuesFiles; char* kubeConfig; + char* kubeConfigContents; char* certFile; char* keyFile; char* caFile; @@ -88,6 +89,7 @@ struct ListOptions { int uninstalling; char* namespace; char* kubeConfig; + char* kubeConfigContents; }; struct PackageOptions { @@ -184,6 +186,7 @@ struct TestOptions { int timeout; char* namespace; char* kubeConfig; + char* kubeConfigContents; int debug; }; @@ -196,6 +199,7 @@ struct UninstallOptions { char* cascade; char* namespace; char* kubeConfig; + char* kubeConfigContents; int debug; }; @@ -223,6 +227,7 @@ struct UpgradeOptions { char* values; char* valuesFiles; char* kubeConfig; + char* kubeConfigContents; char* certFile; char* keyFile; char* caFile; @@ -353,6 +358,7 @@ func Install(options *C.struct_InstallOptions) C.Result { Values: C.GoString(options.values), ValuesFiles: C.GoString(options.valuesFiles), KubeConfig: C.GoString(options.kubeConfig), + KubeConfigContents: C.GoString(options.kubeConfigContents), CertOptions: helm.CertOptions{ CertFile: C.GoString(options.certFile), KeyFile: C.GoString(options.keyFile), @@ -386,16 +392,17 @@ func Lint(options *C.struct_LintOptions) C.Result { func List(options *C.struct_ListOptions) C.Result { return runCommand(func() (string, error) { return helm.List(&helm.ListOptions{ - All: options.all == 1, - AllNamespaces: options.allNamespaces == 1, - Deployed: options.deployed == 1, - Failed: options.failed == 1, - Pending: options.pending == 1, - Superseded: options.superseded == 1, - Uninstalled: options.uninstalled == 1, - Uninstalling: options.uninstalling == 1, - Namespace: C.GoString(options.namespace), - KubeConfig: C.GoString(options.kubeConfig), + All: options.all == 1, + AllNamespaces: options.allNamespaces == 1, + Deployed: options.deployed == 1, + Failed: options.failed == 1, + Pending: options.pending == 1, + Superseded: options.superseded == 1, + Uninstalled: options.uninstalled == 1, + Uninstalling: options.uninstalling == 1, + Namespace: C.GoString(options.namespace), + KubeConfig: C.GoString(options.kubeConfig), + KubeConfigContents: C.GoString(options.kubeConfigContents), }) }) } @@ -626,11 +633,12 @@ func Test(options *C.struct_TestOptions) C.Result { } return runCommand(func() (string, error) { return helm.Test(&helm.TestOptions{ - ReleaseName: C.GoString(options.releaseName), - Namespace: C.GoString(options.namespace), - KubeConfig: C.GoString(options.kubeConfig), - Timeout: timeout, - Debug: options.debug == 1, + ReleaseName: C.GoString(options.releaseName), + Namespace: C.GoString(options.namespace), + KubeConfig: C.GoString(options.kubeConfig), + KubeConfigContents: C.GoString(options.kubeConfigContents), + Timeout: timeout, + Debug: options.debug == 1, }) }) } @@ -639,15 +647,16 @@ func Test(options *C.struct_TestOptions) C.Result { func Uninstall(options *C.struct_UninstallOptions) C.Result { return runCommand(func() (string, error) { return helm.Uninstall(&helm.UninstallOptions{ - ReleaseName: C.GoString(options.releaseName), - DryRun: options.dryRun == 1, - NoHooks: options.noHooks == 1, - IgnoreNotFound: options.ignoreNotFound == 1, - KeepHistory: options.keepHistory == 1, - Cascade: C.GoString(options.cascade), - Namespace: C.GoString(options.namespace), - KubeConfig: C.GoString(options.kubeConfig), - Debug: options.debug == 1, + ReleaseName: C.GoString(options.releaseName), + DryRun: options.dryRun == 1, + NoHooks: options.noHooks == 1, + IgnoreNotFound: options.ignoreNotFound == 1, + KeepHistory: options.keepHistory == 1, + Cascade: C.GoString(options.cascade), + Namespace: C.GoString(options.namespace), + KubeConfig: C.GoString(options.kubeConfig), + KubeConfigContents: C.GoString(options.kubeConfigContents), + Debug: options.debug == 1, }) }) } @@ -685,6 +694,7 @@ func Upgrade(options *C.struct_UpgradeOptions) C.Result { Values: C.GoString(options.values), ValuesFiles: C.GoString(options.valuesFiles), KubeConfig: C.GoString(options.kubeConfig), + KubeConfigContents: C.GoString(options.kubeConfigContents), CertOptions: helm.CertOptions{ CertFile: C.GoString(options.certFile), KeyFile: C.GoString(options.keyFile),