PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/states/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/states/statuspage.py |
""" 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 """ import logging import time # ---------------------------------------------------------------------------------------------------------------------- # 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. """ if "statuspage.create" in __salt__: return True return (False, "statuspage module could not be loaded") 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 { prop_name: prop_val for prop_name, prop_val in endpoint_props.items() 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 { prop_name: prop_val for prop_name, prop_val in endpoint_props.items() 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 { prop_name: prop_val for prop_name, prop_val in endpoint_props.items() 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 = { prop_name: prop_val for prop_name, prop_val in new_ele.items() 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 kwargs.items(): 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"] = f"{endpoint_sg} created!" 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 endpoint 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 kwargs.items(): 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"] = f"{endpoint_sg} #{id} updated!" 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"] = f"{endpoint_sg} #{id} deleted!" 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 config.items(): 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 complete_diff.items(): 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 complete_diff.items(): 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