feat: add --force flag support for helm upgrade (#505)

Add a "Force upgrade" checkbox in the upgrade modal footer that passes
the --force flag to helm upgrade, causing resources to be deleted and
recreated. Also fix the version selector flashing the URL input while
loading by showing a spinner.

Closes #505

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andrei Pohilko
2026-03-17 16:18:00 +00:00
parent 4fb2eb099a
commit c5ae60a779
4 changed files with 28 additions and 8 deletions

View File

@@ -47,6 +47,7 @@ export const InstallReleaseChartModal = ({
const navigate = useNavigateWithSearchParams();
const [userValues, setUserValues] = useState<string>("");
const [installError, setInstallError] = useState("");
const [forceUpgrade, setForceUpgrade] = useState(false);
const {
namespace: queryNamespace,
@@ -62,6 +63,7 @@ export const InstallReleaseChartModal = ({
error: versionsError,
data: _versions = [],
isSuccess,
isLoading: isLoadingVersions,
} = useGetVersions(chartName);
const [selectedVersionData, setSelectedVersionData] = useState<VersionData>();
@@ -166,6 +168,9 @@ export const InstallReleaseChartModal = ({
}
formData.append("version", selectedVersion || "");
formData.append("values", userValues || releaseValues || ""); // if userValues is empty, we use the release values
if (forceUpgrade) {
formData.append("force", "true");
}
const url = `/api/helm/releases/${
namespace ? namespace : "default"
}/${releaseName}`;
@@ -210,6 +215,18 @@ export const InstallReleaseChartModal = ({
/>
}
containerClassNames="w-full text-2xl h-2/3"
bottomContent={
isUpgrade ? (
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={forceUpgrade}
onChange={(e) => setForceUpgrade(e.target.checked)}
/>
Force upgrade
</label>
) : undefined
}
actions={[
{
id: "1",
@@ -223,7 +240,9 @@ export const InstallReleaseChartModal = ({
},
]}
>
{!useURLMode && versions && isNoneEmptyArray(versions) ? (
{isLoadingVersions ? (
<Spinner />
) : !useURLMode && versions && isNoneEmptyArray(versions) ? (
<div className="flex items-center gap-2">
<VersionToInstall
versions={versions}

View File

@@ -122,10 +122,9 @@ const Modal = ({
<div className="max-h-[calc(100vh_-_200px)] gap-6 overflow-y-auto p-4">
{children}
</div>
{bottomContent ? (
<div className="p-5 text-sm">{bottomContent}</div>
) : (
<div className="flex justify-end gap-2 rounded-b border-t border-gray-200 p-6">
<div className="flex items-center justify-between rounded-b border-t border-gray-200 p-6">
<div>{bottomContent}</div>
<div className="flex gap-2">
{actions?.map((action) => (
<button
key={action.id}
@@ -145,7 +144,7 @@ const Modal = ({
</button>
))}
</div>
)}
</div>
</div>
</div>
</div>

View File

@@ -389,7 +389,8 @@ func (h *HelmHandler) Upgrade(c *gin.Context) {
}
justTemplate := c.PostForm("preview") == "true"
rel, err := existing.Upgrade(repoChart, c.PostForm("version"), justTemplate, values)
force := c.PostForm("force") == "true"
rel, err := existing.Upgrade(repoChart, c.PostForm("version"), justTemplate, force, values)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return

View File

@@ -306,7 +306,7 @@ func (r *Release) GetRev(revNo int) (*Release, error) {
return nil, errorx.InternalError.New("No revision found for number %d", revNo)
}
func (r *Release) Upgrade(repoChart string, version string, justTemplate bool, values map[string]interface{}) (*release.Release, error) {
func (r *Release) Upgrade(repoChart string, version string, justTemplate bool, force bool, values map[string]interface{}) (*release.Release, error) {
r.mx.Lock()
defer r.mx.Unlock()
@@ -340,6 +340,7 @@ func (r *Release) Upgrade(repoChart string, version string, justTemplate bool, v
cmd.DryRunOption = "server"
}
cmd.ResetValues = true
cmd.Force = force
chrt, err := locateChart(cmd.ChartPathOptions, repoChart, r.Settings)
if err != nil {