From bbdd5b88c080ea3d5a4da8bc0d2eb6402cdd349d Mon Sep 17 00:00:00 2001 From: floriankoch Date: Sat, 1 Aug 2026 16:52:51 +0200 Subject: [PATCH] fix(template): include hooks on request Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 2 + .../com/marcnuri/helm/TemplateCommand.java | 14 +++++- .../com/marcnuri/helm/HelmTemplateTest.java | 25 +++++++++- .../marcnuri/helm/jni/TemplateOptions.java | 8 +++- native/internal/helm/template.go | 14 ++++++ native/internal/helm/template_test.go | 46 +++++++++++++++++++ native/main.go | 2 + 7 files changed, 106 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e491f07e..c28a06cf 100644 --- a/README.md +++ b/README.md @@ -657,6 +657,8 @@ String result = templateCommand .withKubeVersion("v1.21.0") // Optionally update dependencies if they are missing before installing the chart .dependencyUpdate() + // Optionally include rendered Helm hooks in the output + .includeHooks() // Optionally set values for the chart .set("key", "value") // Optionally set a chart value from a file's contents (equivalent to --set-file) 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 db28447a..1f0f9b3b 100644 --- a/helm-java/src/main/java/com/marcnuri/helm/TemplateCommand.java +++ b/helm-java/src/main/java/com/marcnuri/helm/TemplateCommand.java @@ -50,6 +50,7 @@ public class TemplateCommand extends HelmCommand { private Path keyring; private boolean debug; private Path repositoryConfig; + private boolean includeHooks; public TemplateCommand(HelmLib helmLib) { this(helmLib, null); @@ -83,7 +84,8 @@ public String call() { toInt(plainHttp), toString(keyring), toInt(debug), - toString(repositoryConfig) + toString(repositoryConfig), + toInt(includeHooks) ))).out; } @@ -173,6 +175,16 @@ public TemplateCommand skipCrds() { return this; } + /** + * Include rendered Helm hooks in the output. + * + * @return this {@link TemplateCommand} instance. + */ + public TemplateCommand includeHooks() { + this.includeHooks = true; + return this; + } + /** * Set values for the chart. * 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 a115b246..77f9364d 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmTemplateTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmTemplateTest.java @@ -46,15 +46,36 @@ class FromLocalChart { private Helm helm; @BeforeEach - void setUp() { + void setUp() throws IOException { helm = Helm.create().withName("local-chart-test").withDir(tempDir).call(); + Files.write(tempDir.resolve("local-chart-test").resolve("templates").resolve("hook.yaml"), + ("apiVersion: v1\n" + + "kind: ConfigMap\n" + + "metadata:\n" + + " name: template-hook\n" + + " annotations:\n" + + " \"helm.sh/hook\": pre-install\n" + + "data:\n" + + " marker: hook-manifest\n").getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE); } @Test void withDefaults() { final String result = helm.template().call(); assertThat(result) - .contains("name: release-name-local-chart-test"); + .contains("name: release-name-local-chart-test") + .doesNotContain("marker: hook-manifest"); + } + + @Test + void includeHooks() { + final String result = helm.template().includeHooks().call(); + assertThat(result) + .contains("name: release-name-local-chart-test") + .contains("# Source: local-chart-test/templates/deployment.yaml\n") + .contains("---\n# Source: local-chart-test/templates/hook.yaml\napiVersion: v1\nkind: ConfigMap\n") + .contains("marker: hook-manifest"); } @Test 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 ce68fae1..e4392459 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 @@ -41,7 +41,8 @@ "plainHttp", "keyring", "debug", - "repositoryConfig" + "repositoryConfig", + "includeHooks" }) public class TemplateOptions extends Structure { public String name; @@ -62,6 +63,7 @@ public class TemplateOptions extends Structure { public String keyring; public int debug; public String repositoryConfig; + public int includeHooks; public TemplateOptions( String name, @@ -81,7 +83,8 @@ public TemplateOptions( int plainHttp, String keyring, int debug, - String repositoryConfig + String repositoryConfig, + int includeHooks ) { this.name = name; this.version = version; @@ -101,5 +104,6 @@ public TemplateOptions( this.keyring = keyring; this.debug = debug; this.repositoryConfig = repositoryConfig; + this.includeHooks = includeHooks; } } diff --git a/native/internal/helm/template.go b/native/internal/helm/template.go index 57758b66..aed21817 100644 --- a/native/internal/helm/template.go +++ b/native/internal/helm/template.go @@ -31,6 +31,7 @@ type TemplateOptions struct { KubeVersion string DependencyUpdate bool SkipCRDs bool + IncludeHooks bool Values string SetFiles string ValuesFiles string @@ -74,5 +75,18 @@ func Template(options *TemplateOptions) (string, error) { if _, fmtErr := fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)); fmtErr != nil { return "", fmtErr } + if options.IncludeHooks { + for _, hook := range rel.Hooks { + manifest := strings.TrimSpace(hook.Manifest) + if manifest == "" { + continue + } + if _, fmtErr := fmt.Fprintf( + &manifests, "---\n# Source: %s\n%s\n", strings.TrimSpace(hook.Path), manifest, + ); fmtErr != nil { + return "", fmtErr + } + } + } return appendToOutOrErr(&manifests, "", err) } diff --git a/native/internal/helm/template_test.go b/native/internal/helm/template_test.go index 6b275eee..13cb0eba 100644 --- a/native/internal/helm/template_test.go +++ b/native/internal/helm/template_test.go @@ -19,6 +19,7 @@ package helm import ( "helm.sh/helm/v3/pkg/chartutil" "os" + "path/filepath" "strings" "testing" ) @@ -28,6 +29,18 @@ func TestTemplateFromLocal(t *testing.T) { Name: "chart-for-template-tests", Dir: t.TempDir(), }) + hookManifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: template-hook + annotations: + "helm.sh/hook": pre-install +data: + marker: hook-manifest +` + if err := os.WriteFile(filepath.Join(create, "templates", "hook.yaml"), []byte(hookManifest), 0600); err != nil { + t.Fatalf("Expected hook template creation to succeed, got %s", err) + } t.Run("with defaults", func(t *testing.T) { manifests, err := Template(&TemplateOptions{ Chart: create, @@ -40,6 +53,39 @@ func TestTemplateFromLocal(t *testing.T) { t.Errorf("Expected template to include provided name, got %s", manifests) return } + if strings.Contains(manifests, "marker: hook-manifest") { + t.Errorf("Expected template to exclude hooks by default, got %s", manifests) + return + } + }) + t.Run("with hooks", func(t *testing.T) { + manifests, err := Template(&TemplateOptions{ + Chart: create, + IncludeHooks: true, + }) + if err != nil { + t.Errorf("Expected template to succeed, got %s", err) + return + } + if !strings.Contains(manifests, "name: release-name-chart-for-template-tests") { + t.Errorf("Expected template to retain ordinary manifests, got %s", manifests) + return + } + if !strings.Contains(manifests, "# Source: chart-for-template-tests/templates/deployment.yaml\n") { + t.Errorf("Expected template to preserve ordinary manifest source headers, got %s", manifests) + return + } + if !strings.Contains( + manifests, + "---\n# Source: chart-for-template-tests/templates/hook.yaml\napiVersion: v1\nkind: ConfigMap\n", + ) { + t.Errorf("Expected template to preserve the hook source path, got %s", manifests) + return + } + if !strings.Contains(manifests, "marker: hook-manifest") { + t.Errorf("Expected template to include hooks, got %s", manifests) + return + } }) t.Run("with name", func(t *testing.T) { manifests, err := Template(&TemplateOptions{ diff --git a/native/main.go b/native/main.go index 183fe9a8..2444a923 100644 --- a/native/main.go +++ b/native/main.go @@ -193,6 +193,7 @@ struct TemplateOptions { char* keyring; int debug; char* repositoryConfig; + int includeHooks; }; struct TestOptions { @@ -702,6 +703,7 @@ func Template(options *C.struct_TemplateOptions) C.Result { }, Debug: options.debug == 1, RepositoryConfig: C.GoString(options.repositoryConfig), + IncludeHooks: options.includeHooks == 1, }) }) }