Skip to content

Commit 36ebe5e

Browse files
committed
helm idempotent installs
1 parent 5c9767e commit 36ebe5e

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

rust/helm-sys/go-helm-wrapper/main.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,30 @@ func go_install_helm_release(releaseName *C.char, chartName *C.char, chartVersio
5858
return C.CString("")
5959
}
6060

61+
//export go_upgrade_or_install_helm_release
62+
func go_upgrade_or_install_helm_release(releaseName *C.char, chartName *C.char, chartVersion *C.char, valuesYaml *C.char, namespace *C.char, suppressOutput bool) *C.char {
63+
helmClient := getHelmClient(namespace, suppressOutput)
64+
65+
timeout, _ := time.ParseDuration("20m")
66+
chartSpec := gohelm.ChartSpec{
67+
ReleaseName: C.GoString(releaseName),
68+
ChartName: C.GoString(chartName),
69+
Version: C.GoString(chartVersion),
70+
ValuesYaml: C.GoString(valuesYaml),
71+
Namespace: C.GoString(namespace),
72+
UpgradeCRDs: true,
73+
Wait: true,
74+
Timeout: timeout,
75+
Force: true,
76+
}
77+
78+
if _, err := helmClient.InstallOrUpgradeChart(context.Background(), &chartSpec, nil); err != nil {
79+
return C.CString(fmt.Sprintf("%s%s", HELM_ERROR_PREFIX, err))
80+
}
81+
82+
return C.CString("")
83+
}
84+
6185
//export go_uninstall_helm_release
6286
func go_uninstall_helm_release(releaseName *C.char, namespace *C.char, suppressOutput bool) *C.char {
6387
helmClient := getHelmClient(namespace, suppressOutput)

rust/helm-sys/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,34 @@ pub fn install_helm_release(
3737
}
3838
}
3939

40+
pub fn upgrade_or_install_helm_release(
41+
release_name: &str,
42+
chart_name: &str,
43+
chart_version: &str,
44+
values_yaml: &str,
45+
namespace: &str,
46+
suppress_output: bool,
47+
) -> String {
48+
let release_name = CString::new(release_name).unwrap();
49+
let chart_name = CString::new(chart_name).unwrap();
50+
let chart_version = CString::new(chart_version).unwrap();
51+
let values_yaml = CString::new(values_yaml).unwrap();
52+
let namespace = CString::new(namespace).unwrap();
53+
54+
unsafe {
55+
let c = go_upgrade_or_install_helm_release(
56+
release_name.as_ptr() as *mut c_char,
57+
chart_name.as_ptr() as *mut c_char,
58+
chart_version.as_ptr() as *mut c_char,
59+
values_yaml.as_ptr() as *mut c_char,
60+
namespace.as_ptr() as *mut c_char,
61+
suppress_output as u8,
62+
);
63+
64+
cstr_ptr_to_string(c)
65+
}
66+
}
67+
4068
pub fn uninstall_helm_release(
4169
release_name: &str,
4270
namespace: &str,

rust/stackable-cockpit/src/helm.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ pub enum Error {
6161
#[snafu(display("failed to install Helm release"))]
6262
InstallRelease { source: InstallReleaseError },
6363

64+
#[snafu(display("failed to upgrade/install Helm release"))]
65+
UpgradeRelease { source: InstallReleaseError },
66+
6467
#[snafu(display("failed to uninstall Helm release ({error})"))]
6568
UninstallRelease { error: String },
6669
}
@@ -248,6 +251,78 @@ pub fn install_release_from_repo_or_registry(
248251
})
249252
}
250253

254+
/// Upgrades a Helm release from a repo or registry.
255+
///
256+
/// This function expects the fully qualified Helm release name. In case of our
257+
/// operators this is: `<PRODUCT_NAME>-operator`.
258+
#[instrument(skip(values_yaml), fields(with_values = values_yaml.is_some(), indicatif.pb_show = true))]
259+
pub fn upgrade_or_install_release_from_repo_or_registry(
260+
release_name: &str,
261+
ChartVersion {
262+
chart_source,
263+
chart_name,
264+
chart_version,
265+
}: ChartVersion,
266+
values_yaml: Option<&str>,
267+
namespace: &str,
268+
suppress_output: bool,
269+
) -> Result<InstallReleaseStatus, Error> {
270+
// Ideally, each Helm invocation would spawn_blocking instead in/around helm_sys,
271+
// but that requires a larger refactoring
272+
block_in_place(|| {
273+
debug!("Install/Upgrade Helm release from repo");
274+
Span::current()
275+
.pb_set_message(format!("Installing/Upgrading {chart_name} Helm chart").as_str());
276+
277+
if check_release_exists(release_name, namespace)? {
278+
let release = get_release(release_name, namespace)?.ok_or(Error::InstallRelease {
279+
source: InstallReleaseError::NoSuchRelease {
280+
name: release_name.to_owned(),
281+
},
282+
})?;
283+
284+
let current_version = release.version;
285+
286+
match chart_version {
287+
Some(chart_version) => {
288+
if chart_version == current_version {
289+
return Ok(InstallReleaseStatus::ReleaseAlreadyInstalledWithVersion {
290+
requested_version: chart_version.to_string(),
291+
release_name: release_name.to_string(),
292+
current_version,
293+
});
294+
}
295+
}
296+
None => {
297+
return Ok(InstallReleaseStatus::ReleaseAlreadyInstalledUnspecified {
298+
release_name: release_name.to_string(),
299+
current_version,
300+
});
301+
}
302+
}
303+
}
304+
305+
let full_chart_name = format!("{chart_source}/{chart_name}");
306+
let chart_version = chart_version.unwrap_or(HELM_DEFAULT_CHART_VERSION);
307+
308+
debug!(
309+
release_name,
310+
chart_version, full_chart_name, "Installing Helm release"
311+
);
312+
313+
upgrade_release(
314+
release_name,
315+
&full_chart_name,
316+
chart_version,
317+
values_yaml,
318+
namespace,
319+
suppress_output,
320+
)?;
321+
322+
Ok(InstallReleaseStatus::Installed(release_name.to_string()))
323+
})
324+
}
325+
251326
/// Installs a Helm release.
252327
///
253328
/// This function expects the fully qualified Helm release name. In case of our
@@ -281,6 +356,43 @@ fn install_release(
281356
Ok(())
282357
}
283358

359+
/// Upgrades a Helm release.
360+
/// If a release with the specified `chart_name` does not already exist,
361+
/// this function installs it instead.
362+
///
363+
/// This function expects the fully qualified Helm release name. In case of our
364+
/// operators this is: `<PRODUCT_NAME>-operator`.
365+
#[instrument(fields(with_values = values_yaml.is_some()))]
366+
fn upgrade_release(
367+
release_name: &str,
368+
chart_name: &str,
369+
chart_version: &str,
370+
values_yaml: Option<&str>,
371+
namespace: &str,
372+
suppress_output: bool,
373+
) -> Result<(), Error> {
374+
let result = helm_sys::upgrade_or_install_helm_release(
375+
release_name,
376+
chart_name,
377+
chart_version,
378+
values_yaml.unwrap_or(""),
379+
namespace,
380+
suppress_output,
381+
);
382+
383+
if let Some(error) = helm_sys::to_helm_error(&result) {
384+
error!(
385+
"Go wrapper function go_upgrade_or_install_helm_release encountered an error: {error}"
386+
);
387+
388+
return Err(Error::UpgradeRelease {
389+
source: InstallReleaseError::HelmWrapper { error },
390+
});
391+
}
392+
393+
Ok(())
394+
}
395+
284396
/// Uninstall a Helm release.
285397
///
286398
/// This function expects the fully qualified Helm release name. In case of our

rust/stackable-cockpit/src/platform/manifests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub trait InstallManifestsExt {
116116
.context(SerializeOptionsSnafu)?;
117117

118118
// Install the Helm chart using the Helm wrapper
119-
helm::install_release_from_repo_or_registry(
119+
helm::upgrade_or_install_release_from_repo_or_registry(
120120
&helm_chart.release_name,
121121
helm::ChartVersion {
122122
chart_source: &helm_chart.repo.name,

0 commit comments

Comments
 (0)