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:
parent
c3770bf6f2
commit
960ebd981f
11 changed files with 174 additions and 24 deletions
2
changelogs/fragments/k8s_append_hash.yml
Normal file
2
changelogs/fragments/k8s_append_hash.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- k8s - append_hash parameter adds a hash to the name of ConfigMaps and Secrets for easier immutable resources
|
|
@ -1083,6 +1083,32 @@ To escape special characters within a regex, use the "regex_escape" filter::
|
|||
{{ '^f.*o(.*)$' | regex_escape() }}
|
||||
|
||||
|
||||
Kubernetes Filters
|
||||
``````````````````
|
||||
|
||||
Use the "k8s_config_resource_name" filter to obtain the name of a Kubernetes ConfigMap or Secret,
|
||||
including its hash::
|
||||
|
||||
{{ configmap_resource_definition | k8s_config_resource_name }}
|
||||
|
||||
This can then be used to reference hashes in Pod specifications::
|
||||
|
||||
my_secret:
|
||||
kind: Secret
|
||||
name: my_secret_name
|
||||
|
||||
deployment_resource:
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- envFrom:
|
||||
- secretRef:
|
||||
name: {{ my_secret | k8s_config_resource_name }}
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
Other Useful Filters
|
||||
````````````````````
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
40
lib/ansible/plugins/filter/k8s.py
Normal file
40
lib/ansible/plugins/filter/k8s.py
Normal 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
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ ansible_playbook_python }}"
|
||||
playbook_namespace: ansible-test-k8s-full
|
||||
|
||||
roles:
|
||||
- k8s
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
- hosts: localhost
|
||||
connection: local
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ ansible_playbook_python }}"
|
||||
recreate_crd_default_merge_expectation: recreate_crd is failed
|
||||
|
||||
tasks:
|
||||
- python_requirements_facts:
|
||||
dependencies:
|
||||
- openshift==0.6.0
|
||||
- kubernetes==6.0.0
|
||||
|
||||
- include_role:
|
||||
name: k8s
|
||||
tasks_from: crd
|
|
@ -31,7 +31,7 @@
|
|||
assert:
|
||||
that:
|
||||
- k8s_append_hash is failed
|
||||
- "k8s_append_hash.msg == 'openshift >= 0.7.FIXME is required for append_hash'"
|
||||
- "k8s_append_hash.msg == 'openshift >= 0.7.2 is required for append_hash'"
|
||||
|
||||
# merge_type
|
||||
- include_role:
|
||||
|
@ -58,4 +58,4 @@
|
|||
assert:
|
||||
that:
|
||||
- k8s_validate is failed
|
||||
- "k8s_validate.msg == 'openshift >= 0.7.FIXME is required for validate'"
|
||||
- "k8s_validate.msg == 'openshift >= 0.8.0 is required for validate'"
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
- block:
|
||||
- name: Ensure that append_hash namespace exists
|
||||
k8s:
|
||||
kind: Namespace
|
||||
name: append-hash
|
||||
|
||||
- name: create k8s_resource variable
|
||||
set_fact:
|
||||
k8s_resource:
|
||||
metadata:
|
||||
name: config-map-test
|
||||
namespace: append-hash
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
hello: world
|
||||
|
||||
- name: Create config map
|
||||
k8s:
|
||||
definition: "{{ k8s_resource }}"
|
||||
append_hash: yes
|
||||
register: k8s_configmap1
|
||||
|
||||
- name: check configmap is created with a hash
|
||||
assert:
|
||||
that:
|
||||
- k8s_configmap1 is changed
|
||||
- k8s_configmap1.result.metadata.name != 'config-map-test'
|
||||
- k8s_configmap1.result.metadata.name[:-10] == 'config-map-test-'
|
||||
|
||||
- name: recreate same config map
|
||||
k8s:
|
||||
definition: "{{ k8s_resource }}"
|
||||
append_hash: yes
|
||||
register: k8s_configmap2
|
||||
|
||||
- name: check configmaps are different
|
||||
assert:
|
||||
that:
|
||||
- k8s_configmap2 is not changed
|
||||
- k8s_configmap1.result.metadata.name == k8s_configmap2.result.metadata.name
|
||||
|
||||
- name: add key to config map
|
||||
k8s:
|
||||
definition:
|
||||
metadata:
|
||||
name: config-map-test
|
||||
namespace: append-hash
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
hello: world
|
||||
another: value
|
||||
append_hash: yes
|
||||
register: k8s_configmap3
|
||||
|
||||
- name: check configmaps are different
|
||||
assert:
|
||||
that:
|
||||
- k8s_configmap3 is changed
|
||||
- k8s_configmap1.result.metadata.name != k8s_configmap3.result.metadata.name
|
||||
|
||||
always:
|
||||
- name: ensure that namespace is removed
|
||||
k8s:
|
||||
kind: Namespace
|
||||
name: append-hash
|
||||
state: absent
|
|
@ -278,15 +278,16 @@
|
|||
- testing5
|
||||
register: k8s_facts
|
||||
|
||||
|
||||
- name: Resources are terminating if still in results
|
||||
assert:
|
||||
that: not item.resources or item.resources[0].status.phase == "Terminating"
|
||||
loop: "{{ k8s_facts.results }}"
|
||||
|
||||
- include_tasks: crd.yml
|
||||
- include_tasks: append_hash.yml
|
||||
|
||||
always:
|
||||
|
||||
- name: Delete all namespaces
|
||||
k8s:
|
||||
state: absent
|
||||
|
|
|
@ -28,7 +28,7 @@ ansible-playbook -v playbooks/validate_installed.yml "$@"
|
|||
virtualenv --system-site-packages --python "${PYTHON}" "${MYTMPDIR}/openshift-0.6.0"
|
||||
source "${MYTMPDIR}/openshift-0.6.0/bin/activate"
|
||||
$PYTHON -m pip install openshift==0.6.0 kubernetes==6.0.0
|
||||
ansible-playbook -v playbooks/merge_type_fail.yml "$@"
|
||||
ansible-playbook -v playbooks/older_openshift_fail.yml "$@"
|
||||
|
||||
# Run full test suite
|
||||
virtualenv --system-site-packages --python "${PYTHON}" "${MYTMPDIR}/openshift-recent"
|
||||
|
|
Loading…
Reference in a new issue