diff --git a/changelogs/fragments/343-secret-check-mode.yaml b/changelogs/fragments/343-secret-check-mode.yaml new file mode 100644 index 00000000..6351a888 --- /dev/null +++ b/changelogs/fragments/343-secret-check-mode.yaml @@ -0,0 +1,2 @@ +bugfixes: + - k8s - fix check_mode always showing changes when using stringData on Secrets (https://github.com/ansible-collections/community.kubernetes/issues/282). diff --git a/molecule/default/tasks/apply.yml b/molecule/default/tasks/apply.yml index 2f579755..291a35d6 100644 --- a/molecule/default/tasks/apply.yml +++ b/molecule/default/tasks/apply.yml @@ -761,6 +761,81 @@ that: - deploy_after_serviceaccount_removal is failed + - name: Add a secret + k8s: + definition: + apiVersion: v1 + kind: Secret + metadata: + name: apply-secret + namespace: "{{ apply_namespace }}" + type: Opaque + stringData: + foo: bar + register: k8s_secret + + - name: Check secret was created + assert: + that: + - k8s_secret is changed + - k8s_secret.result.data.foo + + - name: Add same secret + k8s: + definition: + apiVersion: v1 + kind: Secret + metadata: + name: apply-secret + namespace: "{{ apply_namespace }}" + type: Opaque + stringData: + foo: bar + register: k8s_secret + + - name: Check nothing changed + assert: + that: + - k8s_secret is not changed + + - name: Add same secret with check mode on + k8s: + definition: + apiVersion: v1 + kind: Secret + metadata: + name: apply-secret + namespace: "{{ apply_namespace }}" + type: Opaque + stringData: + foo: bar + check_mode: yes + register: k8s_secret + + - name: Check nothing changed + assert: + that: + - k8s_secret is not changed + + - name: Add same secret with check mode on using data + k8s: + definition: + apiVersion: v1 + kind: Secret + metadata: + name: apply-secret + namespace: "{{ apply_namespace }}" + type: Opaque + data: + foo: YmFy + check_mode: yes + register: k8s_secret + + - name: Check nothing changed + assert: + that: + - k8s_secret is not changed + always: - name: Remove namespace k8s: diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index 788c9df8..2e2bc6ed 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -18,6 +18,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import base64 import time import os import traceback @@ -28,7 +29,7 @@ from distutils.version import LooseVersion from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.six import iteritems, string_types -from ansible.module_utils._text import to_native +from ansible.module_utils._text import to_native, to_bytes, to_text from ansible.module_utils.common.dict_transformations import dict_merge from ansible.module_utils.parsing.convert_bool import boolean @@ -690,7 +691,7 @@ class K8sAnsibleMixin(object): else: if self.apply: if self.check_mode: - ignored, patch = apply_object(resource, definition) + ignored, patch = apply_object(resource, _encode_stringdata(definition)) if existing: k8s_obj = dict_merge(existing.to_dict(), patch) else: @@ -721,7 +722,7 @@ class K8sAnsibleMixin(object): if not existing: if self.check_mode: - k8s_obj = definition + k8s_obj = _encode_stringdata(definition) else: try: k8s_obj = resource.create(definition, namespace=namespace).to_dict() @@ -757,7 +758,7 @@ class K8sAnsibleMixin(object): if existing and force: if self.check_mode: - k8s_obj = definition + k8s_obj = _encode_stringdata(definition) else: try: k8s_obj = resource.replace(definition, name=name, namespace=namespace, append_hash=self.append_hash).to_dict() @@ -781,7 +782,7 @@ class K8sAnsibleMixin(object): # Differences exist between the existing obj and requested params if self.check_mode: - k8s_obj = dict_merge(existing.to_dict(), definition) + k8s_obj = dict_merge(existing.to_dict(), _encode_stringdata(definition)) else: if LooseVersion(self.openshift_version) < LooseVersion("0.6.2"): k8s_obj, error = self.patch_resource(resource, definition, existing, name, @@ -858,3 +859,12 @@ class KubernetesAnsibleModule(AnsibleModule, K8sAnsibleMixin): self.warn("class KubernetesAnsibleModule is deprecated" " and will be removed in 2.0.0. Please use K8sAnsibleMixin instead.") + + +def _encode_stringdata(definition): + if definition['kind'] == 'Secret' and 'stringData' in definition: + for k, v in definition['stringData'].items(): + encoded = base64.b64encode(to_bytes(v)) + definition.setdefault('data', {})[k] = to_text(encoded) + del definition['stringData'] + return definition diff --git a/tests/unit/module_utils/test_common.py b/tests/unit/module_utils/test_common.py new file mode 100644 index 00000000..5eb4621b --- /dev/null +++ b/tests/unit/module_utils/test_common.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, 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 + + +from ansible_collections.community.kubernetes.plugins.module_utils.common import ( + _encode_stringdata, +) + + +def test_encode_stringdata_modifies_definition(): + definition = { + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "stringData": { + "mydata": "ansiβle" + } + } + res = _encode_stringdata(definition) + assert "stringData" not in res + assert res["data"]["mydata"] == "YW5zac6ybGU=" + + +def test_encode_stringdata_does_not_modify_data(): + definition = { + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "data": { + "mydata": "Zm9vYmFy" + } + } + res = _encode_stringdata(definition) + assert res["data"]["mydata"] == "Zm9vYmFy"