def set_acknowledgement_on_host_service(params): """Acknowledge for services on a host""" host_name = params['host_name'] service_description = unquote(params['service_description']) body = params['body'] service = Query([Services.description, Services.state], And(Services.description.equals(service_description), Services.host_name.equals(host_name))).first(sites.live()) if service is None: return problem( status=404, title=f'Service {service_description!r} on host {host_name!r} does not exist.', detail='It is not currently monitored.', ) if service.state == 0: return problem(status=400, title=f"Service {service_description!r} does not have a problem.", detail="The state is OK.") acknowledge_service_problem( sites.live(), host_name, service_description, sticky=body.get('sticky', False), notify=body.get('notify', False), persistent=body.get('persistent', False), user=_user_id(), comment=body.get('comment', 'Acknowledged'), ) return http.Response(status=204)
def move(params): """Move a host to another folder""" user.need_permission("wato.edit") user.need_permission("wato.move_hosts") host_name = params["host_name"] host: CREHost = Host.load_host(host_name) _require_host_etag(host) current_folder = host.folder() target_folder: CREFolder = params["body"]["target_folder"] if target_folder is current_folder: return problem( status=400, title="Invalid move action", detail="The host is already part of the specified target folder", ) try: current_folder.move_hosts([host_name], target_folder) except MKUserError as exc: return problem( status=400, title="Problem moving host", detail=exc.message, ) return _serve_host(host, effective_attributes=False)
def set_acknowledgement_on_host(params): """Acknowledge for a specific host""" host_name = params['host_name'] host = Query([Hosts.name, Hosts.state], Hosts.name.equals(host_name)).first(sites.live()) if host is None: return problem( status=404, title=f'Host {host_name} does not exist.', detail='It is not currently monitored.', ) if host.state == 0: return problem(status=400, title=f"Host {host_name} does not have a problem.", detail="The state is UP.") acknowledge_host_problem( sites.live(), host_name, sticky=bool(params.get('sticky')), notify=bool(params.get('notify')), persistent=bool(params.get('persistent')), user=_user_id(), comment=params.get('comment', 'Acknowledged'), ) return http.Response(status=204)
def _validating_wrapper(param): # TODO: Better error messages, pointing to the location where variables are missing try: if path_schema: param.update(path_schema().load(param)) if query_schema: param.update(query_schema().load(request.args)) if header_schema: param.update(header_schema().load(request.headers)) if request_schema: body = request_schema().load(request.json or {}) param['body'] = body except ValidationError as exc: def _format_fields(_messages: Union[List, Dict]) -> str: if isinstance(_messages, list): return ', '.join(_messages) if isinstance(_messages, dict): return ', '.join(_messages.keys()) return '' if isinstance(exc.messages, dict): messages = exc.messages else: messages = {'exc': exc.messages} return problem( status=400, title="Bad request.", detail= f"These fields have problems: {_format_fields(exc.messages)}", ext=messages, ) # make pylint happy assert callable(self.func) # FIXME # We need to get the "original data" somewhere and are currently "piggy-backing" # it on the response instance. This is somewhat problematic because it's not # expected behaviour and not a valid interface of Response. Needs refactoring. response = self.func(param) if hasattr(response, 'original_data') and response_schema: try: response_schema().load(response.original_data) return response except ValidationError as exc: # Hope we never get here in production. return problem( status=500, title="Server was about to send an invalid response.", detail="This is an error of the implementation.", ext={ 'errors': exc.messages, 'orig': response.original_data }, ) return response
def update_host_tag_group(params): """Update a host tag group""" # TODO: ident verification mechanism with ParamDict replacement body = params['body'] ident = params['name'] if is_builtin(ident): return problem( status=405, title="Built-in cannot be modified", detail=f"The built-in host tag group {ident} cannot be modified", ) updated_details = {x: body[x] for x in body if x != "repair"} tag_group = _retrieve_group(ident) group_details = tag_group.get_dict_format() group_details.update(updated_details) try: edit_tag_group(ident, TagGroup(group_details), allow_repair=body['repair']) except RepairError: return problem( 401, f'Updating this host tag group "{ident}" requires additional authorization', 'The host tag group you intend to edit is used by other instances. You must authorize Checkmk ' 'to update the relevant instances using the repair parameter') updated_tag_group = _retrieve_group(ident) return _serve_host_tag_group(updated_tag_group.get_dict_format())
def update_host_tag_group(params): """Update a host tag group""" # TODO: ident verification mechanism with ParamDict replacement body = params["body"] ident = params["name"] if is_builtin(ident): return problem( status=405, title="Built-in cannot be modified", detail=f"The built-in host tag group {ident} cannot be modified", ) updated_details = {x: body[x] for x in body if x != "repair"} tag_group = _retrieve_group(ident) group_details = tag_group.get_dict_format() # This is an incremental update of the TaggroupSpec group_details.update(updated_details) # type: ignore[typeddict-item] try: edit_tag_group(ident, TagGroup.from_config(group_details), allow_repair=body["repair"]) except RepairError: return problem( 401, f'Updating this host tag group "{ident}" requires additional authorization', "The host tag group you intend to edit is used by other instances. You must authorize Checkmk " "to update the relevant instances using the repair parameter", ) updated_tag_group = _retrieve_group(ident) return _serve_host_tag_group(updated_tag_group.get_dict_format())
def delete_host_tag_group(params): """Delete a host tag group""" ident = params['name'] if is_builtin(ident): return problem( status=405, title="Built-in cannot be delete", detail=f"The built-in host tag group {ident} cannot be deleted", ) affected = change_host_tags_in_folders(OperationRemoveTagGroup(ident), TagCleanupMode.CHECK, watolib.Folder.root_folder()) if any(affected): if not params["repair"]: return problem( 401, f'Deleting this host tag group "{ident}" requires additional authorization', 'The host tag group you intend to delete is used by other instances. You must authorize Checkmk ' 'to update the relevant instances using the repair parameter') watolib.host_attributes.undeclare_host_tag_attribute(ident) _ = change_host_tags_in_folders(OperationRemoveTagGroup(ident), TagCleanupMode("delete"), watolib.Folder.root_folder()) tag_config = load_tag_config() tag_config.remove_tag_group(ident) update_tag_config(tag_config) return Response(status=204)
def _set_acknowledgement_on_host( connection, host_name: str, sticky: bool, notify: bool, persistent: bool, comment: str, ): """Acknowledge for a specific host""" host = Query([Hosts.name, Hosts.state], Hosts.name.equals(host_name)).first(connection) if host is None: return problem( status=404, title=f'Host {host_name} does not exist.', detail='It is not currently monitored.', ) if host.state == 0: return problem(status=400, title=f"Host {host_name} does not have a problem.", detail="The state is UP.") acknowledge_host_problem( sites.live(), host_name, sticky=sticky, notify=notify, persistent=persistent, user=_user_id(), comment=comment, ) return http.Response(status=204)
def create_host_related_downtime(params): """Create a host related scheduled downtime""" body = params["body"] live = sites.live() downtime_type: DowntimeType = body["downtime_type"] if downtime_type == "host": downtime_commands.schedule_host_downtime( live, host_entry=body["host_name"], start_time=body["start_time"], end_time=body["end_time"], recur=body["recur"], duration=body["duration"], user_id=user.ident, comment=body.get("comment", f"Downtime for host {body['host_name']!r}"), ) elif downtime_type == "hostgroup": downtime_commands.schedule_hostgroup_host_downtime( live, hostgroup_name=body["hostgroup_name"], start_time=body["start_time"], end_time=body["end_time"], recur=body["recur"], duration=body["duration"], user_id=user.ident, comment=body.get( "comment", f"Downtime for hostgroup {body['hostgroup_name']!r}"), ) elif downtime_type == "host_by_query": try: downtime_commands.schedule_hosts_downtimes_with_query( live, body["query"], start_time=body["start_time"], end_time=body["end_time"], recur=body["recur"], duration=body["duration"], user_id=user.ident, comment=body.get("comment", ""), ) except QueryException: return problem( status=422, title="Query did not match any host", detail= "The provided query returned an empty list so no downtime was set", ) else: return problem( status=400, title="Unhandled downtime-type.", detail=f"The downtime-type {downtime_type!r} is not supported.", ) return Response(status=204)
def move(params): """Move a host to another folder""" host_name = params['host_name'] host: watolib.CREHost = watolib.Host.host(host_name) if host is None: return _missing_host_problem(host_name) current_folder = host.folder() target_folder: watolib.CREFolder = params['body']['target_folder'] if target_folder is current_folder: return problem( status=400, title="Invalid move action", detail="The host is already part of the specified target folder", ) try: current_folder.move_hosts([host_name], target_folder) except MKUserError as exc: return problem( status=400, title="Problem moving host", detail=exc.message, ) return _serve_host(host, False)
def rename_host(params): """Rename a host""" if activate_changes.get_pending_changes_info(): return problem( status=409, title="Pending changes are present", detail= "Please activate all pending changes before executing a host rename process", ) host_name = params['host_name'] host: watolib.CREHost = watolib.Host.host(host_name) if host is None: return _missing_host_problem(host_name) new_name = params['body']["new_name"] _, auth_problems = perform_rename_hosts([(host.folder(), host_name, new_name)]) if auth_problems: return problem( status=422, title="Rename process failed", detail= f"It was not possible to rename the host {host_name} to {new_name}", ) return _serve_host(host, False)
def create_host_related_downtime(params): """Create a host related scheduled downtime""" body = params['body'] live = sites.live() downtime_type: DowntimeType = body['downtime_type'] if downtime_type == 'host': downtime_commands.schedule_host_downtime( live, host_name=body['host_name'], start_time=body['start_time'], end_time=body['end_time'], recur=body['recur'], duration=body['duration'], user_id=config.user.ident, comment=body.get('comment', f"Downtime for host {body['host_name']!r}"), ) elif downtime_type == 'hostgroup': downtime_commands.schedule_hostgroup_host_downtime( live, hostgroup_name=body['hostgroup_name'], start_time=body['start_time'], end_time=body['end_time'], recur=body['recur'], duration=body['duration'], user_id=config.user.ident, comment=body.get( 'comment', f"Downtime for hostgroup {body['hostgroup_name']!r}"), ) elif downtime_type == 'host_by_query': try: downtime_commands.schedule_hosts_downtimes_with_query( live, body['query'], start_time=body['start_time'], end_time=body['end_time'], recur=body['recur'], duration=body['duration'], user_id=config.user.ident, comment=body.get('comment', ''), ) except QueryException: return problem( status=422, title="Query did not match any host", detail= "The provided query returned an empty list so no downtime was set", ) else: return problem( status=400, title="Unhandled downtime-type.", detail=f"The downtime-type {downtime_type!r} is not supported.") return Response(status=204)
def create_rule(param): """Create rule""" body = param["body"] folder = body["folder"] value = body["value_raw"] rulesets = watolib.FolderRulesets(folder) rulesets.load() try: ruleset = rulesets.get(body["ruleset"]) except KeyError: return problem( status=400, detail=f"Ruleset {body['ruleset']!r} could not be found.", ) try: ruleset.valuespec().validate_value(value, "") except exceptions.MKUserError as exc: if exc.varname is None: title = "A field has a problem" else: field_name = exc.varname.replace("_p_", "") title = f"Problem in (sub-)field {field_name!r}" return problem( status=400, detail=strip_tags(exc.message), title=title, ) rule = watolib.Rule( gen_id(), folder, ruleset, RuleConditions( host_folder=folder, host_tags=body["conditions"].get("host_tag"), host_labels=body["conditions"].get("host_label"), host_name=body["conditions"].get("host_name"), service_description=body["conditions"].get("service_description"), service_labels=body["conditions"].get("service_label"), ), RuleOptions.from_config(body["properties"]), value, ) index = ruleset.append_rule(folder, rule) rulesets.save() # TODO Duplicated code is in pages/rulesets.py:2670- # TODO Move to watolib add_change( "new-rule", _l('Created new rule #%d in ruleset "%s" in folder "%s"') % (index, ruleset.title(), folder.alias_path()), sites=folder.all_site_ids(), diff_text=make_diff_text({}, rule.to_log()), object_ref=rule.object_ref(), ) return serve_json(_serialize_rule(folder, index, rule))
def _wsgi_app(self, environ: WSGIEnvironment, start_response): urls = self.url_map.bind_to_environ(environ) try: wsgi_app, path_args = urls.match() # Remove this again (see Submount above), so the validators don't go crazy. del path_args['_path'] # This is an implicit dependency, as we only know the args at runtime, but the # function at setup-time. environ[ARGS_KEY] = path_args return wsgi_app(environ, start_response) except HTTPException as exc: # We don't want to log explicit HTTPExceptions as these are intentional. assert isinstance(exc.code, int) return problem( status=exc.code, title=http.client.responses[exc.code], detail=str(exc), )(environ, start_response) except MKException as exc: if self.debug: raise return problem( status=EXCEPTION_STATUS.get(type(exc), 500), title="An exception occurred.", detail=str(exc), )(environ, start_response) except Exception as exc: crash = APICrashReport.from_exception() crash_reporting.CrashReportStore().save(crash) logger.exception("Unhandled exception (Crash-ID: %s)", crash.ident_to_text()) if self.debug: raise crash_url = f"/{config.omd_site()}/check_mk/crash.py?" + urllib.parse.urlencode( [ ("crash_id", crash.ident_to_text()), ("site", config.omd_site()), ], ) return problem( status=500, title=str(exc), detail= "An internal error occured while processing your request.", ext={ 'crash_report': { 'href': crash_url, 'method': 'get', 'rel': 'cmk/crash-report', 'type': 'text/html', }, 'crash_id': crash.ident_to_text(), })(environ, start_response)
def bulk_set_acknowledgement_on_host_service(params): """Bulk Acknowledge specific services on specific host""" live = sites.live() body = params['body'] host_name = body['host_name'] entries = body.get('entries', []) query = Query([Services.description, Services.state], And( Services.host_name.equals(host_name), Or(*[ Services.description.equals(service_description) for service_description in entries ]))) services = query.to_dict(live) not_found = [] for service_description in entries: if service_description not in services: not_found.append(service_description) if not_found: return problem( status=400, title= f"Services {', '.join(not_found)} not found on host {host_name}", detail='Currently not monitored') up_services = [] for service_description in entries: if services[service_description] == 0: up_services.append(service_description) if up_services: return problem( status=400, title=f"Services {', '.join(up_services)} do not have a problem", detail="The states of these services are OK") for service_description in entries: acknowledge_service_problem( sites.live(), host_name, service_description, sticky=body.get('sticky', False), notify=body.get('notify', False), persistent=body.get('persistent', False), user=str(config.user.id), comment=body.get('comment', 'Acknowledged'), ) return http.Response(status=204)
def _set_acknowledgement_on_queried_services( connection, services: List[Tuple[str, str]], sticky: bool, notify: bool, persistent: bool, comment: str, ): if not len(services): return problem( status=400, title='No services with problems found.', detail='All queried services are OK.', ) for host_name, service_description in services: acknowledge_service_problem( connection, host_name, service_description, sticky=sticky, notify=notify, persistent=persistent, user=_user_id(), comment=comment, ) return http.Response(status=204)
def _set_acknowlegement_on_queried_hosts( connection, query: str, sticky: bool, notify: bool, persistent: bool, comment: str, ): q = Query([Hosts.name, Hosts.state]).filter(tree_to_expr(query, Hosts.__tablename__)) hosts = list(q.iterate(connection)) if not hosts: return problem(status=404, title="The provided query returned no monitored hosts") for host in hosts: if host.state == 0: continue acknowledge_host_problem( connection, host.name, sticky=sticky, notify=notify, persistent=persistent, user=_user_id(), comment=comment, ) return http.Response(status=204)
def update_host(params): """Update a host""" host_name = params["host_name"] body = params["body"] new_attributes = body["attributes"] update_attributes = body["update_attributes"] remove_attributes = body["remove_attributes"] check_hostname(host_name, should_exist=True) host: watolib.CREHost = watolib.Host.load_host(host_name) _require_host_etag(host) if new_attributes: host.edit(new_attributes, None) if update_attributes: host.update_attributes(update_attributes) faulty_attributes = [] for attribute in remove_attributes: if not host.has_explicit_attribute(attribute): faulty_attributes.append(attribute) if remove_attributes: host.clean_attributes( remove_attributes) # silently ignores missing attributes if faulty_attributes: return problem( status=400, title="Some attributes were not removed", detail= f"The following attributes were not removed since they didn't exist: {', '.join(faulty_attributes)}", ) return _serve_host(host, effective_attributes=False)
def show_service(params): """Show the monitored service of a host""" service_description = params["service_description"] host_name = params["host_name"] live = sites.live() q = Query( [ Services.description, Services.host_name, Services.state_type, Services.state, Services.last_check, ], filter_expr=And(Services.host_name.op("=", params["host_name"]), Services.description.op("=", service_description)), ) try: service = q.fetchone(live) except ValueError: return problem( status=404, title="The requested service was not found", detail= f"The service description {service_description} did not match any service", ) return constructors.serve_json( constructors.domain_object( domain_type='service', identifier=f"{host_name}-{service_description}", title=f"Service {service_description}", extensions=service, links=[], editable=False, deletable=False))
def delete_downtime(params): """Delete a scheduled downtime""" body = params['body'] live = sites.live() delete_type = body['delete_type'] if delete_type == "query": downtime_commands.delete_downtime_with_query(live, body['query']) elif delete_type == "by_id": downtime_commands.delete_downtime(live, body['downtime_id']) elif delete_type == "params": hostname = body['host_name'] if "service_descriptions" not in body: host_expr = And(Downtimes.host_name.op("=", hostname), Downtimes.is_service.op('=', 0)) downtime_commands.delete_downtime_with_query(live, host_expr) else: services_expr = And( Downtimes.host_name.op('=', hostname), Or(*[ Downtimes.service_description == svc_desc for svc_desc in body['service_descriptions'] ])) downtime_commands.delete_downtime_with_query(live, services_expr) else: return problem( status=400, title="Unhandled delete_type.", detail=f"The downtime-type {delete_type!r} is not supported.") return Response(status=204)
def list_rules(param): """List rules""" all_sets = watolib.AllRulesets() all_sets.load() ruleset_name = param["ruleset_name"] try: ruleset = all_sets.get(ruleset_name.replace("-", ":")) except KeyError: return problem( status=400, title="Unknown ruleset.", detail=f"The ruleset of name {ruleset_name!r} is not known.", ) result = [] for folder, index, rule in ruleset.get_rules(): result.append(_serialize_rule(folder, index, rule)) return serve_json( constructors.collection_object( domain_type="rule", value=result, extensions={ "found_rules": len(result), }, ) )
def show_downtime(params): """Show downtime""" live = sites.live() downtime_id = params["downtime_id"] q = Query( columns=[ Downtimes.id, Downtimes.host_name, Downtimes.service_description, Downtimes.is_service, Downtimes.author, Downtimes.start_time, Downtimes.end_time, Downtimes.recurring, Downtimes.comment, ], filter_expr=Downtimes.id.op("=", downtime_id), ) try: downtime = q.fetchone(live) except ValueError: return problem( status=404, title="The requested downtime was not found", detail=f"The downtime id {downtime_id} did not match any downtime", ) return _serve_downtime(downtime)
def bulk_create_hosts(params): """Bulk create hosts""" body = params['body'] entries = body['entries'] failed_hosts = [] folder: watolib.CREFolder for folder, grouped_hosts in itertools.groupby( body['entries'], operator.itemgetter('folder')): validated_entries = [] folder.prepare_create_hosts() for host in grouped_hosts: host_name = host["host_name"] attributes = host["attributes"] try: folder.verify_host_details(host_name, host["attributes"]) except (MKUserError, MKAuthException): failed_hosts.append(host_name) validated_entries.append((host_name, attributes, None)) folder.create_validated_hosts(validated_entries, bake_hosts=False) try_bake_agents_for_hosts([host["host_name"] for host in body["entries"]]) if failed_hosts: return problem( status=400, title="Provided details for some hosts are faulty", detail= f"Validated hosts were saved. The configurations for following hosts are faulty and " f"were skipped: {' ,'.join(failed_hosts)}.") hosts = [watolib.Host.host(entry['host_name']) for entry in entries] return host_collection(hosts)
def update_host(params): """Update a host""" host_name = params['host_name'] body = params['body'] new_attributes = body['attributes'] update_attributes = body['update_attributes'] remove_attributes = body['remove_attributes'] check_hostname(host_name, should_exist=True) host: watolib.CREHost = watolib.Host.host(host_name) constructors.require_etag(constructors.etag_of_obj(host)) if new_attributes: host.edit(new_attributes, None) if update_attributes: host.update_attributes(update_attributes) faulty_attributes = [] for attribute in remove_attributes: try: host.remove_attribute(attribute) except KeyError: faulty_attributes.append(attribute) if faulty_attributes: return problem( status=400, title="Some attributes were not removed", detail= f"The following attributes were not removed since they didn't exist: {', '.join(faulty_attributes)}", ) return _serve_host(host, False)
def bulk_set_acknowledgement_on_hosts(params): """Bulk acknowledge for hosts""" live = sites.live() entries = params['entries'] hosts: Dict[str, int] = { host_name: host_state for host_name, host_state in Query( # pylint: disable=unnecessary-comprehension [Hosts.name, Hosts.state], And(*[Hosts.name.equals(host_name) for host_name in entries]), ).fetch_values(live) } not_found = [] for host_name in entries: if host_name not in hosts: not_found.append(host_name) if not_found: return problem(status=400, title=f"Hosts {', '.join(not_found)} not found", detail='Current not monitored') up_hosts = [] for host_name in entries: if hosts[host_name] == 0: up_hosts.append(host_name) if up_hosts: return problem( status=400, title=f"Hosts {', '.join(up_hosts)} do not have a problem", detail="The states of these hosts are UP") for host_name in entries: acknowledge_host_problem( sites.live(), host_name, sticky=params.get('sticky'), notify=params.get('notify'), persistent=params.get('persistent'), user=_user_id(), comment=params.get('comment', 'Acknowledged'), ) return http.Response(status=204)
def delete_password(params): """Delete a password""" ident = params['name'] if ident not in load_passwords(): return problem( 404, f'Password "{ident}" is not known.', 'The password you asked for is not known. Please check for eventual misspellings.') remove_password(ident) return Response(status=204)
def show_password(params): """Show a password""" ident = params['name'] passwords = load_passwords() if ident not in passwords: return problem( 404, f'Password "{ident}" is not known.', 'The password you asked for is not known. Please check for eventual misspellings.') password_details = passwords[ident] return _serve_password(ident, password_details)
def show_host_tag_group(params): """Show a host tag group""" ident = params['name'] if not tag_group_exists(ident): return problem( 404, f'Host tag group "{ident}" is not known.', 'The host tag group you asked for is not known. Please check for eventual misspellings.' ) tag_group = _retrieve_group(ident=ident) return _serve_host_tag_group(tag_group.get_dict_format())
def show_host(params): """Show a host""" host_name = params['host_name'] host: watolib.CREHost = watolib.Host.host(host_name) if host is None: return problem( 404, f'Host "{host_name}" is not known.', 'The host you asked for is not known. Please check for eventual misspellings.' ) return _serve_host(host, params['effective_attributes'])
def delete_password(params): """Delete a password""" ident = params['name'] entries = load_passwords_to_modify() if ident not in entries: return problem( 404, f'Password "{ident}" is not known.', 'The password you asked for is not known. Please check for eventual misspellings.') _ = entries.pop(ident) return Response(status=204)