-
Notifications
You must be signed in to change notification settings - Fork 22
Open
Description
Description
The helm pull command is currently not implemented in helm-java. This command downloads a chart from a repository and optionally unpacks it in a local directory, which is essential for inspecting, modifying, or repackaging charts before installation.
Background
The helm pull command:
- Downloads a chart from a repository to the local filesystem
- Can unpack (untar) the chart after downloading
- Supports cryptographic verification of charts
- Can fetch charts from both traditional Helm repositories and OCI registries
- Useful for offline installations, chart inspection, and chart customization
See the official documentation.
Use Cases
- Inspect charts before installation - Download and review chart contents
- Offline installations - Pre-download charts for air-gapped environments
- Chart customization - Download, modify, and repackage charts
- CI/CD pipelines - Cache charts locally for reproducible builds
- Chart verification - Verify chart signatures before deployment
Proposed API
Following the existing patterns in the codebase (similar to ShowCommand, PushCommand), the implementation should provide a fluent API:
// Basic usage - download chart to current directory
Path chartPath = Helm.pull("bitnami/nginx")
.call();
// Download to specific destination
Path chartPath = Helm.pull("bitnami/nginx")
.withDestination(Paths.get("/tmp/charts"))
.call();
// Download and untar
Path chartPath = Helm.pull("bitnami/nginx")
.withDestination(Paths.get("/tmp/charts"))
.untar()
.call();
// Download specific version
Path chartPath = Helm.pull("bitnami/nginx")
.withVersion("15.0.0")
.withDestination(Paths.get("/tmp/charts"))
.call();
// Download from specific repo URL
Path chartPath = Helm.pull("nginx")
.withRepo("https://charts.bitnami.com/bitnami")
.withDestination(Paths.get("/tmp/charts"))
.call();
// With authentication
Path chartPath = Helm.pull("my-private-chart")
.withRepo("https://private-repo.example.com")
.withUsername("user")
.withPassword("pass")
.withDestination(Paths.get("/tmp/charts"))
.call();
// With verification
Path chartPath = Helm.pull("bitnami/nginx")
.verify()
.withKeyring(Paths.get("~/.gnupg/pubring.gpg"))
.withDestination(Paths.get("/tmp/charts"))
.call();
// From OCI registry
Path chartPath = Helm.pull("oci://registry.example.com/charts/nginx")
.withVersion("1.0.0")
.withDestination(Paths.get("/tmp/charts"))
.call();
// Full example with all options
Path chartPath = Helm.pull("bitnami/nginx")
.withVersion("^15.0.0")
.withRepo("https://charts.bitnami.com/bitnami")
.withDestination(Paths.get("/tmp/charts"))
.untar()
.withUntarDir("nginx-chart")
.withUsername("user")
.withPassword("pass")
.withCaFile(Paths.get("/path/to/ca.crt"))
.withCertFile(Paths.get("/path/to/cert.crt"))
.withKeyFile(Paths.get("/path/to/key.key"))
.verify()
.withKeyring(Paths.get("~/.gnupg/pubring.gpg"))
.devel()
.passCredentials()
.debug()
.call();Implementation Guide
1. Create Go Options struct and function (native/internal/helm/pull.go)
package helm
import (
"os"
"path/filepath"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli"
)
type PullOptions struct {
Chart string
Version string
Repo string
Destination string
Untar bool
UntarDir string
Username string
Password string
CertFile string
KeyFile string
CaFile string
InsecureSkipTlsVerify bool
PlainHttp bool
Verify bool
Keyring string
Devel bool
PassCredentials bool
RepositoryConfig string
RepositoryCache string
Debug bool
}
func Pull(options *PullOptions) (string, error) {
var log action.DebugLog = nil
if options.Debug {
log = debugLog
}
settings := cli.New()
if options.RepositoryConfig != "" {
settings.RepositoryConfig = options.RepositoryConfig
}
if options.RepositoryCache != "" {
settings.RepositoryCache = options.RepositoryCache
}
client := action.NewPullWithOpts(action.WithConfig(&action.Configuration{}))
client.Settings = settings
// Version options
if options.Version != "" {
client.Version = options.Version
}
client.Devel = options.Devel
// Destination
if options.Destination != "" {
client.DestDir = options.Destination
} else {
client.DestDir = "."
}
// Untar options
client.Untar = options.Untar
if options.UntarDir != "" {
client.UntarDir = options.UntarDir
}
// Repository options
if options.Repo != "" {
client.RepoURL = options.Repo
}
client.Username = options.Username
client.Password = options.Password
client.PassCredentialsAll = options.PassCredentials
// TLS options
client.CertFile = options.CertFile
client.KeyFile = options.KeyFile
client.CaFile = options.CaFile
client.InsecureSkipTLSverify = options.InsecureSkipTlsVerify
client.PlainHTTP = options.PlainHttp
// Verification options
client.Verify = options.Verify
if options.Keyring != "" {
client.Keyring = options.Keyring
}
output, err := client.Run(options.Chart)
if err != nil {
return "", err
}
return output, nil
}2. Add CGO export in native/main.go
Add the C struct definition:
struct PullOptions {
char* chart;
char* version;
char* repo;
char* destination;
int untar;
char* untarDir;
char* username;
char* password;
char* certFile;
char* keyFile;
char* caFile;
int insecureSkipTlsVerify;
int plainHttp;
int verify;
char* keyring;
int devel;
int passCredentials;
char* repositoryConfig;
char* repositoryCache;
int debug;
};Add the export function:
//export Pull
func Pull(options *C.struct_PullOptions) C.Result {
return result(helm.Pull(&helm.PullOptions{
Chart: C.GoString(options.chart),
Version: C.GoString(options.version),
Repo: C.GoString(options.repo),
Destination: C.GoString(options.destination),
Untar: options.untar == 1,
UntarDir: C.GoString(options.untarDir),
Username: C.GoString(options.username),
Password: C.GoString(options.password),
CertFile: C.GoString(options.certFile),
KeyFile: C.GoString(options.keyFile),
CaFile: C.GoString(options.caFile),
InsecureSkipTlsVerify: options.insecureSkipTlsVerify == 1,
PlainHttp: options.plainHttp == 1,
Verify: options.verify == 1,
Keyring: C.GoString(options.keyring),
Devel: options.devel == 1,
PassCredentials: options.passCredentials == 1,
RepositoryConfig: C.GoString(options.repositoryConfig),
RepositoryCache: C.GoString(options.repositoryCache),
Debug: options.debug == 1,
}))
}3. Create JNA Options class (lib/api/src/main/java/com/marcnuri/helm/jni/PullOptions.java)
package com.marcnuri.helm.jni;
import com.sun.jna.Structure;
@Structure.FieldOrder({
"chart",
"version",
"repo",
"destination",
"untar",
"untarDir",
"username",
"password",
"certFile",
"keyFile",
"caFile",
"insecureSkipTlsVerify",
"plainHttp",
"verify",
"keyring",
"devel",
"passCredentials",
"repositoryConfig",
"repositoryCache",
"debug"
})
public class PullOptions extends Structure {
public String chart;
public String version;
public String repo;
public String destination;
public int untar;
public String untarDir;
public String username;
public String password;
public String certFile;
public String keyFile;
public String caFile;
public int insecureSkipTlsVerify;
public int plainHttp;
public int verify;
public String keyring;
public int devel;
public int passCredentials;
public String repositoryConfig;
public String repositoryCache;
public int debug;
public PullOptions(
String chart,
String version,
String repo,
String destination,
int untar,
String untarDir,
String username,
String password,
String certFile,
String keyFile,
String caFile,
int insecureSkipTlsVerify,
int plainHttp,
int verify,
String keyring,
int devel,
int passCredentials,
String repositoryConfig,
String repositoryCache,
int debug
) {
this.chart = chart;
this.version = version;
this.repo = repo;
this.destination = destination;
this.untar = untar;
this.untarDir = untarDir;
this.username = username;
this.password = password;
this.certFile = certFile;
this.keyFile = keyFile;
this.caFile = caFile;
this.insecureSkipTlsVerify = insecureSkipTlsVerify;
this.plainHttp = plainHttp;
this.verify = verify;
this.keyring = keyring;
this.devel = devel;
this.passCredentials = passCredentials;
this.repositoryConfig = repositoryConfig;
this.repositoryCache = repositoryCache;
this.debug = debug;
}
}4. Add method to HelmLib interface (lib/api/src/main/java/com/marcnuri/helm/jni/HelmLib.java)
Result Pull(PullOptions options);5. Create PullCommand class (helm-java/src/main/java/com/marcnuri/helm/PullCommand.java)
package com.marcnuri.helm;
import com.marcnuri.helm.jni.HelmLib;
import com.marcnuri.helm.jni.PullOptions;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PullCommand extends HelmCommand<Path> {
private final String chart;
private String version;
private String repo;
private Path destination;
private boolean untar;
private String untarDir;
private String username;
private String password;
private Path certFile;
private Path keyFile;
private Path caFile;
private boolean insecureSkipTlsVerify;
private boolean plainHttp;
private boolean verify;
private Path keyring;
private boolean devel;
private boolean passCredentials;
private Path repositoryConfig;
private Path repositoryCache;
private boolean debug;
public PullCommand(HelmLib helmLib, String chart) {
super(helmLib);
this.chart = chart;
}
@Override
public Path call() {
String output = run(hl -> hl.Pull(new PullOptions(
chart,
version,
repo,
toString(destination),
toInt(untar),
untarDir,
username,
password,
toString(certFile),
toString(keyFile),
toString(caFile),
toInt(insecureSkipTlsVerify),
toInt(plainHttp),
toInt(verify),
toString(keyring),
toInt(devel),
toInt(passCredentials),
toString(repositoryConfig),
toString(repositoryCache),
toInt(debug)
))).out;
// Parse output to return the path to the downloaded chart
return Paths.get(output.trim());
}
/**
* Specify a version constraint for the chart version to use.
* This constraint can be a specific tag (e.g. 1.1.1) or a valid range (e.g. ^2.0.0).
* If not specified, the latest version is used.
*
* @param version the version constraint.
* @return this {@link PullCommand} instance.
*/
public PullCommand withVersion(String version) {
this.version = version;
return this;
}
/**
* Chart repository URL where the requested chart is located.
*
* @param repo the repository URL.
* @return this {@link PullCommand} instance.
*/
public PullCommand withRepo(String repo) {
this.repo = repo;
return this;
}
/**
* Location to write the chart. Defaults to current directory.
*
* @param destination the destination directory.
* @return this {@link PullCommand} instance.
*/
public PullCommand withDestination(Path destination) {
this.destination = destination;
return this;
}
/**
* If true, will untar the chart after downloading it.
*
* @return this {@link PullCommand} instance.
*/
public PullCommand untar() {
this.untar = true;
return this;
}
/**
* Directory into which the chart is expanded when using untar.
* Defaults to current directory.
*
* @param untarDir the directory name for the expanded chart.
* @return this {@link PullCommand} instance.
*/
public PullCommand withUntarDir(String untarDir) {
this.untarDir = untarDir;
return this;
}
/**
* Chart repository username for authentication.
*
* @param username the username.
* @return this {@link PullCommand} instance.
*/
public PullCommand withUsername(String username) {
this.username = username;
return this;
}
/**
* Chart repository password for authentication.
*
* @param password the password.
* @return this {@link PullCommand} instance.
*/
public PullCommand withPassword(String password) {
this.password = password;
return this;
}
/**
* Identify HTTPS client using this SSL certificate file.
*
* @param certFile the path to the certificate file.
* @return this {@link PullCommand} instance.
*/
public PullCommand withCertFile(Path certFile) {
this.certFile = certFile;
return this;
}
/**
* Identify HTTPS client using this SSL key file.
*
* @param keyFile the path to the key file.
* @return this {@link PullCommand} instance.
*/
public PullCommand withKeyFile(Path keyFile) {
this.keyFile = keyFile;
return this;
}
/**
* Verify certificates of HTTPS-enabled servers using this CA bundle.
*
* @param caFile the path to the CA bundle file.
* @return this {@link PullCommand} instance.
*/
public PullCommand withCaFile(Path caFile) {
this.caFile = caFile;
return this;
}
/**
* Skip TLS certificate verification for the chart download.
*
* @return this {@link PullCommand} instance.
*/
public PullCommand insecureSkipTlsVerify() {
this.insecureSkipTlsVerify = true;
return this;
}
/**
* Use insecure HTTP connections for the chart download.
*
* @return this {@link PullCommand} instance.
*/
public PullCommand plainHttp() {
this.plainHttp = true;
return this;
}
/**
* Verify the package before using it.
* The chart MUST have a provenance file and MUST pass verification.
*
* @return this {@link PullCommand} instance.
*/
public PullCommand verify() {
this.verify = true;
return this;
}
/**
* Location of public keys used for verification.
* Defaults to ~/.gnupg/pubring.gpg.
*
* @param keyring the path to the keyring file.
* @return this {@link PullCommand} instance.
*/
public PullCommand withKeyring(Path keyring) {
this.keyring = keyring;
return this;
}
/**
* Use development versions too. Equivalent to version '>0.0.0-0'.
* Ignored if --version is set.
*
* @return this {@link PullCommand} instance.
*/
public PullCommand devel() {
this.devel = true;
return this;
}
/**
* Pass credentials to all domains.
*
* @return this {@link PullCommand} instance.
*/
public PullCommand passCredentials() {
this.passCredentials = true;
return this;
}
/**
* Path to the file containing repository names and URLs.
*
* @param repositoryConfig the path to the repository config file.
* @return this {@link PullCommand} instance.
*/
public PullCommand withRepositoryConfig(Path repositoryConfig) {
this.repositoryConfig = repositoryConfig;
return this;
}
/**
* Path to the directory containing cached repository indexes.
*
* @param repositoryCache the path to the repository cache directory.
* @return this {@link PullCommand} instance.
*/
public PullCommand withRepositoryCache(Path repositoryCache) {
this.repositoryCache = repositoryCache;
return this;
}
/**
* Enable verbose output.
*
* @return this {@link PullCommand} instance.
*/
public PullCommand debug() {
this.debug = true;
return this;
}
}6. Add factory method in Helm.java
/**
* Download a chart from a repository.
*
* @param chart the chart reference (e.g., "bitnami/nginx" or OCI URL).
* @return a new {@link PullCommand} instance.
*/
public static PullCommand pull(String chart) {
return new PullCommand(HelmLibHolder.INSTANCE.helmLib(), chart);
}7. Add tests (helm-java/src/test/java/com/marcnuri/helm/HelmPullTest.java)
Acceptance Criteria
- Create
PullOptionsGo struct innative/internal/helm/pull.go - Implement
Pullfunction in Go usingaction.NewPullWithOpts - Add CGO export
Pullinnative/main.go - Create
PullOptions.javaJNA structure inlib/api - Add
Pullmethod toHelmLibinterface - Create
PullCommand.javainhelm-javamodule - Add
pull(String chart)factory method toHelm.java - Return the path to the downloaded chart file/directory
- Support OCI registry URLs
- Add unit tests for the new command
- Add integration tests (can use the built-in test repo server)
Tests
Following the project's testing philosophy (black-box, no mocks, nested structure):
HelmPullTestValidfromRepo- Pull chart from a repositorywithVersion- Pull specific versionwithDestination- Download to specific directorywithUntar- Download and unpackwithRepoUrl- Pull using explicit repo URLfromOciRegistry- Pull from OCI registry
InvalidnonExistentChart- Should throw appropriate exceptioninvalidVersion- Should handle non-existent version
CLI Options Mapping
| CLI Flag | Java Method | Description |
|---|---|---|
[chart] |
constructor arg | Chart reference |
-d, --destination |
withDestination(Path) |
Download location |
--untar |
untar() |
Unpack after download |
--untardir |
withUntarDir(String) |
Unpack directory name |
--version |
withVersion(String) |
Version constraint |
--repo |
withRepo(String) |
Repository URL |
--username |
withUsername(String) |
Repo username |
--password |
withPassword(String) |
Repo password |
--verify |
verify() |
Verify package |
--keyring |
withKeyring(Path) |
Verification keyring |
--prov |
- | (fetch provenance only, low priority) |
--devel |
devel() |
Include dev versions |
--pass-credentials |
passCredentials() |
Pass creds to all domains |
--ca-file |
withCaFile(Path) |
CA bundle |
--cert-file |
withCertFile(Path) |
SSL certificate |
--key-file |
withKeyFile(Path) |
SSL key |
--insecure-skip-tls-verify |
insecureSkipTlsVerify() |
Skip TLS verify |
--plain-http |
plainHttp() |
Use HTTP |
Additional Information
- CLI Reference: https://helm.sh/docs/helm/helm_pull/
- Helm SDK: Uses
action.NewPullWithOptsfromhelm.sh/helm/v3/pkg/action - Priority: Medium - Essential for offline installations and chart inspection
- Complexity: Medium - Many options but straightforward pattern
Notes
- The command should return the
Pathto the downloaded chart (either.tgzfile or unpacked directory) - When
--verifyis used, the chart MUST have a provenance file and pass verification - The
--provflag (fetch provenance without verification) is low priority and can be added later - OCI registry support should work with URLs like
oci://registry.example.com/charts/nginx
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels