mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-26 21:33:02 +00:00
* Add unit and sanity tests to GHA Signed-off-by: GomathiselviS <gomathiselvi@gmail.com> * Fix sanity issues * Add sanity non voting * Add changelog * Fix typo * Fix typo * Use pytest-ansible * Add support for pytest-ansible --------- Signed-off-by: GomathiselviS <gomathiselvi@gmail.com>
277 lines
7.7 KiB
Python
277 lines
7.7 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright: (c) 2020, Julien Huon <@julienhuon> Institut National de l'Audiovisuel
|
|
# 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: k8s_rollback
|
|
short_description: Rollback Kubernetes (K8S) Deployments and DaemonSets
|
|
version_added: "1.0.0"
|
|
author:
|
|
- "Julien Huon (@julienhuon)"
|
|
description:
|
|
- Use the Kubernetes Python client to perform the Rollback.
|
|
- Authenticate using either a config file, certificates, password or token.
|
|
- Similar to the C(kubectl rollout undo) command.
|
|
options:
|
|
label_selectors:
|
|
description: List of label selectors to use to filter results.
|
|
type: list
|
|
elements: str
|
|
default: []
|
|
field_selectors:
|
|
description: List of field selectors to use to filter results.
|
|
type: list
|
|
elements: str
|
|
default: []
|
|
extends_documentation_fragment:
|
|
- kubernetes.core.k8s_auth_options
|
|
- kubernetes.core.k8s_name_options
|
|
requirements:
|
|
- "python >= 3.6"
|
|
- "kubernetes >= 12.0.0"
|
|
- "PyYAML >= 3.11"
|
|
"""
|
|
|
|
EXAMPLES = r"""
|
|
- name: Rollback a failed deployment
|
|
kubernetes.core.k8s_rollback:
|
|
api_version: apps/v1
|
|
kind: Deployment
|
|
name: web
|
|
namespace: testing
|
|
"""
|
|
|
|
RETURN = r"""
|
|
rollback_info:
|
|
description:
|
|
- The object that was rolled back.
|
|
returned: success
|
|
type: complex
|
|
contains:
|
|
api_version:
|
|
description: The versioned schema of this representation of an object.
|
|
returned: success
|
|
type: str
|
|
code:
|
|
description: The HTTP Code of the response
|
|
returned: success
|
|
type: str
|
|
kind:
|
|
description: Status
|
|
returned: success
|
|
type: str
|
|
metadata:
|
|
description:
|
|
- Standard object metadata.
|
|
- Includes name, namespace, annotations, labels, etc.
|
|
returned: success
|
|
type: dict
|
|
status:
|
|
description: Current status details for the object.
|
|
returned: success
|
|
type: dict
|
|
"""
|
|
|
|
import copy
|
|
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
|
AnsibleModule,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
|
AUTH_ARG_SPEC,
|
|
NAME_ARG_SPEC,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
get_api_client,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
AnsibleK8SModule,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
CoreException,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
K8sService,
|
|
)
|
|
|
|
|
|
def get_managed_resource(kind):
|
|
managed_resource = {}
|
|
|
|
if kind == "DaemonSet":
|
|
managed_resource["kind"] = "ControllerRevision"
|
|
managed_resource["api_version"] = "apps/v1"
|
|
elif kind == "Deployment":
|
|
managed_resource["kind"] = "ReplicaSet"
|
|
managed_resource["api_version"] = "apps/v1"
|
|
else:
|
|
raise CoreException(
|
|
"Cannot perform rollback on resource of kind {0}".format(kind)
|
|
)
|
|
return managed_resource
|
|
|
|
|
|
def execute_module(svc):
|
|
results = []
|
|
module = svc.module
|
|
|
|
resources = svc.find(
|
|
module.params["kind"],
|
|
module.params["api_version"],
|
|
module.params["name"],
|
|
module.params["namespace"],
|
|
module.params["label_selectors"],
|
|
module.params["field_selectors"],
|
|
)
|
|
|
|
changed = False
|
|
for resource in resources["resources"]:
|
|
result = perform_action(svc, resource)
|
|
changed = result["changed"] or changed
|
|
results.append(result)
|
|
|
|
module.exit_json(**{"changed": changed, "rollback_info": results})
|
|
|
|
|
|
def perform_action(svc, resource):
|
|
module = svc.module
|
|
|
|
if module.params["kind"] == "DaemonSet":
|
|
current_revision = resource["metadata"]["generation"]
|
|
elif module.params["kind"] == "Deployment":
|
|
current_revision = resource["metadata"]["annotations"][
|
|
"deployment.kubernetes.io/revision"
|
|
]
|
|
|
|
managed_resource = get_managed_resource(module.params["kind"])
|
|
managed_resources = svc.find(
|
|
managed_resource["kind"],
|
|
managed_resource["api_version"],
|
|
"",
|
|
module.params["namespace"],
|
|
resource["spec"]["selector"]["matchLabels"],
|
|
"",
|
|
)
|
|
|
|
prev_managed_resource = get_previous_revision(
|
|
managed_resources["resources"], current_revision
|
|
)
|
|
if not prev_managed_resource:
|
|
warn = "No rollout history found for resource %s/%s" % (
|
|
module.params["kind"],
|
|
resource["metadata"]["name"],
|
|
)
|
|
result = {"changed": False, "warnings": [warn]}
|
|
return result
|
|
|
|
if module.params["kind"] == "Deployment":
|
|
del prev_managed_resource["spec"]["template"]["metadata"]["labels"][
|
|
"pod-template-hash"
|
|
]
|
|
|
|
resource_patch = [
|
|
{
|
|
"op": "replace",
|
|
"path": "/spec/template",
|
|
"value": prev_managed_resource["spec"]["template"],
|
|
},
|
|
{
|
|
"op": "replace",
|
|
"path": "/metadata/annotations",
|
|
"value": {
|
|
"deployment.kubernetes.io/revision": prev_managed_resource[
|
|
"metadata"
|
|
]["annotations"]["deployment.kubernetes.io/revision"]
|
|
},
|
|
},
|
|
]
|
|
|
|
api_target = "deployments"
|
|
content_type = "application/json-patch+json"
|
|
elif module.params["kind"] == "DaemonSet":
|
|
resource_patch = prev_managed_resource["data"]
|
|
|
|
api_target = "daemonsets"
|
|
content_type = "application/strategic-merge-patch+json"
|
|
|
|
rollback = resource
|
|
if not module.check_mode:
|
|
rollback = svc.client.client.request(
|
|
"PATCH",
|
|
"/apis/{0}/namespaces/{1}/{2}/{3}".format(
|
|
module.params["api_version"],
|
|
module.params["namespace"],
|
|
api_target,
|
|
module.params["name"],
|
|
),
|
|
body=resource_patch,
|
|
content_type=content_type,
|
|
).to_dict()
|
|
|
|
result = {"changed": True}
|
|
result["method"] = "patch"
|
|
result["body"] = resource_patch
|
|
result["resources"] = rollback
|
|
return result
|
|
|
|
|
|
def argspec():
|
|
args = copy.deepcopy(AUTH_ARG_SPEC)
|
|
args.update(NAME_ARG_SPEC)
|
|
args.update(
|
|
dict(
|
|
label_selectors=dict(type="list", elements="str", default=[]),
|
|
field_selectors=dict(type="list", elements="str", default=[]),
|
|
)
|
|
)
|
|
return args
|
|
|
|
|
|
def get_previous_revision(all_resources, current_revision):
|
|
for resource in all_resources:
|
|
if resource["kind"] == "ReplicaSet":
|
|
if (
|
|
int(
|
|
resource["metadata"]["annotations"][
|
|
"deployment.kubernetes.io/revision"
|
|
]
|
|
)
|
|
== int(current_revision) - 1
|
|
):
|
|
return resource
|
|
elif resource["kind"] == "ControllerRevision":
|
|
if (
|
|
int(
|
|
resource["metadata"]["annotations"][
|
|
"deprecated.daemonset.template.generation"
|
|
]
|
|
)
|
|
== int(current_revision) - 1
|
|
):
|
|
return resource
|
|
return None
|
|
|
|
|
|
def main():
|
|
module = AnsibleK8SModule(
|
|
module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True
|
|
)
|
|
|
|
try:
|
|
client = get_api_client(module=module)
|
|
svc = K8sService(client, module)
|
|
execute_module(svc)
|
|
except CoreException as e:
|
|
module.fail_from_exception(e)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|