redhat_cloudforms_azure_arm.../ansible-gpfs-samba-provision/main.yaml

589 lines
20 KiB
YAML
Executable File

---
# PLAY
# Gather cloudforms information and set service name
- name: Query automate workspace
hosts: localhost
gather_facts: False
tasks:
- set_fact:
endpoint: "{{ manageiq.api_url }}"
auth_token: "{{ manageiq.api_token }}"
request_id: "{{ (manageiq.request).split('/')[-1] }}"
service_id: "{{ (manageiq.service).split('/')[-1] }}"
user_id: "{{ (manageiq.user).split('/')[-1] }}"
when: manageiq is defined
# when users run ansible their manageiq auth token does not have sufficient rights to interact with the API, no combination of rights in a role for a non admin user are sufficient
# use the local admin credentials to get an auth token
- name: Get auth token
uri:
url: "{{ endpoint }}/api/auth"
validate_certs: no
method: GET
user: "{{ api_user }}"
password: "{{ api_pass }}"
status_code: 200
register: login
when: manageiq is defined
- set_fact:
auth_token: "{{ login.json.auth_token }}"
when: manageiq is defined
- name: Get requester user attributes
uri:
url: "{{ endpoint }}/api/users/{{ user_id }}"
validate_certs: no
method: GET
headers:
X-Auth-Token: "{{ auth_token }}"
status_code: 200
register: user
when: manageiq is defined
- set_fact:
requester_email: "{{ user.json.email }}"
when: manageiq is defined and user.json.email is not none # cloudforms admin user has no email address (unless set), this is a null field in json
- set_fact:
#requester_email: "{{ from_email }}" # from_email should be able to recieve mail via relay
requester_email: ucats@exmail.nottingham.ac.uk
when: requester_email is not defined
- set_fact:
requester_user: "{{ user.json.name }}"
when: manageiq is defined
# when run from the command line set a default requester user
- set_fact:
requester_user: "command line invocation"
when: manageiq is not defined
# use when we need to determine if this is a self service request - might not be used for this playbook
# - name: get AD account name
# set_fact:
# requester_user_ad: "{{ (user.json.email).split('@')[0] }}"
# when: manageiq is defined
- name: get service
uri:
url: "{{ endpoint }}/api/services/{{ service_id }}"
validate_certs: no
method: GET
headers:
X-Auth-Token: "{{ auth_token }}"
status_code: 200
register: service
when: manageiq is defined
- set_fact:
service_name: "{{ service.json.name }}"
new_service_name: "{{ service.json.name }} {{ request_id }}"
when: manageiq is defined
- set_fact:
service_name: "command line invocation"
new_service_name: "command line invocation"
when: manageiq is not defined
- name: set service name
uri:
url: "{{ endpoint }}/api/services/{{ service_id }}"
validate_certs: no
method: POST
headers:
X-Auth-Token: "{{ auth_token }}"
body_format: json
body: { "action" : "edit", "resource" : { "name" : "{{ new_service_name }}" }}
status_code: 200, 204
register: service
when: manageiq is defined
# PLAY
# Validate parameters passed to script from cloudforms
- name: Script input Validation
hosts: localhost
gather_facts: False
vars_files:
- vars/main.yml
vars:
ownerlist: []
groupmemberslist: []
groupmemberslistvalidate: []
tasks:
# add task to check + fail for any variables with name placeholder here
- name: Convert owner to list
set_fact:
ownerlist: "{{ ownerlist }} + [ '{{ item }}' ]"
with_items: "{{ owner }}"
- name: Split groupmembers parameter on , delimiter
set_fact:
groupmemberslist: "{{ groupmemberslist }} + [ '{{ item }}' ]"
with_items: "{{ groupmembers.split(',') }}"
- name: Remove empty fields from groupmembers parameter
set_fact:
groupmemberslistvalidate: "{{ groupmemberslistvalidate }} + [ '{{ item }}' ]"
when: item | length != 0
with_items: "{{ groupmemberslist }}"
# PLAY
# Add winrm host used for AD querys to inventory
- name: Build inventory for AD server
hosts: localhost
gather_facts: False
vars_files:
- vars/main.yml
tasks:
- name: Add host entry for adserver
add_host: >
name=adserver
groups=windows
ansible_host="{{ ad_host }}"
# PLAY
# Query winrm to validate AD user/group exist, build dict for share ACL and dict of all user/email from a recursively search of user/group
- name: Check AD user exists
hosts: adserver
gather_facts: false
vars_files:
- vars/main.yml
vars:
body_service_name: "{{ hostvars['localhost']['new_service_name'] }}"
requester_email: "{{ hostvars['localhost']['requester_email'] }}"
body_requester_user: "{{ hostvars['localhost']['requester_user'] }}"
ownerlist: "{{ hostvars['localhost']['ownerlist'] }}"
groupmemberslistvalidate: "{{ hostvars['localhost']['groupmemberslistvalidate'] }}"
ownermembers: "{{ ownerlist + groupmemberslistvalidate }}"
no_aduser: []
object_type: []
tasks:
- name: Add connectivity variables for adserver
set_fact:
ansible_user: "{{ ad_user }}"
ansible_password: "{{ ad_pass }}"
ansible_connection: "{{ ad_connection }}"
ansible_winrm_transport: "{{ ad_winrm_transport }}"
ansible_winrm_kinit_mode: "{{ ad_winrm_kinit_mode }}"
ansible_winrm_message_encryption: "{{ ad_winrm_message_encryption }}"
ansible_port: "{{ ad_port }}"
ansible_winrm_scheme: "{{ ad_winrm_scheme }}"
ansible_winrm_server_cert_validation: "{{ ad_winrm_server_cert_validation }}"
# ansible_winrm_operation_timeout_sec: 60
# ansible_winrm_read_timeout_sec: 60
- name: Check AD user/group exists
win_shell: ([ADSISearcher] "(sAMAccountName={{ item }})").FindOne()
register: command_result
with_items:
# - "{{ ownerlist }}"
# - "{{ groupmemberslistvalidate }}"
- "{{ ownermembers }}"
- name: Flag fail where AD user/group not exist
set_fact:
no_aduser: "{{ no_aduser + [item.item] }}"
when: item.stdout | length == 0
with_items: "{{ command_result.results }}"
changed_when: true
notify: topic_noad
- name: Check AD object is user or group
win_shell: ([ADSISearcher] "(sAMAccountName={{ item.item }})").FindOne().Properties.objectcategory
register: object_result
with_items: "{{ command_result.results }}"
when: no_aduser | length == 0
- name: Build list of AD object type
set_fact:
object_type: "{{ object_type + [(item.stdout.split(',')[0].split('CN=')[1])] }}" # faster than regex
with_items: "{{ object_result.results }}"
when: no_aduser | length == 0
# cheat, the first entry in the list ownermember (user or group) is the owner where we merge ownerlist + groupmemberslistvalidate
# for this to work the owner list must have only one entry
# to use this logic for groups of objects (i.e rwx, rw, r groups), we will need to merge a dict with fields name, role and the list object_attributes
- name: Build dict of object names, types and roles positionally from list with object name and list with object type
set_fact:
object_attributes: "{{ object_attributes | default([]) + [dict(name=item[0], type=item[1], role='owner' if object_attributes is undefined else 'member') ] }}"
loop: "{{ ownermembers|zip(object_type)|list }}"
when: no_aduser | length == 0
# - debug:
# msg: "{{ object_attributes }}"
# when: no_aduser | length == 0
# recursive loop control using include_tasks and block rescue behaviour, https://github.com/ansible/ansible/issues/46203
# write/access variables between plays using dummy host variables, https://www.unixarena.com/2019/05/passing-variable-from-one-playbook-to-another-playbook-ansible.html/
- name: Register dummy host with variable object_attributes
add_host:
name: "DUMMY_HOST"
object_attributes: "{{ object_attributes }}"
when: no_aduser | length == 0
- name: Find all group members
include_tasks: group_lookup.yml
when: no_aduser | length == 0
# - name: Inspect all users who will require email notification
# debug:
# msg: "{{ hostvars['DUMMY_HOST']['find_users'] }}"
# when: no_aduser | length == 0
- name: Import dummy host variable from group_lookup.yml
set_fact:
unique_users: "{{ hostvars['DUMMY_HOST']['find_users'] }}"
when: no_aduser | length == 0
- name: Get unique name/role/type entries, remove duplicate entries that may arise from group nesting
set_fact:
unique_users: "{{ unique_users | unique }}"
when: no_aduser | length == 0
# - debug:
# msg: "{{ unique_users }}"
- name: Get owner names into a list
set_fact:
owner_users: "{{ owner_users | default([]) + [item.name] }}"
with_items: "{{ unique_users }}"
when: no_aduser | length == 0 and item.role == 'owner'
- name: Get member names into a list
set_fact:
member_users: "{{ member_users | default([]) + [item.name] }}"
with_items: "{{ unique_users }}"
when: no_aduser | length == 0 and item.role == 'member'
- name: Get names common in both lists
set_fact:
common_users: "{{ owner_users | intersect(member_users) }}"
when: no_aduser | length == 0
# - debug:
# msg: "{{ common_users }}"
# when: no_aduser | length == 0
# KEEP THIS
# this is a dedupe task where duplicate users exist one with role owner and one with role member, the user with role member is removed
# this isnt too helpful in gpfs as the owner attribute is the unix owner of the fileset mount point not a windows ACL
# the logic is useful in a permissions scenario - example multiple entries for a user with rwx and r-- permissions, we want to ensure only the rwx role is used
# - name: Remove member entries where competing owner entry exists
# set_fact:
# email_users: "{{ email_users | default([]) + [dict(name=item.name, role=item.role, type=item.type)] }}"
# with_items: "{{ unique_users }}"
# when: no_aduser | length == 0 and (item.name not in common_users or (item.name in common_users and item.role == 'owner'))
- set_fact:
email_users: "{{ unique_users }}"
when: no_aduser | length == 0
# final deduplicated dict to send appropriate class of email to users
# - debug:
# msg: "{{ email_users }}"
# when: no_aduser | length == 0
- name: Get member email address from AD
win_shell: Get-ADUser {{ item.name }} -Properties mail | Select-Object -ExpandProperty mail
register: email_result
with_items: "{{ email_users }}"
when: no_aduser | length == 0
# - debug:
# msg: "{{ email_result }}"
# when: no_aduser | length == 0
# this will crash out where customer account has no associated email, UoN are very consistent with account creation and adding email - needs logic for empty elements
- name: Get member emails into a list
set_fact:
email_address: "{{ email_address | default([]) + [item.stdout_lines[0]] }}"
with_items: "{{ email_result.results }}"
when: no_aduser | length == 0
- name: Build dict of object name, role, type and email
set_fact:
user_dict: "{{ user_dict | default([]) + [dict(name=item[0].name, role=item[0].role, type=item[0].type, email=item[1])] }}" # adding a new positional field from the list to the dict
loop: "{{ email_users|zip(email_address)|list }}"
when: no_aduser | length == 0
# COMMENT OUT
- debug:
msg: "{{ user_dict }}"
when: no_aduser | length == 0
handlers:
- name: Build invalid user(s) email body
set_fact:
mail_body: "{{ lookup('template', 'templates/mail-invalid.j2') }}"
delegate_to: localhost
listen:
- topic_noad
- name: Send status email
mail:
host: "{{ smtp_relay }}"
port: "{{ smtp_port }}"
charset: utf-8
from: "{{ from_email }}"
to: "{{ requester_email }}"
subject: "Result of request {{ body_service_name }}"
body: "{{ mail_body }}"
delegate_to: localhost
when: requester_email is defined and enable_requester_email
listen:
- topic_noad
- name: Condition fail on invalid AD user
debug:
msg:
- "invalid AD user(s)/groups(s)"
- ---------------------------------------
- "{{ no_aduser }}"
- ---------------------------------------
delegate_to: localhost
listen:
- topic_noad
- name: Hard exit
fail:
listen:
- topic_noad
# PLAY
# Create GPFS fileset with quota, samba export and accociated ACLs
- name: GPFS create fileset and samba export with quota
hosts: localhost
gather_facts: False
vars_files:
- vars/main.yml
- vars/requests.yml
vars:
body_service_name: "{{ hostvars['localhost']['new_service_name'] }}"
requester_email: "{{ hostvars['localhost']['requester_email'] }}"
body_requester_user: "{{ hostvars['localhost']['requester_user'] }}"
tasks:
# task here to work out quota size as a parameter
- name: Check samba share exists
include_tasks: tasks/checkfilesetsambaexport.yml
- name: Check fileset exists
include_tasks: tasks/checkfileset.yml
when: hostvars['DUMMY_HOST']['storage_fail'] is not defined
- name: Create fileset
include_tasks: tasks/createfileset.yml
when: hostvars['DUMMY_HOST']['storage_fail'] is not defined
- name: Create fileset quota
include_tasks: tasks/createfilesetquota.yml
when: hostvars['DUMMY_HOST']['storage_fail'] is not defined
- name: Create samba share
include_tasks: tasks/createsambaexport.yml
when: hostvars['DUMMY_HOST']['storage_fail'] is not defined
# if the ACL apply fails the loop terminates, users following a failed user ACL will not be processed, this shouldnt happen but there is no control for this
# would be good to check share exists already like the following remove task, would help if the script needs to update ACL
- name: Create samba share ACL
include_tasks: tasks/createsambaacl.yml
vars:
ad_object: "{{ item.name }}"
with_items: "{{ hostvars['adserver']['object_attributes'] }}"
when: hostvars['DUMMY_HOST']['storage_fail'] is not defined and item.role == 'member' # remember KEEP THIS comment under hosts: adserver
- name: Remove samba share ACL
include_tasks: tasks/removesambaacl.yml
when: hostvars['DUMMY_HOST']['storage_fail'] is not defined
- name: List samba share ACL
include_tasks: tasks/listsambaacl.yml
when: hostvars['DUMMY_HOST']['storage_fail'] is not defined
- name: Report storage failure
set_fact:
storage_fail: "{{ hostvars['DUMMY_HOST']['storage_fail'] }}"
when: hostvars['DUMMY_HOST']['storage_fail'] is defined
changed_when: true
notify: topic_fail
# - name: Get members for output
# set_fact:
# notify_members: "{{ notify_members | default([]) + [dict(name=item.name, email=item.email)]}}"
# with_items: "{{ hostvars['adserver']['user_dict'] }}"
# when: hostvars['DUMMY_HOST']['acl_entry'] is defined and (item.role == 'member' and item.type == 'Person')
# - name: Report ACL entry
# set_fact:
# acl_entry: "{{ hostvars['DUMMY_HOST']['acl_entry'] }}"
# when: hostvars['DUMMY_HOST']['acl_entry'] is defined
# changed_when: true
# notify: topic_pass
handlers:
- name: Build fail on storage provision email body
set_fact:
mail_body: "{{ lookup('template', 'templates/mail-storage-fail.j2') }}"
listen:
- topic_fail
- name: Send fail email
mail:
host: "{{ smtp_relay }}"
port: "{{ smtp_port }}"
charset: utf-8
from: "{{ from_email }}"
to: "{{ requester_email }}"
subject: "Result of request {{ body_service_name }}"
body: "{{ mail_body }}"
when: requester_email is defined and enable_requester_email
listen:
- topic_fail
- name: Condition fail on provision storage
debug:
msg:
- "provisioning storage failure"
- ---------------------------------------
- "{{ storage_fail }}"
- ---------------------------------------
listen:
- topic_fail
- name: Hard exit
fail:
listen:
- topic_fail
# PLAY
# Console and email output of results
- name: Report results
hosts: localhost
gather_facts: False
vars_files:
- vars/main.yml
vars:
body_service_name: "{{ hostvars['localhost']['new_service_name'] }}"
requester_email: "{{ hostvars['localhost']['requester_email'] }}"
body_requester_user: "{{ hostvars['localhost']['requester_user'] }}"
tasks:
# - name: Report storage failure
# set_fact:
# storage_fail: "{{ hostvars['DUMMY_HOST']['storage_fail'] }}"
# when: hostvars['DUMMY_HOST']['storage_fail'] is defined
# changed_when: true
# notify: topic_fail
- name: Get members name/email for notification requester email body
set_fact:
notify_members: "{{ notify_members | default([]) + [dict(name=item.name, email=item.email)]}}"
with_items: "{{ hostvars['adserver']['user_dict'] }}"
when: hostvars['DUMMY_HOST']['acl_entry'] is defined and (item.role == 'member' and item.type == 'Person')
- name: Get member email to list for email module
set_fact:
notify_customers: "{{ notify_customers | default([]) + [item.email]}}"
with_items: "{{ hostvars['adserver']['user_dict'] }}"
when: hostvars['DUMMY_HOST']['acl_entry'] is defined and (item.role == 'member' and item.type == 'Person')
- name: Report ACL entry
set_fact:
acl_entry: "{{ hostvars['DUMMY_HOST']['acl_entry'] }}"
when: hostvars['DUMMY_HOST']['acl_entry'] is defined
changed_when: true
notify: topic_pass
handlers:
- name: Build requester storage provision email body
set_fact:
mail_body: "{{ lookup('template', 'templates/mail-storage-requester.j2') }}"
listen:
- topic_pass
- name: Send requester storage provision email body
mail:
host: "{{ smtp_relay }}"
port: "{{ smtp_port }}"
charset: utf-8
from: "{{ from_email }}"
to: "{{ requester_email }}"
subject: "Result of request {{ body_service_name }}"
body: "{{ mail_body }}"
when: requester_email is defined and enable_requester_email
listen:
- topic_pass
- name: Build member storage provision email body
set_fact:
mail_body: "{{ lookup('template', 'templates/mail-storage-customer.j2') }}"
listen:
- topic_pass
- name: Send member storage provision email body
mail:
host: "{{ smtp_relay }}"
port: "{{ smtp_port }}"
charset: utf-8
from: "{{ from_email }}"
to: "{{ notify_customers }}"
subject: "Result of request {{ body_service_name }}"
body: "{{ mail_body }}"
when: enable_customer_email
listen:
- topic_pass
- name: Report "{{ filesetName }}" sucessful creation and applied ACL
debug:
msg:
- "storage {{ filesetName }} sucessfully created"
- ------------------------------------------------------------------------------
- "access share @UNC path //{{ clusterapiIP }}/{{ filesetName }}"
- ------------------------------------------------------------------------------
- "applied ACL for {{ filesetName }}"
- ------------------------------------------------------------------------------
- "{{ acl_entry }}"
- ------------------------------------------------------------------------------
- "users with access to the share that will recieve email notification"
- ------------------------------------------------------------------------------
- "{{ notify_members }}"
listen:
- topic_pass
# TEST + BUILD
# need input placeholder validation
# need a maximum size of share as a parameter, this will enable different tiles/tag combinations for users/groups
# may change with netapp module - use a different play to report + email - is there any point? maybe with netapp + dual functionality (play for netapp, play for gpfs) - use some more handlers and the exisitng customer emails switch
# NOT UNTIL MORE REQUIREMENTS
# owner as a group - no need today - this is a unix permission, maybe best set to service_cloudforms or even local root of GPFS
# dont think i need to worry about 'you are already member of' regarding ACL's - we are creating new storage - no requirement to delete storage yet - can put in check it will help for update/add permissions
# nice jmespath check - clean
# - name: query results of output files exist on remote host
# set_fact:
# query_certs: "{{ stat_result | json_query(jmesquery) }}"
# vars:
# jmesquery: "results[?(@.stat.exists)].item"