mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-07-29 19:04:39 +00:00
k8s: add support for template (#230)
Fixes: #37 Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
- include_tasks: tasks/exec.yml
|
- include_tasks: tasks/exec.yml
|
||||||
- include_tasks: tasks/log.yml
|
- include_tasks: tasks/log.yml
|
||||||
- include_tasks: tasks/info.yml
|
- include_tasks: tasks/info.yml
|
||||||
|
- include_tasks: tasks/template.yml
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- helm
|
- helm
|
||||||
|
|||||||
155
molecule/default/tasks/template.yml
Normal file
155
molecule/default/tasks/template.yml
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
---
|
||||||
|
- 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 ]]"
|
||||||
|
ansible_python_interpreter: "[[ ansible_playbook_python ]]"
|
||||||
|
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
molecule/default/templates/pod_template_one.j2
Normal file
16
molecule/default/templates/pod_template_one.j2
Normal file
@@ -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
molecule/default/templates/pod_template_two.j2
Normal file
16
molecule/default/templates/pod_template_two.j2
Normal file
@@ -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 ]]'
|
||||||
@@ -9,14 +9,18 @@ __metaclass__ = type
|
|||||||
import copy
|
import copy
|
||||||
import traceback
|
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.plugins.action import ActionBase
|
||||||
from ansible.errors import AnsibleError
|
|
||||||
|
|
||||||
|
|
||||||
class ActionModule(ActionBase):
|
class ActionModule(ActionBase):
|
||||||
|
|
||||||
TRANSFERS_FILES = True
|
TRANSFERS_FILES = True
|
||||||
|
DEFAULT_NEWLINE_SEQUENCE = "\n"
|
||||||
|
|
||||||
def _ensure_invocation(self, result):
|
def _ensure_invocation(self, result):
|
||||||
# NOTE: adding invocation arguments here needs to be kept in sync with
|
# NOTE: adding invocation arguments here needs to be kept in sync with
|
||||||
@@ -71,6 +75,118 @@ class ActionModule(ActionBase):
|
|||||||
if src:
|
if src:
|
||||||
new_module_args['src'] = 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.
|
# Execute the k8s_* module.
|
||||||
module_return = self._execute_module(module_name=self._task.action, module_args=new_module_args, task_vars=task_vars)
|
module_return = self._execute_module(module_name=self._task.action, module_args=new_module_args, task_vars=task_vars)
|
||||||
|
|
||||||
|
|||||||
@@ -28,5 +28,6 @@ options:
|
|||||||
- Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup
|
- 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
|
plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to
|
||||||
I(resource_definition). See Examples below.
|
I(resource_definition). See Examples below.
|
||||||
|
- Mutually exclusive with I(template) in case of M(k8s) module.
|
||||||
type: path
|
type: path
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -96,6 +96,30 @@ options:
|
|||||||
- Requires openshift >= 0.9.2
|
- Requires openshift >= 0.9.2
|
||||||
- mutually exclusive with C(merge_type)
|
- mutually exclusive with C(merge_type)
|
||||||
type: bool
|
type: bool
|
||||||
|
template:
|
||||||
|
description:
|
||||||
|
- Provide a valid YAML template definition file for an object when creating or updating.
|
||||||
|
- Value can be provided as string or dictionary.
|
||||||
|
- Mutually exclusive with C(src) and C(resource_definition).
|
||||||
|
- Template files needs to be present on the Ansible Controller's file system.
|
||||||
|
- Additional parameters can be specified using dictionary.
|
||||||
|
- 'Valid additional parameters - '
|
||||||
|
- 'C(newline_sequence) (str): Specify the newline sequence to use for templating files.
|
||||||
|
valid choices are "\n", "\r", "\r\n". Default value "\n".'
|
||||||
|
- 'C(block_start_string) (str): The string marking the beginning of a block.
|
||||||
|
Default value "{%".'
|
||||||
|
- 'C(block_end_string) (str): The string marking the end of a block.
|
||||||
|
Default value "%}".'
|
||||||
|
- 'C(variable_start_string) (str): The string marking the beginning of a print statement.
|
||||||
|
Default value "{{".'
|
||||||
|
- 'C(variable_end_string) (str): The string marking the end of a print statement.
|
||||||
|
Default value "}}".'
|
||||||
|
- 'C(trim_blocks) (bool): Determine when newlines should be removed from blocks. When set to C(yes) the first newline
|
||||||
|
after a block is removed (block, not variable tag!). Default value is true.'
|
||||||
|
- 'C(lstrip_blocks) (bool): Determine when leading spaces and tabs should be stripped.
|
||||||
|
When set to C(yes) leading spaces and tabs are stripped from the start of a line to a block.
|
||||||
|
This functionality requires Jinja 2.7 or newer. Default value is false.'
|
||||||
|
type: raw
|
||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
- "python >= 2.7"
|
- "python >= 2.7"
|
||||||
@@ -160,6 +184,19 @@ EXAMPLES = r'''
|
|||||||
state: present
|
state: present
|
||||||
definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
|
definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
|
||||||
|
|
||||||
|
- name: Read definition template file from the Ansible controller file system
|
||||||
|
community.kubernetes.k8s:
|
||||||
|
state: present
|
||||||
|
template: '/testing/deployment.j2'
|
||||||
|
|
||||||
|
- name: Read definition template file from the Ansible controller file system
|
||||||
|
community.kubernetes.k8s:
|
||||||
|
state: present
|
||||||
|
template:
|
||||||
|
path: '/testing/deployment.j2'
|
||||||
|
variable_start_string: '[['
|
||||||
|
variable_end_string: ']]'
|
||||||
|
|
||||||
- name: fail on validation errors
|
- name: fail on validation errors
|
||||||
community.kubernetes.k8s:
|
community.kubernetes.k8s:
|
||||||
state: present
|
state: present
|
||||||
@@ -242,12 +279,15 @@ class KubernetesModule(K8sAnsibleMixin):
|
|||||||
argument_spec['validate'] = dict(type='dict', default=None, options=self.validate_spec)
|
argument_spec['validate'] = dict(type='dict', default=None, options=self.validate_spec)
|
||||||
argument_spec['append_hash'] = dict(type='bool', default=False)
|
argument_spec['append_hash'] = dict(type='bool', default=False)
|
||||||
argument_spec['apply'] = dict(type='bool', default=False)
|
argument_spec['apply'] = dict(type='bool', default=False)
|
||||||
|
argument_spec['template'] = dict(type='raw', default=None)
|
||||||
return argument_spec
|
return argument_spec
|
||||||
|
|
||||||
def __init__(self, *args, k8s_kind=None, **kwargs):
|
def __init__(self, *args, k8s_kind=None, **kwargs):
|
||||||
mutually_exclusive = [
|
mutually_exclusive = [
|
||||||
('resource_definition', 'src'),
|
('resource_definition', 'src'),
|
||||||
('merge_type', 'apply'),
|
('merge_type', 'apply'),
|
||||||
|
('template', 'resource_definition'),
|
||||||
|
('template', 'src'),
|
||||||
]
|
]
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
|
|||||||
Reference in New Issue
Block a user