diff --git a/changelogs/fragments/589-helm-uninstall-chart-releases-with-statuses-different-than-deployed.yaml b/changelogs/fragments/589-helm-uninstall-chart-releases-with-statuses-different-than-deployed.yaml new file mode 100644 index 00000000..d206fe80 --- /dev/null +++ b/changelogs/fragments/589-helm-uninstall-chart-releases-with-statuses-different-than-deployed.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - helm - fix issue occurring when uninstalling chart with statues others than 'deployed' (https://github.com/ansible-collections/kubernetes.core/issues/319). diff --git a/plugins/modules/helm.py b/plugins/modules/helm.py index 8c6d5dd1..baa12813 100644 --- a/plugins/modules/helm.py +++ b/plugins/modules/helm.py @@ -432,14 +432,20 @@ def get_release(state, release_name): return None -def get_release_status(module, release_name): +def get_release_status(module, release_name, all_status=False): """ - Get Release state from deployed release + Get Release state from all release status (deployed, failed, pending-install, etc) """ - list_command = ( - module.get_helm_binary() + " list --output=yaml --filter " + release_name - ) + list_command = [ + module.get_helm_binary(), + "list", + "--output=yaml", + "--filter", + release_name, + ] + if all_status: + list_command.append("--all") rc, out, err = module.run_helm_command(list_command) @@ -773,29 +779,31 @@ def main(): run_repo_update(module) # Get real/deployed release status - release_status = get_release_status(module, release_name) + all_status = release_state == "absent" + release_status = get_release_status(module, release_name, all_status=all_status) helm_cmd = module.get_helm_binary() opt_result = {} if release_state == "absent" and release_status is not None: - if replace: - module.fail_json(msg="replace is not applicable when state is absent") + # skip release statuses 'uninstalled' and 'uninstalling' + if not release_status["status"].startswith("uninstall"): + if replace: + module.fail_json(msg="replace is not applicable when state is absent") - if wait: - helm_version = module.get_helm_version() - if LooseVersion(helm_version) < LooseVersion("3.7.0"): - opt_result["warnings"] = [] - opt_result["warnings"].append( - "helm uninstall support option --wait for helm release >= 3.7.0" - ) - wait = False + if wait: + helm_version = module.get_helm_version() + if LooseVersion(helm_version) < LooseVersion("3.7.0"): + opt_result["warnings"] = [] + opt_result["warnings"].append( + "helm uninstall support option --wait for helm release >= 3.7.0" + ) + wait = False - helm_cmd = delete( - helm_cmd, release_name, purge, disable_hook, wait, wait_timeout - ) - changed = True + helm_cmd = delete( + helm_cmd, release_name, purge, disable_hook, wait, wait_timeout + ) + changed = True elif release_state == "present": - if chart_version is not None: helm_cmd += " --version=" + chart_version @@ -956,7 +964,7 @@ def main(): changed=changed, stdout=out, stderr=err, - status=get_release_status(module, release_name), + status=get_release_status(module, release_name, all_status=True), command=helm_cmd, **opt_result, ) diff --git a/tests/integration/targets/helm/library/helm_test_pending.py b/tests/integration/targets/helm/library/helm_test_pending.py new file mode 100644 index 00000000..c0f3935f --- /dev/null +++ b/tests/integration/targets/helm/library/helm_test_pending.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: helm_test_pending +short_description: created pending-install release +author: + - Aubin Bikouo (@abikouo) +requirements: + - "helm (https://github.com/helm/helm/releases)" +description: + - This module is used to create a pending install release for integration testing + - The scope of this module is the integration testing of the kubernetes.core collection only. +options: + binary_path: + description: + - The path of a helm binary to use. + required: true + type: path + chart_ref: + description: + - chart reference on chart repository (e.g. my-repo/my-chart-ref) + required: true + type: str + chart_release: + description: + - Release name to manage. + required: true + type: str + chart_release_namespace: + description: + - Kubernetes namespace where the chart should be installed. + required: true + type: str +""" + +EXAMPLES = r""" +""" + +RETURN = r""" +""" + +import subprocess +import json +import time +from ansible.module_utils.basic import AnsibleModule + + +class HelmReleaseNotFoundError(Exception): + def __init__(self, message): + super().__init__(message) + + +def create_pending_install_release(helm_binary, chart_ref, chart_release, namespace): + # create pending-install release + command = [ + helm_binary, + "install", + chart_release, + chart_ref, + "--namespace", + namespace, + "--wait", + ] + proc = subprocess.Popen(command) + time.sleep(2) + proc.kill() + # ensure release status is pending-install + command = [ + helm_binary, + "list", + "--all", + "--output=json", + "--namespace", + namespace, + "--filter", + chart_release, + ] + cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = cmd.communicate() + + data = json.loads(out) + if not data: + error = "Release %s not found." % chart_release + raise HelmReleaseNotFoundError(message=error) + return data[0]["status"] == "pending-install", data[0]["status"] + + +def main(): + module = AnsibleModule( + argument_spec=dict( + binary_path=dict(type="path", required=True), + chart_ref=dict(type="str", required=True), + chart_release=dict(type="str", required=True), + chart_release_namespace=dict(type="str", required=True), + ), + ) + + params = dict( + helm_binary=module.params.get("binary_path"), + chart_release=module.params.get("chart_release"), + chart_ref=module.params.get("chart_ref"), + namespace=module.params.get("chart_release_namespace"), + ) + + try: + result, status = create_pending_install_release(**params) + if not result: + module.fail_json( + msg="unable to create pending-install release, current status is %s" + % status + ) + module.exit_json(changed=True, msg="Release created with status '%s'" % status) + except HelmReleaseNotFoundError as err: + module.fail_json( + msg="Error while trying to create pending-install release due to '%s'" % err + ) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/helm/tasks/test_helm_uninstall.yml b/tests/integration/targets/helm/tasks/test_helm_uninstall.yml index e82891e4..23e36fb0 100644 --- a/tests/integration/targets/helm/tasks/test_helm_uninstall.yml +++ b/tests/integration/targets/helm/tasks/test_helm_uninstall.yml @@ -7,6 +7,7 @@ - vars: chart_source: "https://github.com/kubernetes/kube-state-metrics/releases/download/kube-state-metrics-helm-chart-2.13.3/kube-state-metrics-2.13.3.tgz" chart_name: "test-wait-uninstall" + chart_name_pending: "test-uninstall-pending-release" helm_namespace: "{{ test_namespace[1] }}" block: @@ -64,6 +65,36 @@ wait: yes register: uninstall + # Test uninstall chart release with 'pending-install' status + - name: Create chart release with 'pending-install' status + helm_test_pending: + binary_path: "{{ helm_binary }}" + chart_ref: "{{ chart_source }}" + chart_release: "{{ chart_name_pending }}" + chart_release_namespace: "{{ helm_namespace }}" + + - name: Uninstall chart release with 'pending-install' status + helm: + binary_path: "{{ helm_binary }}" + release_name: "{{ chart_name_pending }}" + namespace: "{{ helm_namespace }}" + release_state: absent + register: _uninstall + + - name: Get Helm release details + helm_info: + binary_path: "{{ helm_binary }}" + release_namespace: "{{ helm_namespace }}" + release_state: pending + release_name: "{{ chart_name_pending }}" + register: _info + + - name: assert release does not exist anymore + assert: + that: + - _uninstall is changed + - _info.status is undefined + always: - name: Delete temp directory file: