diff --git a/changelogs/fragments/225-kustomize-lookup-plugin.yml b/changelogs/fragments/225-kustomize-lookup-plugin.yml new file mode 100644 index 00000000..3c50138e --- /dev/null +++ b/changelogs/fragments/225-kustomize-lookup-plugin.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - new lookup plugin to support kubernetes kustomize feature. (https://github.com/ansible-collections/kubernetes.core/issues/39). diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index 0b2d1d38..694357c9 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -169,7 +169,7 @@ include_tasks: file: tasks/lookup_k8s.yml apply: - tags: [ lookup, k8s ] + tags: [ lookup_k8s, k8s ] tags: - always @@ -189,6 +189,14 @@ tags: - always + - name: Include lookup_kustomize.yml + include_tasks: + file: tasks/lookup_kustomize.yml + apply: + tags: [ lookup_kustomize, k8s ] + tags: + - always + roles: - role: helm tags: diff --git a/molecule/default/tasks/lookup_kustomize.yml b/molecule/default/tasks/lookup_kustomize.yml new file mode 100644 index 00000000..164bb828 --- /dev/null +++ b/molecule/default/tasks/lookup_kustomize.yml @@ -0,0 +1,103 @@ +--- +- block: + - set_fact: + kustomize_ns: "kustomize" + + - name: create environment for test + block: + - name: Ensure namespace + k8s: + kind: Namespace + name: "{{ kustomize_ns }}" + + - name: Create temp directory + tempfile: + state: directory + suffix: .test + register: _tmp_dir + + - set_fact: + tmp_dir_path: "{{ _tmp_dir.path }}" + + - set_fact: + kustomize_dir: "{{ tmp_dir_path }}/kustomization" + + - name: create kustomize directory + file: + path: "{{ kustomize_dir }}" + state: directory + + - name: create kustomization file + copy: + content: '{{ item.content }}' + dest: '{{ item.dest }}' + with_items: + - content: | + configMapGenerator: + - name: test-confmap- + files: + - data.properties + dest: "{{ kustomize_dir }}/kustomization.yaml" + - content: "project=ansible" + dest: "{{ kustomize_dir }}/data.properties" + + - name: copy script to install kustomize + get_url: + url: https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh + dest: "{{ tmp_dir_path }}" + + - name: make script as executable + file: + path: "{{ tmp_dir_path }}/install_kustomize.sh" + mode: 0755 + + - name: Install kustomize + command: "{{ tmp_dir_path }}/install_kustomize.sh" + args: + chdir: "{{ tmp_dir_path }}" + register: _install + + - set_fact: + kustomize_binary: "{{ _install.stdout | regex_search('kustomize installed to (.*)', '\\1') | list | join('') }}" + kubectl_binary: "{{ tmp_dir_path }}/kubectl" + + - name: Download kubeclt executable used to compare results + get_url: + url: https://dl.k8s.io/release/v1.21.3/bin/linux/amd64/kubectl + dest: "{{ kubectl_binary }}" + + - name: make kubectl executable + ansible.builtin.file: + path: "{{ kubectl_binary }}" + mode: "+x" + + - name: Run lookup using kustomize binary + set_fact: + resource_kustomize: "{{ lookup('kubernetes.core.kustomize', binary_path=kustomize_binary, dir=kustomize_dir) }}" + + - name: Run lookup using kubectl binary + set_fact: + resource_kubectl: "{{ lookup('kubernetes.core.kustomize', binary_path=kubectl_binary, dir=kustomize_dir) }}" + + - name: assert output are the same + assert: + that: + - resource_kubectl == resource_kustomize + + - name: create kubernetes resource using lookup plugin + k8s: + namespace: "{{ kustomize_ns }}" + definition: "{{ lookup('kubernetes.core.kustomize', dir=kustomize_dir, opt_dirs=tmp_dir_path) }}" + + always: + - name: Delete namespace + k8s: + kind: Namespace + name: "{{ kustomize_ns }}" + state: absent + ignore_errors: true + + - name: Delete temporary directory + file: + state: absent + path: "{{ tmp_dir_path }}" diff --git a/plugins/lookup/kustomize.py b/plugins/lookup/kustomize.py new file mode 100644 index 00000000..c23db145 --- /dev/null +++ b/plugins/lookup/kustomize.py @@ -0,0 +1,133 @@ +# +# Copyright 2021 Red Hat | Ansible +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' + lookup: kustomize + + short_description: Build a set of kubernetes resources using a 'kustomization.yaml' file. + + version_added: "2.2.0" + + author: + - Aubin Bikouo <@abikouo> + notes: + - If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin. + description: + - Uses the kustomize or the kubectl tool. + - Return the result of C(kustomize build) or C(kubectl kustomize). + options: + dir: + description: + - The directory path containing 'kustomization.yaml', + or a git repository URL with a path suffix specifying same with respect to the repository root. + - If omitted, '.' is assumed. + default: "." + binary_path: + description: + - The path of a kustomize or kubectl binary to use. + opt_dirs: + description: + - An optional list of directories to search for the executable in addition to PATH. + + requirements: + - "python >= 3.6" +''' + +EXAMPLES = """ +- name: Run lookup using kustomize + set_fact: + resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}" + +- name: Run lookup using kubectl kustomize + set_fact: + resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}" + +- name: Create kubernetes resources for lookup output + k8s: + definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}" +""" + +RETURN = """ + _list: + description: + - One ore more object definitions returned from the tool execution. + type: complex + contains: + api_version: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: complex + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: complex + status: + description: Current status details for the object. + returned: success + type: complex +""" + +from ansible.errors import AnsibleLookupError +from ansible.plugins.lookup import LookupBase +from ansible.module_utils.common.process import get_bin_path + + +import subprocess + + +def get_binary_from_path(name, opt_dirs=None): + opt_arg = {} + try: + if opt_dirs is not None: + if not isinstance(opt_dirs, list): + opt_dirs = [opt_dirs] + opt_arg['opt_dirs'] = opt_dirs + bin_path = get_bin_path(name, **opt_arg) + return bin_path + except ValueError: + return None + + +def run_command(command): + cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return cmd.communicate() + + +class LookupModule(LookupBase): + + def run(self, terms, variables=None, dir=".", binary_path=None, opt_dirs=None, **kwargs): + executable_path = binary_path + if executable_path is None: + executable_path = get_binary_from_path(name="kustomize", opt_dirs=opt_dirs) + if executable_path is None: + executable_path = get_binary_from_path(name="kubectl", opt_dirs=opt_dirs) + + # validate that at least one tool was found + if executable_path is None: + raise AnsibleLookupError("Failed to find required executable 'kubectl' and 'kustomize' in paths") + + # check input directory + kustomization_dir = dir + + command = [executable_path] + if executable_path.endswith('kustomize'): + command += ['build', kustomization_dir] + elif executable_path.endswith('kubectl'): + command += ['kustomize', kustomization_dir] + else: + raise AnsibleLookupError("unexpected tool provided as parameter {0}, expected one of kustomize, kubectl.".format(executable_path)) + + (out, err) = run_command(command) + if err: + raise AnsibleLookupError("kustomize command failed with: {0}".format(err.decode("utf-8"))) + return [out.decode('utf-8')] diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 79e43880..c543b5de 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -244,4 +244,6 @@ molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip tests/unit/module_utils/test_selector.py future-import-boilerplate!skip tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip \ No newline at end of file +plugins/module_utils/selector.py metaclass-boilerplate!skip +plugins/lookup/kustomize.py future-import-boilerplate!skip +plugins/lookup/kustomize.py metaclass-boilerplate!skip \ No newline at end of file diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 79e43880..c543b5de 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -244,4 +244,6 @@ molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip tests/unit/module_utils/test_selector.py future-import-boilerplate!skip tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip \ No newline at end of file +plugins/module_utils/selector.py metaclass-boilerplate!skip +plugins/lookup/kustomize.py future-import-boilerplate!skip +plugins/lookup/kustomize.py metaclass-boilerplate!skip \ No newline at end of file diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index 0b23ebe1..56c60dff 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -238,4 +238,6 @@ molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip tests/unit/module_utils/test_selector.py future-import-boilerplate!skip tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip \ No newline at end of file +plugins/module_utils/selector.py metaclass-boilerplate!skip +plugins/lookup/kustomize.py future-import-boilerplate!skip +plugins/lookup/kustomize.py metaclass-boilerplate!skip \ No newline at end of file