Skip to content
This repository has been archived by the owner on Jun 13, 2024. It is now read-only.

Commit

Permalink
k8s: Add support for templates
Browse files Browse the repository at this point in the history
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
  • Loading branch information
Akasurde committed Sep 22, 2020
1 parent 308956b commit 9d890df
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 2 deletions.
1 change: 1 addition & 0 deletions molecule/default/converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- include_tasks: tasks/full.yml
- include_tasks: tasks/exec.yml
- include_tasks: tasks/log.yml
- include_tasks: tasks/template.yml

roles:
- helm
Expand Down
154 changes: 154 additions & 0 deletions molecule/default/tasks/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
- block:
- set_fact:
template_namespace: template-test

- name: Ensure namespace exists
k8s:
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ template_namespace }}"

- name: Check if k8s_service does not inherit parameter
community.kubernetes.k8s_service:
template: "pod_template_one.j2"
state: present
ignore_errors: yes
register: r

- name: Check for expected failures in last tasks
assert:
that:
- r.failed
- "'is only supported parameter for' in r.msg"

- name: Specify both definition and template
community.kubernetes.k8s:
state: present
template: "pod_template_one.j2"
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: apply-deploy
namespace: "{{ template_namespace }}"
spec:
replicas: 1
selector:
matchLabels:
app: "{{ k8s_pod_name }}"
vars:
k8s_pod_name: pod
k8s_pod_namespace: "{{ template_namespace }}"
register: r
ignore_errors: yes

- name: Check if definition and template are mutually exclusive
assert:
that:
- r.failed
- "'parameters are mutually exclusive' in r.msg"

- name: Specify both src and template
community.kubernetes.k8s:
state: present
src: "../templates/pod_template_one.j2"
template: "pod_template_one.j2"
vars:
k8s_pod_name: pod
k8s_pod_namespace: "{{ template_namespace }}"
register: r
ignore_errors: yes

- name: Check if src and template are mutually exclusive
assert:
that:
- r.failed
- "'parameters are mutually exclusive' in r.msg"

- name: Create pod using template (direct specification)
community.kubernetes.k8s:
template: "pod_template_one.j2"
wait: yes
vars:
k8s_pod_name: pod-1
k8s_pod_namespace: "{{ template_namespace }}"
register: r

- name: Assert that pod creation succeeded using template
assert:
that:
- r is successful

- name: Create pod using template with wrong parameter
community.kubernetes.k8s:
template:
- default
wait: yes
vars:
k8s_pod_name: pod-2
k8s_pod_namespace: "{{ template_namespace }}"
register: r
ignore_errors: True

- name: Assert that pod creation failed using template due to wrong parameter
assert:
that:
- r is failed
- "'Error while reading template file' in r.msg"

- name: Create pod using template (path parameter)
community.kubernetes.k8s:
template:
path: "pod_template_one.j2"
wait: yes
vars:
k8s_pod_name: pod-3
k8s_pod_namespace: "{{ template_namespace }}"
register: r

- name: Assert that pod creation succeeded using template
assert:
that:
- r is successful

- name: Create pod using template (different variable string)
community.kubernetes.k8s:
template:
path: "pod_template_two.j2"
variable_start_string: '[['
variable_end_string: ']]'
wait: yes
vars:
k8s_pod_name: pod-4
k8s_pod_namespace: "[[ template_namespace ]]"
register: r

- name: Assert that pod creation succeeded using template
assert:
that:
- r is successful

- name: Remove Pod (Cleanup)
k8s:
api_version: v1
kind: Pod
name: "{{ item }}"
namespace: "{{ template_namespace }}"
state: absent
wait: yes
ignore_errors: yes
with_items:
- pod-1
- pod-2
- pod-3
- pod-4

always:
- name: Remove namespace (Cleanup)
k8s:
kind: Namespace
name: "{{ template_namespace }}"
state: absent
16 changes: 16 additions & 0 deletions molecule/default/templates/pod_template_one.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
labels:
app: "{{ k8s_pod_name }}"
name: '{{ k8s_pod_name }}'
namespace: '{{ k8s_pod_namespace }}'
spec:
containers:
- args:
- /bin/sh
- -c
- while true; do echo $(date); sleep 10; done
image: python:3.7-alpine
imagePullPolicy: Always
name: '{{ k8s_pod_name }}'
16 changes: 16 additions & 0 deletions molecule/default/templates/pod_template_two.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
labels:
app: '[[ k8s_pod_name ]]'
name: '[[ k8s_pod_name ]]'
namespace: '[[ k8s_pod_namespace ]]'
spec:
containers:
- args:
- /bin/sh
- -c
- while true; do echo $(date); sleep 10; done
image: python:3.7-alpine
imagePullPolicy: Always
name: '[[ k8s_pod_name ]]'
120 changes: 118 additions & 2 deletions plugins/action/k8s_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@
import copy
import traceback

from ansible.module_utils._text import to_text
from ansible.config.manager import ensure_type
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleAction, AnsibleActionFail
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_text, to_bytes, to_native
from ansible.plugins.action import ActionBase
from ansible.errors import AnsibleError


class ActionModule(ActionBase):

TRANSFERS_FILES = True
DEFAULT_NEWLINE_SEQUENCE = "\n"

def _ensure_invocation(self, result):
# NOTE: adding invocation arguments here needs to be kept in sync with
Expand Down Expand Up @@ -71,6 +75,118 @@ def run(self, tmp=None, task_vars=None):
if src:
new_module_args['src'] = src

template = self._task.args.get('template', None)
if template:
# template is only supported by k8s module.
if self._task.action not in ('k8s', 'community.kubernetes.k8s', 'community.okd.k8s'):
raise AnsibleActionFail("'template' is only supported parameter for 'k8s' module.")
if isinstance(template, string_types):
# treat this as raw_params
template_path = template
newline_sequence = self.DEFAULT_NEWLINE_SEQUENCE
variable_start_string = None
variable_end_string = None
block_start_string = None
block_end_string = None
trim_blocks = True
lstrip_blocks = False
elif isinstance(template, dict):
template_args = template
template_path = template_args.get('path', None)
if not template:
raise AnsibleActionFail("Please specify path for template.")

# Options type validation strings
for s_type in ('newline_sequence', 'variable_start_string', 'variable_end_string', 'block_start_string',
'block_end_string'):
if s_type in template_args:
value = ensure_type(template_args[s_type], 'string')
if value is not None and not isinstance(value, string_types):
raise AnsibleActionFail("%s is expected to be a string, but got %s instead" % (s_type, type(value)))
try:
trim_blocks = boolean(template_args.get('trim_blocks', True), strict=False)
lstrip_blocks = boolean(template_args.get('lstrip_blocks', False), strict=False)
except TypeError as e:
raise AnsibleActionFail(to_native(e))

newline_sequence = template_args.get('newline_sequence', self.DEFAULT_NEWLINE_SEQUENCE)
variable_start_string = template_args.get('variable_start_string', None)
variable_end_string = template_args.get('variable_end_string', None)
block_start_string = template_args.get('block_start_string', None)
block_end_string = template_args.get('block_end_string', None)
else:
raise AnsibleActionFail("Error while reading template file - "
"a string or dict for template expected, but got %s instead" % type(template))
try:
source = self._find_needle('templates', template_path)
except AnsibleError as e:
raise AnsibleActionFail(to_text(e))

# Option `lstrip_blocks' was added in Jinja2 version 2.7.
if lstrip_blocks:
try:
import jinja2.defaults
except ImportError:
raise AnsibleError('Unable to import Jinja2 defaults for determining Jinja2 features.')

try:
jinja2.defaults.LSTRIP_BLOCKS
except AttributeError:
raise AnsibleError("Option `lstrip_blocks' is only available in Jinja2 versions >=2.7")

wrong_sequences = ["\\n", "\\r", "\\r\\n"]
allowed_sequences = ["\n", "\r", "\r\n"]

# We need to convert unescaped sequences to proper escaped sequences for Jinja2
if newline_sequence in wrong_sequences:
newline_sequence = allowed_sequences[wrong_sequences.index(newline_sequence)]
elif newline_sequence not in allowed_sequences:
raise AnsibleActionFail("newline_sequence needs to be one of: \n, \r or \r\n")

# Get vault decrypted tmp file
try:
tmp_source = self._loader.get_real_file(source)
except AnsibleFileNotFound as e:
raise AnsibleActionFail("could not find template=%s, %s" % (source, to_text(e)))
b_tmp_source = to_bytes(tmp_source, errors='surrogate_or_strict')

# template the source data locally & get ready to transfer
try:
with open(b_tmp_source, 'rb') as f:
try:
template_data = to_text(f.read(), errors='surrogate_or_strict')
except UnicodeError:
raise AnsibleActionFail("Template source files must be utf-8 encoded")

# add ansible 'template' vars
temp_vars = task_vars.copy()
old_vars = self._templar.available_variables

self._templar.environment.newline_sequence = newline_sequence
if block_start_string is not None:
self._templar.environment.block_start_string = block_start_string
if block_end_string is not None:
self._templar.environment.block_end_string = block_end_string
if variable_start_string is not None:
self._templar.environment.variable_start_string = variable_start_string
if variable_end_string is not None:
self._templar.environment.variable_end_string = variable_end_string
self._templar.environment.trim_blocks = trim_blocks
self._templar.environment.lstrip_blocks = lstrip_blocks
self._templar.available_variables = temp_vars
resultant = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
self._templar.available_variables = old_vars
resource_definition = self._task.args.get('definition', None)
if not resource_definition:
new_module_args.pop('template')
new_module_args['definition'] = resultant
except AnsibleAction:
raise
except Exception as e:
raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
finally:
self._loader.cleanup_tmp_file(b_tmp_source)

# Execute the k8s_* module.
module_return = self._execute_module(module_name=self._task.action, module_args=new_module_args, task_vars=task_vars)

Expand Down
1 change: 1 addition & 0 deletions plugins/doc_fragments/k8s_resource_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ class ModuleDocFragment(object):
- Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup
plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to
I(resource_definition). See Examples below.
- Mutually exclusive with I(template) in case of M(k8s) module.
type: path
'''
Loading

0 comments on commit 9d890df

Please sign in to comment.