Ansible loop to add member in Azure AD group using graph api - azure

I have been stuck to loop over each "members" in variable file. Reason to take each members is to get user_id and passing those user id in graph api will be able to POST those members in specific AD group.
main.yml
- name: Check if Group Exists
include_tasks: tasks/groups/check_group.yml
- name: Create Group
include_tasks: tasks/groups/create_group.yml
when: item.state == "present" and az_group_id == ""
- name: Populate the group with members
include_tasks: tasks/groups/add_member.yml
when: az_group_id != "" and item.state == "present"
loop: "{{ az_group_config_data }}"
add_member.yml
- name: Get Azure AD User ID
include_tasks: tasks/users/gets/get_user_id.yml
- name: Add Member to a group
uri:
url: "{{ graph_api_base_url }}{{ group_service_context }}/{{ az_group_id }}/members/$ref"
method: POST
body: "{{ lookup('template','./add-group-member-body.j2') }}"
status_code: 204
return_content: yes
use_proxy: no
headers:
Authorization: Bearer {{ token }}
body_format: json
register: group_create_result
get_userid.yml
- name: Register list of Azure Users as Ansible Fact
uri:
url: "{{ graph_api_base_url }}{{ user_service_context }}?$top=999&$select=mail,id"
method: GET
status_code: 200
use_proxy: no
headers:
Authorization: Bearer {{ token }}
register: user_list_result
- name: Register User {{ az_user_mail }} ID as Ansible Fact
set_fact:
az_user_id: "{{ user_list_result.json.value | json_query(az_user_id_query) }}"
defaults/main.yml
az_group_config_data:
- az_group_name: ghe-test-users
aws_account: sbx
environment: poc
state: present
members:
- name#name.com
- name#example.com
- test#test.com
vars/main.yml
az_group_name: "{{ item.az_group_name }}"
az_user_mail: "{{ item.members }}"
# api helpers
graph_api_base_url: "https://graph.microsoft.com/v1.0/"
group_service_context: "groups"
user_service_context: "users"
# query helpers
az_group_id_query: "[?displayName == '{{ az_group_name }}'].id | [0]"
az_user_id_query: "[?mail == '{{ az_user_mail }}'].id | [0]"
add-group-member-body.j2
{
"#odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/{{ az_user_id }}"
}
Below code works to fetch email accounts
- name: latest-debug
set_fact:
az_user_mail: "{{ populate.1 }}"
loop: "{{ az_group_config_data|subelements('members') }}"
loop_control:
loop_var: populate
My issue is only last email account is getting created.

Related

How to use "maps" in anisble along with "with_items"

I am trying to add labels to each node where a label is a map as:
set_node_labels:
topology.kubernetes.io/region: "syd"
topology.kubernetes.io/zone: "syd01"
I have written the Ansible task as follows, however it does not work as expected:
- name: Get all Nodes
shell: "oc get nodes | awk '(NR>1) { print $1 }'"
register: node_names
- name: Print phone records
k8s:
state: present
kind: Node
name: "{{ item }}"
definition:
metadata:
labels: "{{ item.key }} {{ item.value }}"
loop: "{{ lookup('dict', set_node_labels) }}"
with_items: "{{ node_names.stdout_lines }}"
You can also use your existing code with a little tweak
- name: Get all Nodes
shell: "oc get nodes | awk '(NR>1) { print $1 }'"
register: node_names
- name: Print phone records
k8s:
state: present
kind: Node
name: "{{ item }}"
definition:
metadata:
labels: "{{ set_node_labels }}"
with_items: "{{ node_names.stdout_lines }}"
First things first, you should use the existing module, when they are available instead of a shell module.
In your case, you can get the information about your nodes thanks to the k8s_info module.
So, your first taks should be:
- name: Get all Nodes
k8s_info:
kind: Node
register: node_names
Then in order to pass your labels, those should actually be in a dictionary, so you should be able to pass the whole set_node_labels as labels:
- name: Print phone records
k8s:
state: present
kind: Node
name: "{{ item.metadata.name }}"
definition:
metadata:
labels: "{{ set_node_labels }}"
loop: "{{ node_names.resources }}"

Multiple with_items in an Ansible module block

I want to create multiple logical volumes with a variable file but it return a sintax error found character that cannot start any token, I have tried in different ways but still doesn't work
main.yml
---
- name: playbook for create volume groups
hosts: localhost
become: true
tasks:
- include_vars: vars.yml
- name: Create a logical volume
lvol:
vg: vg03
lv: "{{ item.var1 }}"
size: "{{ item.var2 }}"
with_items:
- { var1: "{{ var_lv_name }}", var2: "{{ var_lv_size }}" }
vars.yml
var_lv_name:
- lv05
- lv06
var_lv_size:
- 1g
- 1g
Use with_together. Test it first. For example,
- debug:
msg: "Create lv: {{ item.0 }} size: {{ item.1 }}"
with_together:
- "{{ var_lv_name }}"
- "{{ var_lv_size }}"
gives (abridged)
msg: 'Create lv: lv05 size: 1g'
msg: 'Create lv: lv06 size: 1g'
Optionally, put the declaration below into the file vars.yml
var_lv: "{{ var_lv_name|zip(var_lv_size) }}"
This creates the list
var_lv:
- [lv05, 1g]
- [lv06, 1g]
Use it in the code. The simplified task below gives the same results
- debug:
msg: "Create lv: {{ item.0 }} size: {{ item.1 }}"
loop: "{{ var_lv }}"
The previous answer it's totally correct but In my humble opinion we should be getting into the new way to do the things with loop and filters.
Here's my answer:
---
- name: playbook for create volume groups
hosts: localhost
gather_facts: no
become: true
vars_files: vars.yml
tasks:
- name: Create a logical volume
lvol:
vg: vg03
lv: "{{ item[0] }}"
size: "{{ item[1] }}"
loop: "{{ var_lv_name | zip(var_lv_size) | list }}"
In this answer you're using the new way to use loops with keyword loop and using filters like zip and turning the result into a list type for iteration in the loop.

Error getting an item from json file in Ansible

I am trying to get the subnets from a json query in Ansible. My debug is result is as below:
"msg": [
{
"address_prefix": "10.10.2.0/23",
"id": "/subscriptions/xxxxxxxxx-xxxxxxxxxxx-xxxxxxxx-xxx/resourceGroups/myRG/providers/Microsoft.Network/virtualNetworks/MYVNET/subnets/MySubnet",
"name": "MySubnet",
"network_security_group": "/subscriptions/xxxxxxxxx-xxxxxxxxxxx-xxxxxxxx-xxx/resourceGroups/MYVNET/providers/Microsoft.Network/networkSecurityGroups/mynsg",
"provisioning_state": "Succeeded",
"route_table": null
}
]
I am trying to get the name of the subnet. My Ansible play is as below:
tasks:
- name: "Retrieve resourcegroup infos"
azure_rm_virtualnetwork_info:
register: object_vnet
- name: get subnet name
debug: msg='{{ item.value }}'
with_dict: "{{ object_vnet.virtualnetworks }}"
when:
- item.key == 'subnets'
- item.value.name is match *MySub*
The error that I get , is as below:
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'item.value.name is match *-MySub*' failed.
The error was: template error while templating string: unexpected 'end of statement block'.
String: {% if item.value.name is match *MySub* %} True {% else %} False {% endif %}\n\n
The error appears to be in '/myplay.yml': line 41, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\n
The offending line appears to be:\n\n\n - name: get subnet name\n ^ here\n"}
Any ideas to help?
subnets are a list, so you need to loop on subnets objects of your vnets and loop on all subnets in a vnet.
to do this, you can create a separate file to loop and you subnets and loop on this file:
loopfile:
- name: create facts azure_subnets_result
set_fact:
azure_subnets_result: >-
{{
( azure_subnets_result | default([]) )
+ [ az_sub_item ]
}}
loop_control:
loop_var: az_sub_item
loop: "{{ az_sub }}"
when: (az_sub_item.name | lower ) is match(".*mysub.*")
main file:
tasks:
- name: "Retrieve resourcegroup infos"
azure_rm_virtualnetwork_info:
register: object_vnet
- name: create azure_subnets object
set_fact:
azure_subnets: "{{ object_vnet.virtualnetworks | json_query(vnet_query) }}"
vars:
vnet_query: "[*].subnets"
- name: loop on all object subnets and all sub objects
include_tasks: loop.yml
loop_control:
loop_var: az_sub
loop: "{{ azure_subnets }}"
- name: debug message
debug: msg='{{ azure_subnets_result }}'

Ansible playbook loop with with_items

I have to update sudoers.d multiple user files with few lines/commands using ansible playbook
users.yml
user1:
- Line1111
- Line2222
- Line3333
user2:
- Line4444
- Line5555
- Line6666
main.yml
- hosts: "{{ host_group }}"
vars_files:
- ../users.yml
tasks:
- name: Add user "user1" to sudoers.d
lineinfile:
path: /etc/sudoers.d/user1
line: '{{ item }}'
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ user1 }}"
The above one is working only for user1..
If I want to also include user2 --> How to change the file name : path: /etc/sudoers.d/user1
I tried below and its not working :
Passing below users as variable to main.yml while running
users:
- "user1"
- "user2"
- name: Add user "{{users}}" to sudoers.d
lineinfile:
path: /etc/sudoers.d/{{users}}
line: '{{ item }}'
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ users }}"
So, basically I want to pass in users to a variable {{users}} as user1 and user2 and wanted to use the lines for each user from users.yml and add it to respective user files (/etc/sudoers.d/user1 and /etc/sudoers.d/user2).
So /etc/sudoers.d/user1 should look like
Line1111
Line2222
Line3333
and /etc/sudoers.d/user2 should look like
Line4444
Line5555
Line6666
Try to add quotes:
users:
- "user1"
- "user2"
- name: "Add user {{users}} to sudoers.d"
lineinfile:
path: "/etc/sudoers.d/{{users}}"
line: "{{ item }}"
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ users }}"
As per Ansible Documentation on Using Variables:
YAML syntax requires that if you start a value with {{ foo }} you quote the whole line, since it wants to be sure you aren’t trying to start a YAML dictionary. This is covered on the YAML Syntax documentation.
This won’t work:
- hosts: app_servers
vars:
app_path: {{ base_path }}/22
Do it like this and you’ll be fine:
- hosts: app_servers
vars:
app_path: "{{ base_path }}/22"
cat users.yml
---
users:
- user1:
filename: user1sudoers
args:
- Line1111
- Line2222
- Line3333
- user2:
filename: user2sudoers
args:
- Line4444
- Line5555
- Line6666
I use template here, instead of lineinfile
---
cat sudoers.j2
{% if item.args is defined and item.args %}
{% for arg in item.args %}
{{ arg }}
{% endfor %}
{% endif %}
the task content
---
- hosts: localhost
vars_files: ./users.yml
tasks:
- name: sync sudoers.j2 to localhost
template:
src: sudoers.j2
dest: "/tmp/{{ item.filename }}"
loop: "{{ users_list }}"
when: "users_list is defined and users_list"
after run the task.yml, generate two files under /tmp directory.
cat /tmp/user1sudoers
Line1111
Line2222
Line3333
cat /tmp/user2sudoers
Line4444
Line5555
Line6666

Using ansible launch configuration module ec2_lc and securitygroup names versus id

I want to accomplish the following in aws ec2:
Create security groups using ansible module ec2_group.
Create a launch configuration using ansible module ec2_lc and attach a security group created earlier.
Now, i want to use the security group names instead of id's because i want to be able to recreate the whole infrastructure with ansible if needed.
Recreating security groups will cause the id of the group to be different.
But the ec2_lc module only accepts security group id's.
Is there any way i can map a security group id to a name?
I am defining security groups like this:
- name: create ec2 group
ec2_group:
name: "{{ item.name }}"
description: "{{ item.description }}"
vpc_id: "{{ item.vpc_id }}"
region: "{{ item.region }}"
state: present
rules: "{{ item.rules }}"
rules_egress: "{{ item.rules_egress }}"
register: sg
The launch configuration code looks like this:
- name: Create Launch Configuration
ec2_lc:
region: "{{ item.region }}"
name: "{{ item.name }}"
image_id: "{{ item.image_id }}"
key_name: "{{ item.key_name }}"
security_groups: "{{ item.security_groups }}" # how can i refer to specific group_id based on a group name?
instance_type: "{{ item.instance_type }}"
user_data: "{{ item.ec2_user_data }}"
instance_profile_name: "{{ item.instance_profile_name }}"
assign_public_ip: "{{ item.assign_public_ip }}"
Use the ec2_group-facts to query the security groups by name:
- ec2_group_facts:
filters:
group-name:
- "{{ sg.name }}"
register: ec2sgs
- debug:
msg: "{{ ec2sgs.security_groups | map(attribute='group_id')| list }}"
With some tribute to this question, you can try this:
- name: Create Launch Configuration
ec2_lc:
...
security_groups: "{{ sg.results | selectattr('item.name','equalto',item) | join('',attribute='group_id') }}"
...
You can write a filter, that can make an aws api call for you dynamically.
For instance I have something like this in my vars/main.yml
public_sg_id: "{{ 'Public' |get_sg(public_vpc_id, aws_region) }}"
Here is the code for get_sg filter.
import boto.ec2
from ansible import errors
def get_sg(name, vpc_id, region):
connect = boto.ec2.connect_to_region(region)
filter_by = {
"tag-key": "Name",
"tag-value": name,
"vpc-id": vpc_id
}
sg_groups = connect.get_all_security_groups(filters=filter_by)
if len(sg_groups) == 1:
return sg_groups[0].id
elif len(sg_groups) > 1:
raise errors.AnsibleFilterError(
"Too many results for {0}: {1}".format(
name, ",".join(sg_groups)
)
)
else:
raise errors.AnsibleFilterError(
"Security Group {0} was not found".format(name)
)

Resources