%PDF- %PDF-
Direktori : /proc/thread-self/root/lib/python2.7/site-packages/salt/states/ |
Current File : //proc/thread-self/root/lib/python2.7/site-packages/salt/states/statuspage.py |
# -*- coding: utf-8 -*- ''' StatusPage ========== Manage the StatusPage_ configuration. .. _StatusPage: https://www.statuspage.io/ In the minion configuration file, the following block is required: .. code-block:: yaml statuspage: api_key: <API_KEY> page_id: <PAGE_ID> .. versionadded:: 2017.7.0 ''' from __future__ import absolute_import, unicode_literals, print_function # import python std lib import time import logging # import salt from salt.ext import six # ---------------------------------------------------------------------------------------------------------------------- # module properties # ---------------------------------------------------------------------------------------------------------------------- __virtualname__ = 'statuspage' log = logging.getLogger(__file__) _DO_NOT_COMPARE_FIELDS = [ 'created_at', 'updated_at' ] _MATCH_KEYS = [ 'id', 'name' ] _PACE = 1 # 1 request per second # ---------------------------------------------------------------------------------------------------------------------- # property functions # ---------------------------------------------------------------------------------------------------------------------- def __virtual__(): ''' Return the execution module virtualname. ''' return __virtualname__ def _default_ret(name): ''' Default dictionary returned. ''' return { 'name': name, 'result': False, 'comment': '', 'changes': {} } def _compute_diff_ret(): ''' Default dictionary retuned by the _compute_diff helper. ''' return { 'add': [], 'update': [], 'remove': [] } def _clear_dict(endpoint_props): ''' Eliminates None entries from the features of the endpoint dict. ''' return dict( (prop_name, prop_val) for prop_name, prop_val in six.iteritems(endpoint_props) if prop_val is not None ) def _ignore_keys(endpoint_props): ''' Ignores some keys that might be different without any important info. These keys are defined under _DO_NOT_COMPARE_FIELDS. ''' return dict( (prop_name, prop_val) for prop_name, prop_val in six.iteritems(endpoint_props) if prop_name not in _DO_NOT_COMPARE_FIELDS ) def _unique(list_of_dicts): ''' Returns an unique list of dictionaries given a list that may contain duplicates. ''' unique_list = [] for ele in list_of_dicts: if ele not in unique_list: unique_list.append(ele) return unique_list def _clear_ignore(endpoint_props): ''' Both _clear_dict and _ignore_keys in a single iteration. ''' return dict( (prop_name, prop_val) for prop_name, prop_val in six.iteritems(endpoint_props) if prop_name not in _DO_NOT_COMPARE_FIELDS and prop_val is not None ) def _clear_ignore_list(lst): ''' Apply _clear_ignore to a list. ''' return _unique([ _clear_ignore(ele) for ele in lst ]) def _find_match(ele, lst): ''' Find a matching element in a list. ''' for _ele in lst: for match_key in _MATCH_KEYS: if _ele.get(match_key) == ele.get(match_key): return ele def _update_on_fields(prev_ele, new_ele): ''' Return a dict with fields that differ between two dicts. ''' fields_update = dict( (prop_name, prop_val) for prop_name, prop_val in six.iteritems(new_ele) if new_ele.get(prop_name) != prev_ele.get(prop_name) or prop_name in _MATCH_KEYS ) if len(set(fields_update.keys()) | set(_MATCH_KEYS)) > len(set(_MATCH_KEYS)): if 'id' not in fields_update: # in case of update, the ID is necessary # if not specified in the pillar, # will try to get it from the prev_ele fields_update['id'] = prev_ele['id'] return fields_update def _compute_diff(expected_endpoints, configured_endpoints): ''' Compares configured endpoints with the expected configuration and returns the differences. ''' new_endpoints = [] update_endpoints = [] remove_endpoints = [] ret = _compute_diff_ret() # noth configured => configure with expected endpoints if not configured_endpoints: ret.update({ 'add': expected_endpoints }) return ret # noting expected => remove everything if not expected_endpoints: ret.update({ 'remove': configured_endpoints }) return ret expected_endpoints_clear = _clear_ignore_list(expected_endpoints) configured_endpoints_clear = _clear_ignore_list(configured_endpoints) for expected_endpoint_clear in expected_endpoints_clear: if expected_endpoint_clear not in configured_endpoints_clear: # none equal => add or update matching_ele = _find_match(expected_endpoint_clear, configured_endpoints_clear) if not matching_ele: # new element => add new_endpoints.append(expected_endpoint_clear) else: # element matched, but some fields are different update_fields = _update_on_fields(matching_ele, expected_endpoint_clear) if update_fields: update_endpoints.append(update_fields) for configured_endpoint_clear in configured_endpoints_clear: if configured_endpoint_clear not in expected_endpoints_clear: matching_ele = _find_match(configured_endpoint_clear, expected_endpoints_clear) if not matching_ele: # no match found => remove remove_endpoints.append(configured_endpoint_clear) return { 'add': new_endpoints, 'update': update_endpoints, 'remove': remove_endpoints } # ---------------------------------------------------------------------------------------------------------------------- # callable functions # ---------------------------------------------------------------------------------------------------------------------- def create(name, endpoint='incidents', api_url=None, page_id=None, api_key=None, api_version=None, **kwargs): ''' Insert a new entry under a specific endpoint. endpoint: incidents Insert under this specific endpoint. page_id Page ID. Can also be specified in the config file. api_key API key. Can also be specified in the config file. api_version: 1 API version. Can also be specified in the config file. api_url Custom API URL in case the user has a StatusPage service running in a custom environment. kwargs Other params. SLS Example: .. code-block:: yaml create-my-component: statuspage.create: - endpoint: components - name: my component - group_id: 993vgplshj12 ''' ret = _default_ret(name) endpoint_sg = endpoint[:-1] # singular if __opts__['test']: ret['comment'] = 'The following {endpoint} would be created:'.format(endpoint=endpoint_sg) ret['result'] = None ret['changes'][endpoint] = {} for karg, warg in six.iteritems(kwargs): if warg is None or karg.startswith('__'): continue ret['changes'][endpoint][karg] = warg return ret sp_create = __salt__['statuspage.create'](endpoint=endpoint, api_url=api_url, page_id=page_id, api_key=api_key, api_version=api_version, **kwargs) if not sp_create.get('result'): ret['comment'] = 'Unable to create {endpoint}: {msg}'.format(endpoint=endpoint_sg, msg=sp_create.get('comment')) else: ret['comment'] = '{endpoint} created!'.format(endpoint=endpoint_sg) ret['result'] = True ret['changes'] = sp_create.get('out') def update(name, endpoint='incidents', id=None, api_url=None, page_id=None, api_key=None, api_version=None, **kwargs): ''' Update attribute(s) of a specific endpoint. id The unique ID of the enpoint entry. endpoint: incidents Endpoint name. page_id Page ID. Can also be specified in the config file. api_key API key. Can also be specified in the config file. api_version: 1 API version. Can also be specified in the config file. api_url Custom API URL in case the user has a StatusPage service running in a custom environment. SLS Example: .. code-block:: yaml update-my-incident: statuspage.update: - id: dz959yz2nd4l - status: resolved ''' ret = _default_ret(name) endpoint_sg = endpoint[:-1] # singular if not id: log.error('Invalid %s ID', endpoint_sg) ret['comment'] = 'Please specify a valid {endpoint} ID'.format(endpoint=endpoint_sg) return ret if __opts__['test']: ret['comment'] = '{endpoint} #{id} would be updated:'.format(endpoint=endpoint_sg, id=id) ret['result'] = None ret['changes'][endpoint] = {} for karg, warg in six.iteritems(kwargs): if warg is None or karg.startswith('__'): continue ret['changes'][endpoint][karg] = warg return ret sp_update = __salt__['statuspage.update'](endpoint=endpoint, id=id, api_url=api_url, page_id=page_id, api_key=api_key, api_version=api_version, **kwargs) if not sp_update.get('result'): ret['comment'] = 'Unable to update {endpoint} #{id}: {msg}'.format(endpoint=endpoint_sg, id=id, msg=sp_update.get('comment')) else: ret['comment'] = '{endpoint} #{id} updated!'.format(endpoint=endpoint_sg, id=id) ret['result'] = True ret['changes'] = sp_update.get('out') def delete(name, endpoint='incidents', id=None, api_url=None, page_id=None, api_key=None, api_version=None): ''' Remove an entry from an endpoint. endpoint: incidents Request a specific endpoint. page_id Page ID. Can also be specified in the config file. api_key API key. Can also be specified in the config file. api_version: 1 API version. Can also be specified in the config file. api_url Custom API URL in case the user has a StatusPage service running in a custom environment. SLS Example: .. code-block:: yaml delete-my-component: statuspage.delete: - endpoint: components - id: ftgks51sfs2d ''' ret = _default_ret(name) endpoint_sg = endpoint[:-1] # singular if not id: log.error('Invalid %s ID', endpoint_sg) ret['comment'] = 'Please specify a valid {endpoint} ID'.format(endpoint=endpoint_sg) return ret if __opts__['test']: ret['comment'] = '{endpoint} #{id} would be removed!'.format(endpoint=endpoint_sg, id=id) ret['result'] = None sp_delete = __salt__['statuspage.delete'](endpoint=endpoint, id=id, api_url=api_url, page_id=page_id, api_key=api_key, api_version=api_version) if not sp_delete.get('result'): ret['comment'] = 'Unable to delete {endpoint} #{id}: {msg}'.format(endpoint=endpoint_sg, id=id, msg=sp_delete.get('comment')) else: ret['comment'] = '{endpoint} #{id} deleted!'.format(endpoint=endpoint_sg, id=id) ret['result'] = True def managed(name, config, api_url=None, page_id=None, api_key=None, api_version=None, pace=_PACE, allow_empty=False): ''' Manage the StatusPage configuration. config Dictionary with the expected configuration of the StatusPage. The main level keys of this dictionary represent the endpoint name. If a certain endpoint does not exist in this structure, it will be ignored / not configured. page_id Page ID. Can also be specified in the config file. api_key API key. Can also be specified in the config file. api_version: 1 API version. Can also be specified in the config file. api_url Custom API URL in case the user has a StatusPage service running in a custom environment. pace: 1 Max requests per second allowed by the API. allow_empty: False Allow empty config. SLS example: .. code-block:: yaml my-statuspage-config: statuspage.managed: - config: components: - name: component1 group_id: uy4g37rf - name: component2 group_id: 3n4uyu4gf incidents: - name: incident1 status: resolved impact: major backfilled: false - name: incident2 status: investigating impact: minor ''' complete_diff = {} ret = _default_ret(name) if not config and not allow_empty: ret.update({ 'result': False, 'comment': 'Cannot remove everything. To allow this, please set the option `allow_empty` as True.' }) return ret is_empty = True for endpoint_name, endpoint_expected_config in six.iteritems(config): if endpoint_expected_config: is_empty = False endpoint_existing_config_ret = __salt__['statuspage.retrieve'](endpoint=endpoint_name, api_url=api_url, page_id=page_id, api_key=api_key, api_version=api_version) if not endpoint_existing_config_ret.get('result'): ret.update({ 'comment': endpoint_existing_config_ret.get('comment') }) return ret # stop at first error endpoint_existing_config = endpoint_existing_config_ret.get('out') complete_diff[endpoint_name] = _compute_diff(endpoint_expected_config, endpoint_existing_config) if is_empty and not allow_empty: ret.update({ 'result': False, 'comment': 'Cannot remove everything. To allow this, please set the option `allow_empty` as True.' }) return ret any_changes = False for endpoint_name, endpoint_diff in six.iteritems(complete_diff): if endpoint_diff.get('add') or endpoint_diff.get('update') or endpoint_diff.get('remove'): any_changes = True if not any_changes: ret.update({ 'result': True, 'comment': 'No changes required.', 'changes': {} }) return ret ret.update({ 'changes': complete_diff }) if __opts__.get('test'): ret.update({ 'comment': 'Testing mode. Would apply the following changes:', 'result': None }) return ret for endpoint_name, endpoint_diff in six.iteritems(complete_diff): endpoint_sg = endpoint_name[:-1] # singular for new_endpoint in endpoint_diff.get('add'): log.debug('Defining new %s %s', endpoint_sg, new_endpoint ) adding = __salt__['statuspage.create'](endpoint=endpoint_name, api_url=api_url, page_id=page_id, api_key=api_key, api_version=api_version, **new_endpoint) if not adding.get('result'): ret.update({ 'comment': adding.get('comment') }) return ret if pace: time.sleep(1/pace) for update_endpoint in endpoint_diff.get('update'): if 'id' not in update_endpoint: continue endpoint_id = update_endpoint.pop('id') log.debug('Updating %s #%s: %s', endpoint_sg, endpoint_id, update_endpoint ) updating = __salt__['statuspage.update'](endpoint=endpoint_name, id=endpoint_id, api_url=api_url, page_id=page_id, api_key=api_key, api_version=api_version, **update_endpoint) if not updating.get('result'): ret.update({ 'comment': updating.get('comment') }) return ret if pace: time.sleep(1/pace) for remove_endpoint in endpoint_diff.get('remove'): if 'id' not in remove_endpoint: continue endpoint_id = remove_endpoint.pop('id') log.debug('Removing %s #%s', endpoint_sg, endpoint_id ) removing = __salt__['statuspage.delete'](endpoint=endpoint_name, id=endpoint_id, api_url=api_url, page_id=page_id, api_key=api_key, api_version=api_version) if not removing.get('result'): ret.update({ 'comment': removing.get('comment') }) return ret if pace: time.sleep(1/pace) ret.update({ 'result': True, 'comment': 'StatusPage updated.' }) return ret