-
Notifications
You must be signed in to change notification settings - Fork 20
Description
Description
The helm rollback command is currently not implemented in helm-java. This command rolls back a release to a previous revision, which is critical for recovery when deployments fail or need to be reverted.
Background
The helm rollback command:
- Takes a release name as the first argument
- Optionally takes a revision number as the second argument
- If revision is omitted or set to 0, it rolls back to the immediately previous release
- Supports waiting for resources to be ready before marking rollback successful
- Can clean up resources created during a failed rollback
This is one of the most critical Helm commands for maintaining cluster stability and enabling rapid recovery. See the official documentation.
Related Issues
This addresses part of issue #97 which mentions missing rollback command but lacks implementation details.
Proposed API
Following the existing patterns in the codebase (similar to UninstallCommand, UpgradeCommand), the implementation should provide a fluent API:
// Basic usage - rollback to previous revision
Release result = Helm.rollback("my-release")
.withKubeConfig(kubeConfigPath)
.call();
// Rollback to specific revision
Release result = Helm.rollback("my-release")
.toRevision(3)
.withKubeConfig(kubeConfigPath)
.call();
// With wait and timeout
Release result = Helm.rollback("my-release")
.toRevision(2)
.waitReady()
.withTimeout(300)
.withKubeConfig(kubeConfigPath)
.call();
// With cleanup on failure
Release result = Helm.rollback("my-release")
.toRevision(1)
.cleanupOnFail()
.withNamespace("production")
.withKubeConfig(kubeConfigPath)
.call();
// Dry run
Release result = Helm.rollback("my-release")
.toRevision(2)
.dryRun()
.withKubeConfig(kubeConfigPath)
.call();
// With all options
Release result = Helm.rollback("my-release")
.toRevision(5)
.cleanupOnFail()
.force()
.noHooks()
.waitReady()
.withTimeout(600)
.withHistoryMax(10)
.withNamespace("my-namespace")
.withKubeConfig(kubeConfigPath)
.debug()
.call();Implementation Guide
1. Create Go Options struct and function (native/internal/helm/rollback.go)
package helm
import (
"time"
"helm.sh/helm/v3/pkg/action"
)
type RollbackOptions struct {
ReleaseName string
Revision int
CleanupOnFail bool
DryRun bool
Force bool
NoHooks bool
Wait bool
Timeout int // seconds
HistoryMax int
Namespace string
KubeConfig string
KubeConfigContents string
Debug bool
}
func Rollback(options *RollbackOptions) (string, error) {
var log action.DebugLog = nil
if options.Debug {
log = debugLog
}
cfg, err := NewCfg(&CfgOptions{
KubeConfig: options.KubeConfig,
KubeConfigContents: options.KubeConfigContents,
Namespace: options.Namespace,
Log: log,
})
if err != nil {
return "", err
}
client := action.NewRollback(cfg)
client.Version = options.Revision // 0 means previous revision
client.CleanupOnFail = options.CleanupOnFail
client.DryRun = options.DryRun
client.Force = options.Force
client.DisableHooks = options.NoHooks
client.Wait = options.Wait
if options.Timeout > 0 {
client.Timeout = time.Duration(options.Timeout) * time.Second
}
if options.HistoryMax > 0 {
client.MaxHistory = options.HistoryMax
}
err = client.Run(options.ReleaseName)
if err != nil {
return "", err
}
// Get the release after rollback to return status
statusClient := action.NewStatus(cfg)
rel, err := statusClient.Run(options.ReleaseName)
if err != nil {
return "", err
}
return StatusReport(rel, true, options.Debug), nil
}2. Add CGO export in native/main.go
Add the C struct definition:
struct RollbackOptions {
char* releaseName;
int revision;
int cleanupOnFail;
int dryRun;
int force;
int noHooks;
int wait;
int timeout;
int historyMax;
char* namespace;
char* kubeConfig;
char* kubeConfigContents;
int debug;
};Add the export function:
//export Rollback
func Rollback(options *C.struct_RollbackOptions) C.Result {
return result(helm.Rollback(&helm.RollbackOptions{
ReleaseName: C.GoString(options.releaseName),
Revision: int(options.revision),
CleanupOnFail: options.cleanupOnFail == 1,
DryRun: options.dryRun == 1,
Force: options.force == 1,
NoHooks: options.noHooks == 1,
Wait: options.wait == 1,
Timeout: int(options.timeout),
HistoryMax: int(options.historyMax),
Namespace: C.GoString(options.namespace),
KubeConfig: C.GoString(options.kubeConfig),
KubeConfigContents: C.GoString(options.kubeConfigContents),
Debug: options.debug == 1,
}))
}3. Create JNA Options class (lib/api/src/main/java/com/marcnuri/helm/jni/RollbackOptions.java)
package com.marcnuri.helm.jni;
import com.sun.jna.Structure;
@Structure.FieldOrder({
"releaseName",
"revision",
"cleanupOnFail",
"dryRun",
"force",
"noHooks",
"wait",
"timeout",
"historyMax",
"namespace",
"kubeConfig",
"kubeConfigContents",
"debug"
})
public class RollbackOptions extends Structure {
public String releaseName;
public int revision;
public int cleanupOnFail;
public int dryRun;
public int force;
public int noHooks;
public int wait;
public int timeout;
public int historyMax;
public String namespace;
public String kubeConfig;
public String kubeConfigContents;
public int debug;
public RollbackOptions(
String releaseName,
int revision,
int cleanupOnFail,
int dryRun,
int force,
int noHooks,
int wait,
int timeout,
int historyMax,
String namespace,
String kubeConfig,
String kubeConfigContents,
int debug
) {
this.releaseName = releaseName;
this.revision = revision;
this.cleanupOnFail = cleanupOnFail;
this.dryRun = dryRun;
this.force = force;
this.noHooks = noHooks;
this.wait = wait;
this.timeout = timeout;
this.historyMax = historyMax;
this.namespace = namespace;
this.kubeConfig = kubeConfig;
this.kubeConfigContents = kubeConfigContents;
this.debug = debug;
}
}4. Add method to HelmLib interface (lib/api/src/main/java/com/marcnuri/helm/jni/HelmLib.java)
Result Rollback(RollbackOptions options);5. Create RollbackCommand class (helm-java/src/main/java/com/marcnuri/helm/RollbackCommand.java)
package com.marcnuri.helm;
import com.marcnuri.helm.jni.HelmLib;
import com.marcnuri.helm.jni.RollbackOptions;
import java.nio.file.Path;
public class RollbackCommand extends HelmCommand<Release> {
private final String releaseName;
private int revision;
private boolean cleanupOnFail;
private boolean dryRun;
private boolean force;
private boolean noHooks;
private boolean wait;
private int timeout;
private int historyMax;
private String namespace;
private Path kubeConfig;
private String kubeConfigContents;
private boolean debug;
public RollbackCommand(HelmLib helmLib, String releaseName) {
super(helmLib);
this.releaseName = releaseName;
}
@Override
public Release call() {
return Release.parseSingle(run(hl -> hl.Rollback(new RollbackOptions(
releaseName,
revision,
toInt(cleanupOnFail),
toInt(dryRun),
toInt(force),
toInt(noHooks),
toInt(wait),
timeout,
historyMax,
namespace,
toString(kubeConfig),
kubeConfigContents,
toInt(debug)
))));
}
/**
* Rollback to a specific revision.
* If set to 0 or not called, rolls back to the previous release.
*
* @param revision the revision number to rollback to.
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand toRevision(int revision) {
this.revision = revision;
return this;
}
/**
* Allow deletion of new resources created in this rollback when rollback fails.
*
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand cleanupOnFail() {
this.cleanupOnFail = true;
return this;
}
/**
* Simulate a rollback.
*
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand dryRun() {
this.dryRun = true;
return this;
}
/**
* Force resource update through delete/recreate if needed.
*
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand force() {
this.force = true;
return this;
}
/**
* Prevent hooks from running during rollback.
*
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand noHooks() {
this.noHooks = true;
return this;
}
/**
* Wait until all resources are in a ready state before marking the rollback as successful.
*
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand waitReady() {
this.wait = true;
return this;
}
/**
* Time to wait for any individual Kubernetes operation (in seconds).
* Default is 300 seconds.
*
* @param timeout timeout in seconds.
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand withTimeout(int timeout) {
this.timeout = timeout;
return this;
}
/**
* Limit the maximum number of revisions saved per release.
* Use 0 for no limit. Default is 10.
*
* @param historyMax maximum number of revisions to keep.
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand withHistoryMax(int historyMax) {
this.historyMax = historyMax;
return this;
}
/**
* Kubernetes namespace scope for this request.
*
* @param namespace the Kubernetes namespace for this request.
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand withNamespace(String namespace) {
this.namespace = namespace;
return this;
}
/**
* Set the path to the ~/.kube/config file to use.
*
* @param kubeConfig the path to kube config file.
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand 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 RollbackCommand} instance.
*/
public RollbackCommand withKubeConfigContents(String kubeConfigContents) {
this.kubeConfigContents = kubeConfigContents;
return this;
}
/**
* Enable verbose output.
*
* @return this {@link RollbackCommand} instance.
*/
public RollbackCommand debug() {
this.debug = true;
return this;
}
}6. Add factory method in Helm.java
/**
* Roll back a release to a previous revision.
*
* @param releaseName name of the release.
* @return a new {@link RollbackCommand} instance.
*/
public static RollbackCommand rollback(String releaseName) {
return new RollbackCommand(HelmLibHolder.INSTANCE.helmLib(), releaseName);
}7. Add tests (helm-java/src/test/java/com/marcnuri/helm/HelmRollbackTest.java)
Acceptance Criteria
- Create
RollbackOptionsGo struct innative/internal/helm/rollback.go - Implement
Rollbackfunction in Go usingaction.NewRollback - Add CGO export
Rollbackinnative/main.go - Create
RollbackOptions.javaJNA structure inlib/api - Add
Rollbackmethod toHelmLibinterface - Create
RollbackCommand.javainhelm-javamodule - Add
rollback(String releaseName)factory method toHelm.java - Returns
Releaseobject with updated status after rollback - Add unit tests for the new command
- Add integration tests using Testcontainers/KinD
Tests
Following the project's testing philosophy (black-box, no mocks, nested structure):
HelmRollbackTestValidtoPreviousRevision- Rollback without specifying revision (goes to previous)toSpecificRevision- Rollback to a specific revision numberwithDryRun- Simulate rollback without persistingwithWait- Wait for resources to be readywithNamespace- Rollback with explicit namespacewithKubeConfigContents- Use inline kubeconfig
InvalidnonExistentRelease- Should throw appropriate exceptioninvalidRevision- Should handle non-existent revision
Additional Information
- CLI Reference: https://helm.sh/docs/helm/helm_rollback/
- Helm SDK: Uses
action.NewRollbackfromhelm.sh/helm/v3/pkg/action - Priority: High - Critical for recovery and maintaining cluster stability
- Complexity: Medium - Similar pattern to
UninstallCommand - Dependencies: Should be implemented after
helm historyfor complete workflow
CLI Options Mapping
| CLI Flag | Java Method | Description |
|---|---|---|
[REVISION] |
toRevision(int) |
Target revision (0 = previous) |
--cleanup-on-fail |
cleanupOnFail() |
Delete new resources on failure |
--dry-run |
dryRun() |
Simulate rollback |
--force |
force() |
Force resource updates |
--no-hooks |
noHooks() |
Prevent hooks from running |
--timeout |
withTimeout(int) |
Wait timeout in seconds |
--wait |
waitReady() |
Wait for resources to be ready |
--history-max |
withHistoryMax(int) |
Max revisions to keep |
-n, --namespace |
withNamespace(String) |
Kubernetes namespace |
Notes
- The rollback returns a
Releaseobject showing the state after rollback - The existing
Release.parseSingle()can be reused StatusReportfunction fromhelm.goformats the response- Should work in conjunction with
helm historyto identify target revisions