ansible - changing a specific line in configuration file using a jinja template - linux

I have an .ini config file that I'd like to template, but I only want to manipulate one or two lines with the jinja2 files.
example config file:
[configuration]
config1 = this_is_fine
config2 = to_be_templated
config3 = to_be_templated
config_4 = this_is_fine
What would be the best way to define a configuration.ini.j2 that would only change
config2 = {{ assigned_value }}
config3 = {{ assigned_value }}
while keeping the formatting and remaining configuration in place when running the playbook?

Q: "Manipulate one or two lines with the Jinja2 files."
A: "One or two lines" means a block. Use blockinfile to manipulate a block in a file. You'll need markers to do this, e.g. given the file
shell> cat conf.ini
[configuration]
config1 = this_is_fine
config2 = to_be_templated
config3 = to_be_templated
config_4 = this_is_fine
the two tasks below put markers around the block starting config2 and ending config3
- replace:
path: conf.ini
regexp: '(config2.*)'
replace: |-
{{ '#' }} BEGIN ANSIBLE MANAGED BLOCK c2-3
\g<1>
- replace:
path: conf.ini
regexp: '(config2.*[\s\S]*?config3.*)'
replace: |-
\g<1>
{{ '#' }} END ANSIBLE MANAGED BLOCK c2-3
gives
shell> cat conf.ini
[configuration]
config1 = this_is_fine
# BEGIN ANSIBLE MANAGED BLOCK c2-3
config2 = to_be_templated
config3 = to_be_templated
# END ANSIBLE MANAGED BLOCK c2-3
config_4 = this_is_fine
These two tasks are not idempotent. Run them only once. Either put them in a separate playbook to set up the project or test the presence of the markers.
Now you can use the template
shell> cat conf.ini.j2
config2 = {{ assigned_value }}
config3 = {{ assigned_value }}
For example, the task below
- blockinfile:
path: conf.ini
marker: '# {mark} ANSIBLE MANAGED BLOCK c2-3'
block: |
{{ lookup('template', 'conf.ini.j2') }}
vars:
assigned_value: AVALUE
gives
shell> cat conf.ini
[configuration]
config1 = this_is_fine
# BEGIN ANSIBLE MANAGED BLOCK c2-3
config2 = AVALUE
config3 = AVALUE
# END ANSIBLE MANAGED BLOCK c2-3
config_4 = this_is_fine

Related

Passing Jinja variables in airflow params dict

I am trying to use templated params dict in SnwoflakeOperator. I have 2 tasks one to create a S3 object and then delete and replace the content of the table with s3 object content
execution_date = "{{ execution_date.int_timestamp | int }}" # built in macro
task1 = PythonOperator(task_id='s3_create',
op_kwargs={'s3_key': f'{execution_date}_{file}'})
task_2 = SnowflakeOperator(task_id='load_data',
sql=["""copy into {{params.table}} from {{ params.stage }}
files = ('{{ params.files }}')
file_format = 'csv'
"""]
params={'table':'test_table',
'files'=f'{execution_date}_{file}'}
)
task_1 >> task_2
It is not rendering as expected and have tried almost all formatting combinations.
Note: I cannot use S3ToSnowflakeOperator as I have do more than just copying the contents into the table.
sql is templated filed thus it can automatically render execution_date (since it's Airflow build-in macro) there is no need to set it via params. The params is used to render python variables or just strings that you want to pass.
You can just set it directly as:
file = "test_file.csv"
SnowflakeOperator(
task_id='load_data',
sql=""" copy into {{ params.table }} from {{ params.stage }}
files = ('{{ execution_date }}_{{ params.file }}')
file_format = csv
""",
params={
'table': 'test_table',
'stage': 'test_stage',
'file': file
}
)
It will be rendered as:
I'm not sure this is the file name you wanted but you can change execution_date to any other macro you wish.
Edit:
You can replace {{ execution_date }} with {{ execution_date.int_timestamp | int }} it will produce:

How to test if a value is a string in a template

I would like to find out if it is possible and if so how, to test if a value is a string in a Go template.
I have tried the following with no success
{{- range .Table.PrimaryKeys.DBNames.Sorted }}{{ with (index $colsByName .)}}
{{ .Name }}: {{ if .IsArray }}[]{{ end }}'{{.Type}}', {{end}}
{{- end }}
{{- range $nonPKDBNames }}{{ with (index $colsByName .) }}
{{ .Name }}: {{ if .IsArray }}[]{{end -}} {
type: {{ if .Type IsString}}GraphQLString{{end -}}, # line of interest where Type is a value that could be a number, string or an array
}, {{end}}
{{- end }}
And this is the error that I get
Error: error parsing TablePaths: error parsing contents template: template: templates/table.gotmpl:42: function "IsString" not defined
With a custom function
There is no predeclared IsString() function available in templates, but we may easily register and use such a function:
t := template.Must(template.New("").Funcs(template.FuncMap{
"IsString": func(i interface{}) bool {
_, ok := i.(string)
return ok
},
}).Parse(`{{.}} {{if IsString .}}is a string{{else}}is not a string{{end}}`))
fmt.Println(t.Execute(os.Stdout, "hi"))
fmt.Println(t.Execute(os.Stdout, 23))
This will output (try it on the Go Playground):
hi is a string<nil>
23 is not a string<nil>
(The <nil> literals at the end of lines are the error values returned by the template execution, telling there were no errors.)
Using printf and %T verb
We may also do this without custom functions. There is a printf function available by default, which is an alias for fmt.Sprintf(). And there is a %T verb which outputs the argument's type.
The idea is to call printf %T on the value, and compare the result with "string", and we're done:
t := template.Must(template.New("").
Parse(`{{.}} {{if eq "string" (printf "%T" .)}}is a string{{else}}is not a string{{end}}`))
fmt.Println(t.Execute(os.Stdout, "hi"))
fmt.Println(t.Execute(os.Stdout, 23))
This will also output (try it on the Go Playground):
hi is a string<nil>
23 is not a string<nil>

Ansible loop over list var

I am trying to add users belongs to group abc into group xyz. to do this in ansible I am doing following
- name: Get the list of user belongs to abc group
command: lid -g -n abc
register: abc_users
- name: Add user belongs to abc group to xyz group
user: name={{ items }} groups=xyz append=yes
with_items: "{{ abc_users.stdout }}"
But getting following error
fatal: [10.8.17.14]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'items' is undefined\n\nThe error appears to have been in '/home/#/workspace/#/ansible/roles/ubuntu-common/tasks/main.yml': line 26, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: Add user belongs to sentience group to docker group\n ^ here\n"}
Anyone know better way to do this ?
Thanks
Replace {{ items }} with {{ item }}.
Update: as #udondan points out, iterate over stdout_lines
- name: Add user belongs to abc group to xyz group
user: name={{ item }} groups=xyz append=yes
with_items: "{{ abc_users.stdout_lines }}"

In Twig, how do I use a dynamic key when translating

In my translation yml file I have these translations setup
pages:
training_missions:
...
application_name:
admin: "Admin Website"
mobile: "Mobile App"
kiosk: "Kiosk"
In my twig file, I need to set the application_name dynamically, but I can't get it to work properly.
This will translate fine, it gives me "Mobile App"
{{ 'pages.training_missions.application_name.mobile' | trans()}}
But this doesn't work, it gives me "pages.training_missions.application_name.mobile"
{{ 'pages.training_missions.application_name.'~trainingMission.application | trans() }}
edit:
The variable trainingMission.application contains one of the 3 strings I put in the yaml file : admin, mobile, kiosk
edit 2:
The solution is to wrap the string in parenthesis as per #Matteo 'Ingannatore' G. comment
It is possible! Use round brackets like this:
{{ ('pages.training_missions.application_name.'~trainingMission.application) | trans() }}
Use array accessor syntax:
{{ pages[training_missions].application_name.mobile }}

Twig - use quotation mark as separator for join filter

I pass my template an array of strings which I would like to convert to a jaavascript array:
Controller file (php):
$myVar = array('a','b','c');
Desired html:
var myVar = ["a","b","c"];
I try the following code (twig):
var myVar = ["{{ myVar | join('","') }}"];
But the twig generator converts the quotation marks to html entities and this is the result:
var myVar = ["a","b","c"];
Some idea?
You need to apply the raw filter:
var myVar = ["{{ myVar | join('","') | raw }}"];
Maerlyn's answer will work, however the drawback with it is that the values in the myVar array will be outputted raw too, which, depending on the origin of that variable, could lead to vulnerabilities in your website, such as XSS.
I found two ways to do this while maintaining the escaping on the array values. The first is using a loop with an if statement to check whether it's the last iteration to determine whether we need to output the "glue" used in the join or not:
var myVar = [{% for val in myVar %}"{{ val }}"{% if loop.last == false %},{% endif %}{% endfor %}]
The second way is to let PHP your PHP handle everything, including the escaping, and then output the raw string in Twig:
$arr = array_map(
function($value) {
return '"' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"';
},
$arr
);
$myVar = '[' . implode(',', $arr) . ']';
Then pass the $myVar variable to your view, and then you can just do:
{{ myVar|raw }}

Resources