Skip to content

Commit c1a37f6

Browse files
committed
feat: add support for "helm status" command (#349)
Signed-off-by: Marc Nuri <marc@marcnuri.com>
1 parent 94ded5a commit c1a37f6

File tree

9 files changed

+657
-0
lines changed

9 files changed

+657
-0
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,32 @@ String result = new Helm(Paths.get("path", "to", "chart")).show()
607607
.call();
608608
```
609609

610+
### Status
611+
612+
Equivalent of [`helm status`](https://helm.sh/docs/helm/helm_status/).
613+
614+
Displays the status of the named release.
615+
616+
``` java
617+
Release result = Helm.status("release-name")
618+
// Optionally get the status of a specific revision
619+
.withRevision(1)
620+
// Optionally specify the Kubernetes namespace
621+
.withNamespace("namespace")
622+
// Optionally specify the path to the kubeconfig file to use for CLI requests
623+
.withKubeConfig(Paths.get("path", "to", "kubeconfig"))
624+
// Optionally set the contents of the kubeconfig file as a string (takes precedence over the path)
625+
.withKubeConfigContents("apiVersion: v1\nkind: Config\nclusters:\n...")
626+
.call();
627+
result.getName(); // release name
628+
result.getNamespace(); // Kubernetes namespace
629+
result.getStatus(); // status (deployed, failed, etc.)
630+
result.getRevision(); // revision number
631+
result.getLastDeployed(); // last deployment time
632+
result.getChart(); // chart name and version
633+
result.getAppVersion(); // app version
634+
```
635+
610636
### Template
611637

612638
Equivalent of [`helm template`](https://helm.sh/docs/helm/helm_template/).

helm-java/src/main/java/com/marcnuri/helm/Helm.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,25 @@ public static GetCommand get(String releaseName) {
107107
return new GetCommand(HelmLibHolder.INSTANCE, releaseName);
108108
}
109109

110+
/**
111+
* This command displays the status of the named release.
112+
*
113+
* <p>The status shows information such as:
114+
* <ul>
115+
* <li>Last deployment time</li>
116+
* <li>Kubernetes namespace</li>
117+
* <li>Current state (deployed, failed, etc.)</li>
118+
* <li>Revision number</li>
119+
* <li>Chart name and version</li>
120+
* </ul>
121+
*
122+
* @param releaseName the name of the release.
123+
* @return the {@link StatusCommand} callable command.
124+
*/
125+
public static StatusCommand status(String releaseName) {
126+
return new StatusCommand(HelmLibHolder.INSTANCE, releaseName);
127+
}
128+
110129
/**
111130
* This command packages a chart into a versioned chart archive file.
112131
* If a path is given, this will look at that path for a chart (which must contain a Chart.yaml file) and then package that directory.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2026 Marc Nuri
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.marcnuri.helm;
18+
19+
import com.marcnuri.helm.jni.HelmLib;
20+
import com.marcnuri.helm.jni.StatusOptions;
21+
22+
import java.nio.file.Path;
23+
24+
import static com.marcnuri.helm.Release.parseSingle;
25+
26+
/**
27+
* Displays the status of the named release.
28+
*
29+
* <p>This command shows the current state of a release including:
30+
* <ul>
31+
* <li>The last deployment time</li>
32+
* <li>The Kubernetes namespace</li>
33+
* <li>The current state (deployed, failed, etc.)</li>
34+
* <li>The revision number</li>
35+
* <li>The chart name and version</li>
36+
* <li>Any notes provided by the chart</li>
37+
* </ul>
38+
*
39+
* @author Marc Nuri
40+
*/
41+
public class StatusCommand extends HelmCommand<Release> {
42+
43+
private final String releaseName;
44+
private int revision;
45+
private String namespace;
46+
private Path kubeConfig;
47+
private String kubeConfigContents;
48+
49+
public StatusCommand(HelmLib helmLib, String releaseName) {
50+
super(helmLib);
51+
this.releaseName = releaseName;
52+
}
53+
54+
/**
55+
* Execute the status command.
56+
*
57+
* @return a {@link Release} containing the status information.
58+
*/
59+
@Override
60+
public Release call() {
61+
return parseSingle(run(hl -> hl.Status(new StatusOptions(
62+
releaseName,
63+
revision,
64+
namespace,
65+
toString(kubeConfig),
66+
kubeConfigContents
67+
))));
68+
}
69+
70+
/**
71+
* Get the status of the named release with the specified revision.
72+
*
73+
* <p>If not specified, the latest release is returned.
74+
*
75+
* @param revision the revision number.
76+
* @return this {@link StatusCommand} instance.
77+
*/
78+
public StatusCommand withRevision(int revision) {
79+
this.revision = revision;
80+
return this;
81+
}
82+
83+
/**
84+
* Kubernetes namespace scope for this request.
85+
*
86+
* @param namespace the Kubernetes namespace for this request.
87+
* @return this {@link StatusCommand} instance.
88+
*/
89+
public StatusCommand withNamespace(String namespace) {
90+
this.namespace = namespace;
91+
return this;
92+
}
93+
94+
/**
95+
* Set the path to the ~/.kube/config file to use.
96+
*
97+
* @param kubeConfig the path to kube config file.
98+
* @return this {@link StatusCommand} instance.
99+
*/
100+
public StatusCommand withKubeConfig(Path kubeConfig) {
101+
this.kubeConfig = kubeConfig;
102+
return this;
103+
}
104+
105+
/**
106+
* Set the kube config to use.
107+
*
108+
* @param kubeConfigContents the contents of the kube config file.
109+
* @return this {@link StatusCommand} instance.
110+
*/
111+
public StatusCommand withKubeConfigContents(String kubeConfigContents) {
112+
this.kubeConfigContents = kubeConfigContents;
113+
return this;
114+
}
115+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Copyright 2026 Marc Nuri
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.marcnuri.helm;
18+
19+
import com.dajudge.kindcontainer.KindContainer;
20+
import com.dajudge.kindcontainer.KindContainerVersion;
21+
import org.junit.jupiter.api.AfterAll;
22+
import org.junit.jupiter.api.BeforeAll;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Nested;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.api.condition.EnabledOnOs;
27+
import org.junit.jupiter.api.condition.OS;
28+
import org.junit.jupiter.api.io.TempDir;
29+
30+
import java.io.IOException;
31+
import java.nio.charset.StandardCharsets;
32+
import java.nio.file.Files;
33+
import java.nio.file.Path;
34+
import java.nio.file.StandardOpenOption;
35+
import java.time.LocalDate;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
39+
40+
/**
41+
* Tests for the helm status command.
42+
*
43+
* @author Marc Nuri
44+
*/
45+
@EnabledOnOs(OS.LINUX)
46+
class HelmStatusTest {
47+
48+
static KindContainer<?> kindContainer;
49+
static String kubeConfigContents;
50+
static Path kubeConfigFile;
51+
52+
private Helm helm;
53+
54+
@BeforeAll
55+
static void setUpKubernetes(@TempDir Path tempDir) throws IOException {
56+
kindContainer = new KindContainer<>(KindContainerVersion.VERSION_1_31_0);
57+
kindContainer.start();
58+
kubeConfigContents = kindContainer.getKubeconfig();
59+
kubeConfigFile = tempDir.resolve("config.yaml");
60+
Files.write(kubeConfigFile, kubeConfigContents.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
61+
}
62+
63+
@AfterAll
64+
static void tearDownKubernetes() {
65+
kindContainer.stop();
66+
}
67+
68+
@BeforeEach
69+
void setUp(@TempDir Path tempDir) {
70+
helm = Helm.create().withName("test").withDir(tempDir).call();
71+
}
72+
73+
@Nested
74+
class Valid {
75+
76+
@Test
77+
void withName() {
78+
helm.install()
79+
.withKubeConfig(kubeConfigFile)
80+
.withName("status-with-name")
81+
.call();
82+
final Release result = Helm.status("status-with-name")
83+
.withKubeConfig(kubeConfigFile)
84+
.call();
85+
assertThat(result)
86+
.returns("status-with-name", Release::getName)
87+
.returns("deployed", Release::getStatus)
88+
.returns("1", Release::getRevision)
89+
.satisfies(r -> assertThat(r.getLastDeployed()).matches(d -> d.toLocalDate().equals(LocalDate.now())))
90+
.returns("test-0.1.0", Release::getChart)
91+
.returns("1.16.0", Release::getAppVersion)
92+
.extracting(Release::getOutput).asString()
93+
.contains(
94+
"NAME: status-with-name\n",
95+
"LAST DEPLOYED: ",
96+
"STATUS: deployed",
97+
"REVISION: 1",
98+
"DESCRIPTION: Install complete"
99+
);
100+
}
101+
102+
@Test
103+
void withNamespace() {
104+
helm.install()
105+
.withKubeConfig(kubeConfigFile)
106+
.withName("status-with-namespace")
107+
.withNamespace("status-namespace")
108+
.createNamespace()
109+
.call();
110+
final Release result = Helm.status("status-with-namespace")
111+
.withKubeConfig(kubeConfigFile)
112+
.withNamespace("status-namespace")
113+
.call();
114+
assertThat(result)
115+
.returns("status-with-namespace", Release::getName)
116+
.returns("status-namespace", Release::getNamespace)
117+
.returns("deployed", Release::getStatus);
118+
}
119+
120+
@Test
121+
void withRevision() {
122+
helm.install()
123+
.withKubeConfig(kubeConfigFile)
124+
.withName("status-with-revision")
125+
.set("replicaCount", "1")
126+
.call();
127+
helm.upgrade()
128+
.withKubeConfig(kubeConfigFile)
129+
.withName("status-with-revision")
130+
.set("replicaCount", "2")
131+
.call();
132+
final Release result = Helm.status("status-with-revision")
133+
.withKubeConfig(kubeConfigFile)
134+
.withRevision(1)
135+
.call();
136+
assertThat(result)
137+
.returns("status-with-revision", Release::getName)
138+
.returns("1", Release::getRevision)
139+
.returns("superseded", Release::getStatus);
140+
}
141+
142+
@Test
143+
void withKubeConfigContents() {
144+
helm.install()
145+
.withKubeConfigContents(kubeConfigContents)
146+
.withName("status-kube-config-contents")
147+
.call();
148+
final Release result = Helm.status("status-kube-config-contents")
149+
.withKubeConfigContents(kubeConfigContents)
150+
.call();
151+
assertThat(result)
152+
.returns("status-kube-config-contents", Release::getName)
153+
.returns("deployed", Release::getStatus);
154+
}
155+
156+
@Test
157+
void afterUpgrade() {
158+
helm.install()
159+
.withKubeConfig(kubeConfigFile)
160+
.withName("status-after-upgrade")
161+
.call();
162+
helm.upgrade()
163+
.withKubeConfig(kubeConfigFile)
164+
.withName("status-after-upgrade")
165+
.call();
166+
final Release result = Helm.status("status-after-upgrade")
167+
.withKubeConfig(kubeConfigFile)
168+
.call();
169+
assertThat(result)
170+
.returns("status-after-upgrade", Release::getName)
171+
.returns("2", Release::getRevision)
172+
.returns("deployed", Release::getStatus)
173+
.extracting(Release::getOutput).asString()
174+
.contains("DESCRIPTION: Upgrade complete");
175+
}
176+
}
177+
178+
@Nested
179+
class Invalid {
180+
181+
@Test
182+
void nonExistentRelease() {
183+
final StatusCommand status = Helm.status("non-existent-release")
184+
.withKubeConfig(kubeConfigFile);
185+
assertThatThrownBy(status::call)
186+
.message()
187+
.contains("not found");
188+
}
189+
190+
@Test
191+
void invalidRevision() {
192+
helm.install()
193+
.withKubeConfig(kubeConfigFile)
194+
.withName("status-invalid-revision")
195+
.call();
196+
final StatusCommand status = Helm.status("status-invalid-revision")
197+
.withKubeConfig(kubeConfigFile)
198+
.withRevision(999);
199+
assertThatThrownBy(status::call)
200+
.message()
201+
.contains("not found");
202+
}
203+
}
204+
}

lib/api/src/main/java/com/marcnuri/helm/jni/HelmLib.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ public interface HelmLib extends Library {
6969

7070
Result Show(ShowOptions options);
7171

72+
Result Status(StatusOptions options);
73+
7274
Result Template(TemplateOptions options);
7375

7476
Result Test(TestOptions options);

0 commit comments

Comments
 (0)