This file is the primary context for AI coding agents (Claude Code, Copilot, etc.) working in this repo. Read it first; fall back to grep/search only when something here is wrong or missing.
A Java client library for Helm with no external CLI dependency. The Helm Go SDK (helm.sh/helm/v3) is compiled to a per-platform shared library via CGO and loaded from Java with JNA. The public Java API is a fluent builder that mirrors Helm CLI verbs: install, upgrade, uninstall, template, package, lint, repo, registry, search, show, status, history, get (values), test, list, dependency, push, version, create.
- Helm SDK version: tracked by
native/go.mod(currentlyhelm.sh/helm/v3 v3.21.0). - Java baseline: source/target 1.8. Tests run on 8 (Linux CI) or 11 (macOS/Windows CI, because macos-latest dropped 8).
- Go baseline: pinned in
native/go.modand.github/workflows/build.yml(GO_VERSION); currently1.25.10.
Multi-module Maven build (parent pom.xml) + a sibling Go module under native/. To find a file, use the naming patterns in Conventions — don't trust enumerations here, they go stale.
| Path | Role |
|---|---|
helm-java/ |
Public Java API (the published artifact). Holds Helm.java entry point, one {Verb}Command.java per command, and result/domain types (Release, Repository, …). Tests in src/test/java/. |
lib/api/ |
JNA layer, shared across platforms. Holds HelmLib (the JNA interface — one method per exported native function), NativeLibrary (SPI loader with RemoteJarLoader fallback for Gradle), Result, and one {Operation}Options.java per native call. |
lib/{darwin,linux,windows}-{amd64,arm64}/ |
Per-platform native binary modules. Each ships the compiled shared library as a classpath resource and an SPI entry at META-INF/services/com.marcnuri.helm.jni.NativeLibrary. Selected automatically by OS-activated profiles in helm-java/pom.xml. |
lib/wasi/ |
Abandoned WebAssembly experiment (issue #239, PR #243). Not in parent pom.xml. Don't touch; will be removed. |
native/main.go |
CGO bridge only: C struct definitions + //export wrappers that delegate to internal/helm. No business logic. |
native/internal/helm/ |
Real Go implementations — one file per verb (install.go, upgrade.go, …) plus _test.go siblings. helm.go holds shared config (NewCfg), debug.go the stdout/stderr capture. |
native/internal/test/ |
Go test helpers (envtest). |
native/wasm/, native/out/ |
TinyGo/WASI experiment entrypoint (see lib/wasi/ note); build output for shared libraries. |
docs/research/ |
Audits and decision records (e.g. native-codebase audit). Snapshots — may be stale; check the date. |
scripts/check-authors.sh |
Validates @author Javadoc tags (invoked via make check-authors). |
Makefile, build/*.mk |
Build/test/release entrypoints. Top-level Makefile auto-includes build/*.mk (e.g. build/release.mk). Run make help for the categorized target list. See Build & test. |
The Maven parent has requireFilesExist enforcer that needs all 5 native binaries in native/out/. Submodule helm-java and lib/api skip the rule, but a top-level ./mvnw install will fail without them.
# Discover all targets (categorized: Build / Test / Code Quality / Development / Release)
make help
# Local dev (current platform only — skips the cross-platform enforcer check)
make build-current-platform # = build-native + mvn clean verify with -Denforcer.skipRules=requireFilesExist
# Full Maven build (needs all 5 native binaries built first)
./mvnw clean install
./mvnw clean install -Dquickly # skips tests AND invoker plugin
# Native binaries
make build-native # current platform (auto-detected: OS/arch → helm-<os>-<arch>.<ext>)
make build-native-cross-platform # all 5 platforms via xgo (Docker required)
# Single Java test
./mvnw test -pl helm-java -Dtest=HelmInstallTest
./mvnw test -pl helm-java -Dtest=HelmInstallTest#withName
# Go tests
make test-go # = cd native && go clean -testcache && go test ./...
# Other useful targets
make build-all # cross-platform natives + Java build
make update-go-deps # bulk-bump non-indirect Go deps
make license # apply license-header.txt to .go/.java files
make check-authors # verify @author tags (ARGS='--fix' suggests additions)
make release V=1.2.3 VS=1.3.0 # tag release and bump to next snapshot (maintainer only)
make maven-deploy # mvn -Prelease clean deploy (CI only; needs Central credentials)CI:
.github/workflows/build.yml: Linux job runsmake test-go+make build-allon Java 8. Matrix jobs runmake build-current-platformon Windows/macOS with Java 11..github/workflows/release.ymlandsnapshots.yml: Linux Java 8; runmake build-allthenmake maven-deploy.- Shared CI infra:
.github/actions/free-disk-space(composite action) reclaims runner disk space before Ubuntu builds.
Versioning is 0.0.X patch-only; main always sits on 0.0-SNAPSHOT (so VS=0.0 every time). Full flow for cutting v0.0.X:
- Pre-flight: be on
main, clean tree, in sync withorigin. Skimgit log v0.0.<X-1>..HEAD --onelineto confirm the scope is patch-worthy. - Cut the release:
make release V=0.0.X VS=0.0. This sets the pom to0.0.X, commits, tagsv0.0.X, pushes the tag (which triggersrelease.yml), then sets the pom back to0.0-SNAPSHOT, commits, and pushesmain. Two commits land onmainplus one tag. - Wait for
release.yml: ~20 min.gh run watch <run-id> --exit-statusuntil it's green. It builds all 5 native binaries and runsmake maven-deployto Maven Central. If it fails partway, artifacts may be partially staged — don't re-run blindly, inspect first. - Create the GitHub Release manually:
release.ymlonly publishes to Maven Central; it does NOT cut a GitHub Release. On https://github.com/manusa/helm-java/releases/new pick thev0.0.Xtag and click Generate release notes (this is what every prior release used — a PR list plus thecompare/v0.0.<X-1>...v0.0.Xlink). Publish. - Verify on Maven Central: artifacts appear under https://central.sonatype.com/artifact/com.marcnuri.helm-java/helm-java within ~30 min of step 3 finishing.
Don't cancel running tests that hit Kubernetes — they leak cluster resources.
Java caller
→ Helm.install() returns InstallCommand (helm-java/.../InstallCommand.java)
→ builder methods set fields
→ .call() invokes HelmLibHolder.INSTANCE.Install(installOptions)
(lib/api/.../HelmLib.java — JNA interface)
→ JNA marshals InstallOptions → C struct
→ //export Install in native/main.go (CGO bridge, no logic)
→ helm.Install(...) in native/internal/helm/install.go (real implementation)
→ returns C.Result (out/err/stdOut/stdErr) → JNA → Result → parsed into Release
Native library loading: Helm class holds HelmLibHolder.INSTANCE, initialized lazily by NativeLibrary.getInstance().load(). getInstance() uses Java ServiceLoader to discover the platform module on the classpath (each platform JAR has META-INF/services/com.marcnuri.helm.jni.NativeLibrary); if none found, falls back to RemoteJarLoader (downloads the correct platform JAR at runtime — works for Gradle, fails when air-gapped). helm-java/pom.xml has OS-activated profiles that pull in the right platform module automatically; both amd64/x86_64 and arm64/aarch64 aliases are handled.
Verbs come in two forms where it makes sense:
- Static
Helm.install(chart)— operates on an external chart path/URL. - Instance
new Helm(path).install()— operates on the chart at thePaththeHelmwas constructed with.
Verbs with both forms: install, template, show, upgrade. Most others are static-only (list, history, status, get, repo, registry, search, push, test, uninstall, version) or instance-only (dependency, lint, packageIt — naming dodges the package keyword).
Java
- Source/target 1.8 — no
var, noMap.of(...), no Optional in APIs, no records. - Apache 2.0 header on every
.goand.javafile (make licenseenforces). @authorJavadoc tags maintained (make check-authors).- Tests: JUnit 5 + AssertJ. No mocking libraries — use real impls (Testcontainers/KinD for k8s).
Go
go fmt. CGO//exportfunctions innative/main.goare wrappers ONLY; logic goes innative/internal/helm/<verb>.go.
Naming
- Java command:
{Verb}Command.java(e.g.InstallCommand). - Java JNI options:
{Operation}Options.java(e.g.InstallOptions,HistoryOptions,GetValuesOptions). - Result/domain types: noun, no suffix (e.g.
Release,Repository,LintResult,SearchResult). - Tests:
Helm{Feature}Test.java(HelmInstallTest,HelmKubernetesTest); exception: infrastructure tests likeNativeLibraryTest. - Go: file per verb in
internal/helm/(install.go,install_test.go).
native/internal/helm/<verb>.go— write the real Go function (params struct + function returning(string, error)).native/internal/helm/<verb>_test.go— Go test (useinternal/test/env.gohelpers if k8s needed).native/main.go:- Add
struct <Verb>Options { ... }in the CGO comment block. - Add
//export <Verb>wrapper callinghelm.<Verb>(...)viarunCommand(...).
- Add
lib/api/src/main/java/com/marcnuri/helm/jni/<Verb>Options.java— JNAStructuremirroring the C struct (field order MUST match).lib/api/.../HelmLib.java— addResult <Verb>(<Verb>Options options);.helm-java/src/main/java/com/marcnuri/helm/<Verb>Command.java— fluent builder +call()that builds the options and invokesHelmLib. If the command returns structured data, add a result type alongside (e.g.Release).helm-java/src/main/java/com/marcnuri/helm/Helm.java— factory method (static, instance, or both).helm-java/src/test/java/com/marcnuri/helm/Helm{Verb}Test.java— JUnit test (see structure below).- Rebuild native:
make build-nativethen./mvnw test -pl helm-java.
- Black-box: test the public Java API, not internals.
- Use
@Nestedclasses to group scenarios (typical:Valid/Invalid, or per-mode likeClientOnly/Kubernetes). - Common setup in outer
@BeforeEach; scenario-specific in nested. - Prefer one assertion concern per
@Testfor clear failure isolation. @TempDiras a field for shared dirs; as a parameter for per-test dirs (seeHelmInstallTest).- k8s integration:
HelmKubernetesTestspins up KinD via Testcontainers in@BeforeAll; clean releases in@AfterEach. - Surefire sets
KUBECONFIG=/dev/nullinhelm-java/pom.xmlso tests cannot accidentally hit your local cluster — passkubeConfigFile/kubeConfigContentsexplicitly.
class HelmFeatureTest {
@TempDir private Path tempDir;
private Helm helm;
@BeforeEach
void setUp() {
helm = Helm.create().withName("test").withDir(tempDir).call();
}
@Nested
class Valid {
@Test
void withName() {
Release result = helm.install().clientOnly().withName("test").call();
assertThat(result)
.returns("test", Release::getName)
.returns("deployed", Release::getStatus);
}
}
@Nested
class Invalid {
@Test
void withMissingChart() {
InstallCommand cmd = Helm.install("/tmp/nothing").clientOnly().withName("test");
assertThatThrownBy(cmd::call).message().contains("not found");
}
}
}Most commands have .debug():
helm.install().debug().withName("test").call();Stdout/stderr captured by runCommand in native/main.go and surfaced on the Result.
- Enforcer "files do not exist" — missing binaries in
native/out/. Usemake build-current-platform(skips the rule) for local dev. UnsatisfiedLinkError/ no NativeLibrary found — platform module isn't on the classpath. Check the OS profile inhelm-java/pom.xmlactivated for youros.family/os.arch.- KinD tests hang / OOM — Docker not running or under-resourced.
- Go build fails — check
go versionmatchesnative/go.mod; runcd native && go mod tidy. - Windows native build — needs MinGW (CGO toolchain).
lib/wasi/andnative/wasm/— abandoned WebAssembly experiment (issue #239). Not in the parent pom, no source files. Don't extend it; don't fix it.
docs/research/native-codebase-audit.md— point-in-time snapshot of the Go layer (coverage, missing commands, known issues). Check the date before trusting it.