salt/salt/modules/bamboohr.py
Erik Johnson 002aa88a97
Replace yaml usage with a helper to ensure unicode is handled properly
Without allow_unicode=True, unicode characters are processed through the
str representer and on Python 2 are dumped as a Unicode code point (i.e.
a literal \u0414). This commit makes allow_unicode=True the default in
our salt.utils.yamlloader.safe_dump() helper. It also adds a new
salt.utils.yamlloader.dump() helper which wraps yaml.dump() and also
makes allow_unicode=True the default.

To make importing and using our custom yaml loader/dumper easier, a
convenience module called salt.utils.yaml has been added, which does a
wildcard import from both salt.utils.yamldumper and
salt.utils.yamlloader.

Refs to yaml.load/dump and yaml.safe_load/safe_dump have been updated to
salt.utils.yaml, to ensure that unicode is handled properly.
2018-01-03 14:14:21 -06:00

292 lines
7.5 KiB
Python

# -*- coding: utf-8 -*-
'''
Support for BambooHR
.. versionadded:: 2015.8.0
Requires a ``subdomain`` and an ``apikey`` in ``/etc/salt/minion``:
.. code-block: yaml
bamboohr:
apikey: 012345678901234567890
subdomain: mycompany
'''
# Import python libs
from __future__ import absolute_import, print_function
import logging
# Import salt libs
import salt.utils.http
import salt.utils.yaml
from salt.ext import six
from salt._compat import ElementTree as ET
log = logging.getLogger(__name__)
def __virtual__():
'''
Only load the module if apache is installed
'''
if _apikey():
return True
return (False, 'The API key was not specified. Please specify it using the "apikey" config.')
def _apikey():
'''
Get the API key
'''
return __opts__.get('bamboohr', {}).get('apikey', None)
def list_employees(order_by='id'):
'''
Show all employees for this company.
CLI Example:
salt myminion bamboohr.list_employees
By default, the return data will be keyed by ID. However, it can be ordered
by any other field. Keep in mind that if the field that is chosen contains
duplicate values (i.e., location is used, for a company which only has one
location), then each duplicate value will be overwritten by the previous.
Therefore, it is advisable to only sort by fields that are guaranteed to be
unique.
CLI Examples:
salt myminion bamboohr.list_employees order_by=id
salt myminion bamboohr.list_employees order_by=displayName
salt myminion bamboohr.list_employees order_by=workEmail
'''
ret = {}
status, result = _query(action='employees', command='directory')
root = ET.fromstring(result)
directory = root.getchildren()
for cat in directory:
if cat.tag != 'employees':
continue
for item in cat:
emp_id = item.items()[0][1]
emp_ret = {'id': emp_id}
for details in item.getchildren():
emp_ret[details.items()[0][1]] = details.text
ret[emp_ret[order_by]] = emp_ret
return ret
def show_employee(emp_id, fields=None):
'''
Show all employees for this company.
CLI Example:
salt myminion bamboohr.show_employee 1138
By default, the fields normally returned from bamboohr.list_employees are
returned. These fields are:
- canUploadPhoto
- department
- displayName
- firstName
- id
- jobTitle
- lastName
- location
- mobilePhone
- nickname
- photoUploaded
- photoUrl
- workEmail
- workPhone
- workPhoneExtension
If needed, a different set of fields may be specified, separated by commas:
CLI Example:
salt myminion bamboohr.show_employee 1138 displayName,dateOfBirth
A list of available fields can be found at
http://www.bamboohr.com/api/documentation/employees.php
'''
ret = {}
if fields is None:
fields = ','.join((
'canUploadPhoto',
'department',
'displayName',
'firstName',
'id',
'jobTitle',
'lastName',
'location',
'mobilePhone',
'nickname',
'photoUploaded',
'photoUrl',
'workEmail',
'workPhone',
'workPhoneExtension',
))
status, result = _query(
action='employees',
command=emp_id,
args={'fields': fields}
)
root = ET.fromstring(result)
items = root.getchildren()
ret = {'id': emp_id}
for item in items:
ret[item.items()[0][1]] = item.text
return ret
def update_employee(emp_id, key=None, value=None, items=None):
'''
Update one or more items for this employee. Specifying an empty value will
clear it for that employee.
CLI Examples:
salt myminion bamboohr.update_employee 1138 nickname Curly
salt myminion bamboohr.update_employee 1138 nickname ''
salt myminion bamboohr.update_employee 1138 items='{"nickname": "Curly"}
salt myminion bamboohr.update_employee 1138 items='{"nickname": ""}
'''
if items is None:
if key is None or value is None:
return {'Error': 'At least one key/value pair is required'}
items = {key: value}
elif isinstance(items, six.string_types):
items = salt.utils.yaml.safe_load(items)
xml_items = ''
for pair in items:
xml_items += '<field id="{0}">{1}</field>'.format(pair, items[pair])
xml_items = '<employee>{0}</employee>'.format(xml_items)
status, result = _query(
action='employees',
command=emp_id,
data=xml_items,
method='POST',
)
return show_employee(emp_id, ','.join(items.keys()))
def list_users(order_by='id'):
'''
Show all users for this company.
CLI Example:
salt myminion bamboohr.list_users
By default, the return data will be keyed by ID. However, it can be ordered
by any other field. Keep in mind that if the field that is chosen contains
duplicate values (i.e., location is used, for a company which only has one
location), then each duplicate value will be overwritten by the previous.
Therefore, it is advisable to only sort by fields that are guaranteed to be
unique.
CLI Examples:
salt myminion bamboohr.list_users order_by=id
salt myminion bamboohr.list_users order_by=email
'''
ret = {}
status, result = _query(action='meta', command='users')
root = ET.fromstring(result)
users = root.getchildren()
for user in users:
user_id = None
user_ret = {}
for item in user.items():
user_ret[item[0]] = item[1]
if item[0] == 'id':
user_id = item[1]
for item in user.getchildren():
user_ret[item.tag] = item.text
ret[user_ret[order_by]] = user_ret
return ret
def list_meta_fields():
'''
Show all meta data fields for this company.
CLI Example:
salt myminion bamboohr.list_meta_fields
'''
ret = {}
status, result = _query(action='meta', command='fields')
root = ET.fromstring(result)
fields = root.getchildren()
for field in fields:
field_id = None
field_ret = {'name': field.text}
for item in field.items():
field_ret[item[0]] = item[1]
if item[0] == 'id':
field_id = item[1]
ret[field_id] = field_ret
return ret
def _query(action=None,
command=None,
args=None,
method='GET',
data=None):
'''
Make a web call to BambooHR
The password can be any random text, so we chose Salty text.
'''
subdomain = __opts__.get('bamboohr', {}).get('subdomain', None)
path = 'https://api.bamboohr.com/api/gateway.php/{0}/v1/'.format(
subdomain
)
if action:
path += action
if command:
path += '/{0}'.format(command)
log.debug('BambooHR URL: {0}'.format(path))
if not isinstance(args, dict):
args = {}
return_content = None
result = salt.utils.http.query(
path,
method,
username=_apikey(),
password='saltypork',
params=args,
data=data,
decode=False,
text=True,
status=True,
opts=__opts__,
)
log.debug(
'BambooHR Response Status Code: {0}'.format(
result['status']
)
)
return [result['status'], result['text']]