Skip to content

Commit 798f922

Browse files
committed
Add extra_vars parsing directives due to Jinja
Jinja always casts extra_vars to string because it is impossible to specify in a schema what vars might pass through extra_vars. So, to make it possible to pass objects through Jinja and have them show up as objects or ints or whatever, this adds directives with tests to show they work. The directives available are: '!INT', '!JSON', '!AST'. See the readme for how to use them.
1 parent 95aab90 commit 798f922

6 files changed

Lines changed: 178 additions & 2 deletions

File tree

CHANGES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## v0.6.0
4+
5+
* Add extra_vars parsing directives to get around difficulties with Jinja (which casts all
6+
extra_vars as strings). When passing in an object via Jinja, all values become strings. To get
7+
around this, add "!AST", "!JSON", or "!INT" directives in your action-chain yaml:
8+
9+
```yaml
10+
chain:
11+
name: 'example'
12+
ref: 'ansible.command_local'
13+
extra_parameters:
14+
-
15+
keyA: "!AST{{ jinja_variable_a }}"
16+
keyB: "!JSON{{ jinja_variable_b | tojson }}"
17+
keyC: "!INT{{ jinja_variable_c | int }}"
18+
```
19+
320
## v0.5.0
421
522
* Added ability to use yaml structures to pass arbitrarily complex values through extra_vars. key=value and @file syntax is still supported. Example usage:

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,53 @@ sample_task:
7272
key8: value8
7373
```
7474
75+
##### Structured input and Jinja
76+
Note that `value3`, in the previous example, was passed in as a variable in a Jinja expression.
77+
StackStorm normally casts variables to the types like int, array, and object.
78+
However, it can't do that for extra_vars because we cannot know ahead of time,
79+
for all Ansible playbooks, what schema and data types each entry will be. As such,
80+
within extra_vars, all Jinja expressions result in strings. To get around this, we've
81+
added some extra_vars parsing diretives: `!INT`, `!JSON`, and `!AST`.
82+
83+
Use `!INT` if you are using a playbook that expects a value to be an integer:
84+
85+
```yaml
86+
extra_vars:
87+
port: '!INT{{ port_number }}'
88+
```
89+
90+
Use `!JSON` when you have something that is already a JSON string or when it makes sense
91+
to convert it into a json string using Jinja filters:
92+
93+
```yaml
94+
extra_vars:
95+
special_config: '!JSON{{ config | tojson }}'
96+
```
97+
98+
Use `!AST` (referring to python AST) when you have an object that you want to send straight over
99+
without any additional jinja filters:
100+
101+
```yaml
102+
extra_vars:
103+
data: '!AST{{ data }}'
104+
```
105+
106+
These directives are supported recursively. So, you can embed directives in other directives.
107+
Consider this contrived example (though mostly you'll only hit embedding directives or objects
108+
if you're chaining workflows together to build up a data object):
109+
110+
```yaml
111+
vars:
112+
answer: "!INT{{ life_universe_everything }}"
113+
chain:
114+
-
115+
name: '...'
116+
ref: 'ansible.playbook'
117+
...
118+
extra_vars:
119+
earth: '!JSON{"question": "unknown", "answer": {{ answer }} }'
120+
```
121+
75122
##### Structured output
76123
```sh
77124
# get structured JSON output from a playbook

actions/lib/ansible_base.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import os
22
import sys
33
import subprocess
4+
5+
import yaml
6+
47
import shell
58
import ast
69
import json
@@ -27,6 +30,24 @@ def __init__(self, args):
2730
self._parse_extra_vars() # handle multiple entries in --extra_vars arg
2831
self._prepend_venv_path()
2932

33+
def _parse_extra_vars_directives(self, value):
34+
if isinstance(value, six.string_types):
35+
if value.strip().startswith("!AST"):
36+
a = ast.literal_eval(value.strip()[4:])
37+
return self._parse_extra_vars_directives(a)
38+
elif value.strip().startswith("!JSON"):
39+
j = json.loads(value.strip()[5:])
40+
return self._parse_extra_vars_directives(j)
41+
elif value.strip().startswith("!INT"):
42+
return int(value.strip()[4:])
43+
else:
44+
return value
45+
elif isinstance(value, dict):
46+
return {k: self._parse_extra_vars_directives(v) for k, v in six.iteritems(value)}
47+
elif isinstance(value, list):
48+
return [self._parse_extra_vars_directives(item) for item in value]
49+
return value
50+
3051
def _parse_extra_vars(self):
3152
"""
3253
This method turns the string list ("--extra_vars=[...]") passed in from the args
@@ -50,10 +71,14 @@ def _parse_extra_vars(self):
5071
if isinstance(n, six.string_types):
5172
if n.strip().startswith("@"):
5273
var_list.append(('file', n.strip()))
74+
elif (n.strip().startswith("!AST")
75+
or n.strip().startswith("!JSON")
76+
or '=' not in n):
77+
var_list.append(('json', self._parse_extra_vars_directives(n)))
5378
else:
5479
var_list.append(('kwarg', n.strip()))
5580
elif isinstance(n, dict):
56-
var_list.append(('json', n))
81+
var_list.append(('json', self._parse_extra_vars_directives(n)))
5782

5883
last = ''
5984
kv_param = ''

pack.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ keywords:
66
- ansible
77
- cfg management
88
- configuration management
9-
version : 0.5.1
9+
version : 0.6.0
1010
author : StackStorm, Inc.
1111
email : info@stackstorm.com

tests/fixtures/extra_vars_complex.yaml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,70 @@
2525
key6:
2626
key7: 'value7'
2727
- 'key8=value8'
28+
-
29+
name: ast_directive
30+
test:
31+
- "!AST{u'key1': u'12345', u'key2': u'value2'}"
32+
expected:
33+
-
34+
key1: '12345'
35+
key2: 'value2'
36+
-
37+
name: json_directive
38+
test:
39+
- '!JSON{"key1": "12345", "key2": "value2"}'
40+
expected:
41+
-
42+
key1: '12345'
43+
key2: 'value2'
44+
-
45+
name: sub_ast_directive
46+
test:
47+
-
48+
astkey: "!AST[{u'key1': u'12345', u'key2': u'value2'}]"
49+
expected:
50+
-
51+
astkey:
52+
-
53+
key1: '12345'
54+
key2: 'value2'
55+
-
56+
name: sub_json_directive
57+
test:
58+
-
59+
jsonkey: '!JSON[{"key1": "12345", "key2": "value2"}]'
60+
expected:
61+
-
62+
jsonkey:
63+
-
64+
key1: '12345'
65+
key2: 'value2'
66+
-
67+
name: int_directive
68+
test:
69+
-
70+
key1: "!INT12345"
71+
expected:
72+
-
73+
key1: 12345
74+
-
75+
name: multi_directives_ast_int
76+
test:
77+
-
78+
astkey: "!AST[{u'intkey': u'!INT12345'}]"
79+
expected:
80+
-
81+
astkey:
82+
-
83+
intkey: 12345
84+
-
85+
name: multi_directives_json_int
86+
test:
87+
-
88+
jsonkey: '!JSON[{"intkey": "!INT12345"}]'
89+
expected:
90+
-
91+
jsonkey:
92+
-
93+
intkey: 12345
2894

tests/test_actions_lib_ansiblebaserunner.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,24 @@ def extra_vars_complex_yaml_fixture(self, test_name):
121121

122122
def test_parse_extra_vars_complex_yaml_arbitrarily_complex(self):
123123
self.extra_vars_complex_yaml_fixture('arbitrarily_complex')
124+
125+
def test_parse_extra_vars_complex_yaml_ast_directive(self):
126+
self.extra_vars_complex_yaml_fixture('ast_directive')
127+
128+
def test_parse_extra_vars_complex_yaml_json_directive(self):
129+
self.extra_vars_complex_yaml_fixture('json_directive')
130+
131+
def test_parse_extra_vars_complex_yaml_sub_ast_directive(self):
132+
self.extra_vars_complex_yaml_fixture('sub_ast_directive')
133+
134+
def test_parse_extra_vars_complex_yaml_sub_json_directive(self):
135+
self.extra_vars_complex_yaml_fixture('sub_json_directive')
136+
137+
def test_parse_extra_vars_complex_yaml_int_directive(self):
138+
self.extra_vars_complex_yaml_fixture('int_directive')
139+
140+
def test_parse_extra_vars_complex_yaml_multi_directives_ast_int(self):
141+
self.extra_vars_complex_yaml_fixture('multi_directives_ast_int')
142+
143+
def test_parse_extra_vars_complex_yaml_multi_directives_json_int(self):
144+
self.extra_vars_complex_yaml_fixture('multi_directives_json_int')

0 commit comments

Comments
 (0)