k8s append_hash (#48830)

* Add append_hash functionality to k8s module

append_hash adds a hash based on the contents of a ConfigMap
or Secret to the name - this enables immutable ConfigMaps and
Secrets.

* Provide k8s_config_resource_name plugin

The k8s_config_resource_name filter plugin provides a means of determining
the name of ConfigMaps and Secrets created with append_hash

* Add changelog fragment

* fix failing tests

* Update openshift version needed for append_hash
This commit is contained in:
Will Thames 2018-11-22 18:14:43 +10:00 committed by John R Barker
commit 960ebd981f
11 changed files with 174 additions and 24 deletions

View file

@ -29,6 +29,8 @@ from ansible.module_utils.six import string_types
from ansible.module_utils.k8s.common import KubernetesAnsibleModule
from ansible.module_utils.common.dict_transformations import dict_merge
from distutils.version import LooseVersion
try:
import yaml
@ -43,6 +45,12 @@ try:
except ImportError:
HAS_KUBERNETES_VALIDATE = False
try:
from openshift.helper.hashes import generate_hash
HAS_K8S_CONFIG_HASH = True
except ImportError:
HAS_K8S_CONFIG_HASH = False
class KubernetesRawModule(KubernetesAnsibleModule):
@ -62,6 +70,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
argument_spec['wait'] = dict(type='bool', default=False)
argument_spec['wait_timeout'] = dict(type='int', default=120)
argument_spec['validate'] = dict(type='dict', default=None, options=self.validate_spec)
argument_spec['append_hash'] = dict(type='bool', default=False)
return argument_spec
def __init__(self, *args, **kwargs):
@ -83,6 +92,10 @@ class KubernetesRawModule(KubernetesAnsibleModule):
if self.params['validate']:
if LooseVersion(self.openshift_version) < LooseVersion("0.8.0"):
self.fail_json(msg="openshift >= 0.8.0 is required for validate")
self.append_hash = self.params.get('append_hash')
if self.append_hash:
if not HAS_K8S_CONFIG_HASH:
self.fail_json(msg="openshift >= 0.7.2 is required for append_hash")
if self.params['merge_type']:
if LooseVersion(self.openshift_version) < LooseVersion("0.6.2"):
self.fail_json(msg="openshift >= 0.6.2 is required for merge_type")
@ -96,7 +109,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
self.resource_definitions = resource_definition
else:
self.resource_definitions = [resource_definition]
src = self.params.pop('src')
src = self.params.get('src')
if src:
self.resource_definitions = self.load_resource_definitions(src)
@ -181,7 +194,12 @@ class KubernetesRawModule(KubernetesAnsibleModule):
return result
try:
existing = resource.get(name=name, namespace=namespace)
# ignore append_hash for resources other than ConfigMap and Secret
if self.append_hash and definition['kind'] in ['ConfigMap', 'Secret']:
name = '%s-%s' % (name, generate_hash(definition))
definition['metadata']['name'] = name
params = dict(name=name, namespace=namespace)
existing = resource.get(**params)
except NotFoundError:
# Remove traceback so that it doesn't show up in later failures
try:
@ -207,7 +225,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
# Delete the object
if not self.check_mode:
try:
k8s_obj = resource.delete(name, namespace=namespace)
k8s_obj = resource.delete(**params)
result['result'] = k8s_obj.to_dict()
except DynamicApiError as exc:
self.fail_json(msg="Failed to delete object: {0}".format(exc.body),
@ -256,7 +274,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
k8s_obj = definition
else:
try:
k8s_obj = resource.replace(definition, name=name, namespace=namespace).to_dict()
k8s_obj = resource.replace(definition, name=name, namespace=namespace, append_hash=self.append_hash).to_dict()
except DynamicApiError as exc:
msg = "Failed to replace object: {0}".format(exc.body)
if self.warnings:

View file

@ -89,6 +89,16 @@ options:
default: no
type: bool
version_added: "2.8"
append_hash:
description:
- Whether to append a hash to a resource name for immutability purposes
- Applies only to ConfigMap and Secret resources
- The parameter will be silently ignored for other resource kinds
- The full definition of an object is needed to generate the hash - this means that deleting an object created with append_hash
will only work if the same object is passed with state=absent (alternatively, just use state=absent with the name including
the generated hash and append_hash=no)
type: bool
version_added: "2.8"
requirements:
- "python >= 2.7"

View file

@ -0,0 +1,40 @@
# Copyright (c) 2017 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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
try:
from openshift.helper.hashes import generate_hash
HAS_GENERATE_HASH = True
except ImportError:
HAS_GENERATE_HASH = False
from ansible.errors import AnsibleFilterError
def k8s_config_resource_name(resource):
if not HAS_GENERATE_HASH:
raise AnsibleFilterError("k8s_config_resource_name requires openshift>=0.7.2")
try:
return resource['metadata']['name'] + '-' + generate_hash(resource)
except KeyError:
raise AnsibleFilterError("resource must have a metadata.name key to generate a resource name")
# ---- Ansible filters ----
class FilterModule(object):
def filters(self):
return {
'k8s_config_resource_name': k8s_config_resource_name
}