Different variables values for every host in group in ansible - configuration

I have a role in ansible that expects different variables. I want to apply this role to a host group but every host in the group needs different values for this role.
I tried to archive this with this configuration:
Group Var:
host1:
var1: 'project a'
var2: 'some other'
host2:
var1: 'project b'
var2: 'some different'
hosts:
[myHosts]
host1
host2
But I have no clue how can I loop to the different hosts in a play
#something before
- hosts: myHosts
become: true
roles:
- docker
- docker-compose
- git
vars:
- var1: ??
# Something like this possible?
- var2: currentHost.var2
Or is my attempt wrong and I use the tool incorrect?
This role is the last step for a deployment. So in the vars I want to say something like become project a or become project b. Would this be bad practice? Should I better have a role project a, project b and assign them to the specific host via playbook even if the only difference between the two roles are some env variables?

If you want to keep the data in group_vars put the variables into a dictionary. For example,
shell> cat group_vars/myhosts.yml
my_dict:
host1:
var1: 'project a'
var2: 'some other'
host2:
var1: 'project b'
var2: 'some different'
Then, the variables may be easily "assigned" to each host
- hosts: myhosts
vars:
var1: "{{ my_dict[inventory_hostname]['var1'] }}"
var2: "{{ my_dict[inventory_hostname]['var2'] }}"
However, the best practice is to put such variables into the host_vars and let Ansible "assign" the variables to the hosts automatically. For example,
shell> cat host_vars/host1.yml
var1: 'project a'
var2: 'some other'
shell> cat host_vars/host2.yml
var1: 'project b'
var2: 'some different'
See Group And Host Variables.
Notes
To simplify the code and improve the readability, you can put the declarations of var* also into the group_vars. For example,
shell> cat group_vars/myhosts.yml
my_dict:
host1:
var1: 'project a'
var2: 'some other'
host2:
var1: 'project b'
var2: 'some different'
var1: "{{ my_dict[inventory_hostname]['var1'] }}"
var2: "{{ my_dict[inventory_hostname]['var2'] }}"

Related

Set an env variable using bash command that checks for existence of input variable

I've been reading about expressions.
I need to create an env var that all jobs in a workflow can reference that, in English, checks for the existence of ${{ inputs.db_schema }} and if it exists, use it, otherwise set it to 'prod'.
Tried (borrows from JWLs solution on this SO post):
env:
db_schema: ${{ ${{inputs.db_schema}} :-'prod }}
This returned an error when I tried to run:
The workflow is not valid. .github/workflows/main.yaml (Line: 17, Col: 14): Unexpected symbol: '${{inputs'. Located at position 1 within expression: ${{inputs.db_schema .github/workflows/update-sk4-caller.yaml (Line: 18, Col: 16): Unexpected symbol: '${{inputs'. Located at position 1 within expression: ${{inputs.update_date
How can I create an env variable that can be used by all jobs in a workflow where the value is either what exists in ${{inputs.db_schema}} else if that input doesn't exist then 'prod'?
[EDIT]
Adding a more complete example of what I"m trying to do. Here's a piece of my workflow:
name: MyWorkflow
on:
workflow_dispatch:
inputs:
db_schema:
required: true
type: string
default: US.DATA_SCIENCE
schedule:
- cron: '10 3 * * *' # daily at ten past 3am
env:
db_schema: ${{inputs.db_schema || 'US.DATA_SCIENCE'}}
jobs:
check-env-vars:
runs-on: ubuntu-latest
steps:
- name: check env vars
run: |
echo ${{ env.db_schema }}
update-clicks:
uses: ./.github/workflows/update-clicks.yaml
secrets: inherit
with:
db_schema: ${{ env.db_schema }}
I need to pass a value to db_schema within the with statement.
You're mixing access to GitHub Actions context and shell parameter expansion. You might be able to do something like
db_schema: ${{ inputs.db_schema || 'prod' }}
or if you access db_schema in a shell script, you could use
db_schema: ${{ inputs.db_schema }}
and then access via
${db_schema:-prod}

JMESPath Query in Ansible

--- EDIT ---
Partial-Solution: Messed around with the JMESPath syntax and was able to successfully get a match for the first test case (without the optional variable) using:
jmesquery: "{{ datacenter }}{{ subcategory }}.{{ refine_hosts }}.[*][].[*][][]"
I am writing an Ansible Playbook that takes a list of hosts from a network server, parses the JSON list, and finds hostnames that matches the user's input when they deploy the playbook as a Jenkin's Job through it's API.
The issue I am encountering is that I am unable to successfully query the JSON host list. Currently, I am only trying to run the following test case:
datacenter: a
subcategory: bc
refine_hosts: QA
However, the final version of this playbook should be able to take in values for datacenter, subcategory, and refine_hosts with an optional input value of host_type. An example test case including the optional input value would be the following:
datacenter: a
subcategory: bc
refine_hosts: QA
host_type: WEBSITE
In my playbook, I am using JMESPath within the following task:
- name: Build HOSTS list
set_fact:
hosts_list: "{{ jsondata | json_query(jmesquery) }}"
vars:
jmesquery: '%%datacenter%%-%%subcategory%%.%%refine_hosts%%.[*][*][][]'
The JSON host list is structured in the following manner (I am unable to edit the structure of the host list, but it will always follow the following structure nonetheless):
{
"a-bc":{
"all":{
"webServer":[
],
"archive":[
"someHostAlias-123.privateDomain.com"
],
"central":[
"someHostAlias-456.privateDomain.com"
]
},
"QA":{
"xyz":{
"INBOUND_HTTP":[
"someHostAlias-789.privateDomain.com"
],
"WEBSITE":[
"someHostAlias-1011.privateDomain.com"
]
}
}
}
}
I have been using the following websites for this issue:
JMESPath Tutorial
Ansible JMESPath Documentation
JSONPath Expression Tester
StackOverflow: How to Use Variable in JMESPath Expression
Gitter: JMESPath/chat
I apologize if the query seems obvious, this is my first attempt at an Ansible Playbook. All help/feedback is greatly appreciated.
One of the issue of your query is that you are confusing [*] — a list projection — that selects all the elements of a list with .* — an object projection — that selects all the properties of a dictionary.
So, one solution in JMESPath, would be to do:
jmesquery: >-
"{{ datacenter }}-{{ subcategory }}".{{ refine_hosts }}.*.
{{ host_type if host_type | default('') != '' else '*' }}[] | []
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: "{{ jsondata | json_query(jmesquery) }}"
loop: "{{ fake_user_input }}"
loop_control:
label: "{{ jmesquery }}"
vars:
jmesquery: >-
"{{ datacenter }}-{{ subcategory }}".{{ refine_hosts }}.*.
{{ host_type if host_type | default('') != '' else '*' }}[] | []
datacenter: "{{ item.datacenter }}"
subcategory: "{{ item.subcategory }}"
refine_hosts: "{{ item.refine_hosts }}"
host_type: "{{ item.host_type | default('') }}"
fake_user_input:
- datacenter: a
subcategory: bc
refine_hosts: QA
host_type: WEBSITE
- datacenter: a
subcategory: bc
refine_hosts: QA
jsondata:
a-bc:
all:
webServer: []
archive:
- someHostAlias-123.privateDomain.com
central:
- someHostAlias-456.privateDomain.com
QA:
xyz:
INBOUND_HTTP:
- someHostAlias-789.privateDomain.com
WEBSITE:
- someHostAlias-1011.privateDomain.com
This yields:
ok: [localhost] => (item="a-bc".QA.*. WEBSITE[] | []) =>
msg:
- someHostAlias-1011.privateDomain.com
ok: [localhost] => (item="a-bc".QA.*. *[] | []) =>
msg:
- someHostAlias-789.privateDomain.com
- someHostAlias-1011.privateDomain.com

Map an environment variable in Github Actions

I created a GitHub Actions Job with a strategy matrix that creates a set of environment variables.
One of them is machine_architecture which is either 32 or 64.
In most steps I can use it directly i.e. via ${{ machine_architecture }}.
But some steps requires strings like 'i386' vs 'x86_64'. Is there an easy way in github actions to create a map-object that I can use in expressions like:
map_object = { 32: "i386", 64: 'x86_64' }
...
${{ map_object[machine_architecture] }}
If not, what is the idiomatic way in github actions to solve that problem?
PS: I am aware, that I can set environment variables in steps, but the problem is, that these variables are only available for the following steps (i.e. not for usage in "run-on:" tag)
In the meantime I found a solution:
Although GitHub Actions has no syntax for directly creating a Mappings/Objects it can be done indirectly with fromJson():
${{ fromJson('{ 32: "i386", 64: "x86_64" }')[machine_architecture] }}
this fromJson() will create a mapping from int to string. the following []-operator resolves the int type "machine_architecture" to a string type.
Here is a way to do it with JSON and jq. It creates the step output ${{ steps.vars.outputs.arch }} which you can use in later steps.
jobs:
varMap:
strategy:
matrix:
machine_architecture: [32, 64]
runs-on: ubuntu-latest
steps:
- name: Set arch var
id: vars
run: |
echo ::set-output name=arch::\
$(echo '{ "32": "i386", "64": "x86_64" }' | jq -r 'to_entries[] | select(.key=="${{ matrix.machine_architecture }}") | .value')
- name: Test arch var
run: echo "Testing ${{ steps.vars.outputs.arch }}"
I don't need a matrix for this, but I need a map for lookups.
Couldn't get #PeterEvans answer to work with GitHub, so I adapted it slightly:
jobs:
yourJobName:
name: Cool Job
runs-on: ubuntu-latest
steps:
- name: Get Machine Arc
run: |
MACHINE_ARC_MAP=$(cat <<END
{
32: "i386",
64: "x86_64",
999: ${{ secrets.SECRET_ARC }}
}
END
)
TARGET_ARC=$(echo $MACHINE_ARC_MAP | jq -r 'to_entries[] | select(.key=="${{ github.event.inputs.machine_architecture }}") | .value')
echo "TARGET_ARC=$TARGET_ARC" >> $GITHUB_ENV
- name: Echo Selected value
run: |
echo ${env.TARGET_VAR}
How about your "map_object" is actually a file mapping machine_architecture values into the values you need, such:
32=i386
64=x86_64
or any other format you want to keep.
Then, your job can define it in a secondary variable as:
jobs:
FirstJob:
name: job 1
runs-on: .....
steps:
- uses: ....
- name: Define variables
run: |
cat $(cat MAP_OBJECT_FILE_NAME) | grep $(cat machine_architecture)= | > MACHINE_ARCHITECTURE_STRING
From there on, you would have the MACHINE_ARCHITECTURE_STRING variable available for the jobs you need. You could of course do it much more simple concatenating or whatever, but here you maintain the mapping in your code with the mapping file and is escalable.

Ansible: Issues when importing boolean from json file

I want to use a json file for user management in ansible. Therefore I created a json file containing all the user and groups like this (user_group_management.json):
{
"linux_users": [
{
"name": "myuser",
"uid": 1003,
"group": "myuser",
"groups": "users,sudo",
"shell": "/bin/bash",
"password": <password as sha-512>,
"create_home": "yes",
"home": "/home/user",
"hosts": ["hostname1","hostname2","hostname3"]
},
{...},
...
],
"linux_groups": [...],
}
Now I wrote an ansible script for adding the users and groups (user_group_management.yml):
- name: User and group management
hosts: all
vars_files:
- user_group_management.json
tasks:
[part for adding groups (no problems here)]
- name: Add users
ignore_errors: yes
user:
name: item.name
uid: item.uid
group: item.group
groups: item.groups
shell: item.shell
password: item.password
create_home: item.create_home
home: item.home
state: present
when: ansible_hostname in item.hosts
loop: "{{ linux_users }}"
When running the ansible script using ansible-playbook --check user_management.yml I get these error messages concerning the "Add users" task:
"msg": "The value 'item.create_home' is not a valid boolean. Valid booleans include: 0, 'on', 'f', 'false', 1, 'no', 'n', '1', '0', 't', 'y', 'off', 'yes', 'true'"
I tried to fix this issue by replacing "create_home": "yes", with "create_home": 1, but it did not change anything. Now I added curly brackets around the variable (item.create_home -> "{{ item.create_home }}") which solved the issue. So now I do not get any error messages again.
Sadly I do not understand why this helped me. I thought that item.create_home is a string ("yes") in the first place and should be an integer (1) after editing the json file. But both give me the errors. Is there an explanation for this phenomenon?
If don't have a lot of experience with json manipulation in ansible however have you tried to change
"create_home": "yes",
to
"create_home": true,
Another solution it to convert the value you expect to be boolean with a jinja filter |bool when using it.
The official documentation provides
- debug:
msg: test
when: some_string_value | bool
as an example with when condition.
For your case it should be
create_home: item.create_home
should become
create_home: "{{ item.create_home | bool }}"
general advice: You should enclose your variables inside "{{ }}"

Ansible: parsing/concatenating output of getent module

I try to set up chroot for sftp users, so that they can see user/group names on ls -l as per this article. To this end I need to get output of getent command and place it into /chroots/{{ user.username }}/etc/passwd file.
I try to use Ansible to replace this command getent passwd sftpuser > /chroots/sftpuser/etc/passwd as follows:
- name: get {{ user.username }} user info
getent:
database: passwd
key: "{{ user.username }}"
- debug:
var: getent_passwd
- name: create /chroots/{{ user.username }}/etc/passwd file
lineinfile:
path: /chroots/{{ user.username }}/etc/passwd
line: "{{ getent_passwd | from_json }}"
state: present
create: yes
owner: root
group: root
mode: '0644'
The 'getent_passwd' looks as follows:
ok: [cf1] => {
"getent_passwd": {
"testuser1": [
"x",
"1001",
"1002",
"",
"/home/testuser1",
"/usr/sbin/nologin"
]
}
}
But I get this error: FAILED! => {"failed": true, "msg": "Unexpected templating type error occurred on ({{ getent_passwd | from_json }}): expected string or buffer"}
What is the proper way to get those values supplied by getent_passwd into one flat string joined by ":"?
Is it safe to use genent module with key: "root" this way instead of echo "root:x:0:0:not really root:::" >> /chroots/sftpuser/etc/passwd?
one can run getent passwd user1 user2 - is it possible to supply two keys to the ansible's getent module somehow?
What is the proper way to get those values supplied by getent_passwd into one flat string joined by ":"?
For example using a Jinja2 template with join filter:
- debug:
msg: "{{ user.username }}:{{getent_passwd[user.username]|join(':')}}"
One can run getent passwd user1 user2 - is it possible to supply two keys to the ansible's getent module somehow?
No. Either a single one or all.
Use an outer loop to request values in the first case, or filter the resulting list in the second.