Kerala Cyber
Warriors
KCW Uploader V1.1
"""
This module is responsible for handling all events
that must be raised to the user somehow. The main idea
behind this module is to centralize all events that happens
during the execution of Pro commands and allows us to report
those events in real time or through a machine-readable format.
"""
import enum
import json
import sys
from typing import Any, Dict, List, Optional, Set, Union # noqa: F401
from uaclient.yaml import safe_dump
JSON_SCHEMA_VERSION = "0.1"
EventFieldErrorType = Optional[Union[str, Dict[str, str]]]
_event_logger = None
def get_event_logger():
global _event_logger
if _event_logger is None:
_event_logger = EventLogger()
return _event_logger
@enum.unique
class EventLoggerMode(enum.Enum):
"""
Defines event logger supported modes.
Currently, we only support the cli and machine-readable mode. On cli mode,
we will print to stdout/stderr any event that we receive. Otherwise, we
will store those events and parse them for the specified format.
"""
CLI = object()
JSON = object()
YAML = object()
def format_machine_readable_output(status: Dict[str, Any]) -> Dict[str, Any]:
from uaclient.util import get_pro_environment
status["environment_vars"] = [
{"name": name, "value": value}
for name, value in sorted(get_pro_environment().items())
]
# We don't need the origin info in the json output
status.pop("origin", "")
# In case there is an error during status and the services were
# not processed
status.setdefault("services", [])
# We are redacting every variant information from the status output
# because we still are not sure on the best way to represent this
# information on the status machine readable output
for service in status.get("services", []):
if "variants" in service:
service.pop("variants")
return status
class EventLogger:
def __init__(self):
self._error_events = [] # type: List[Dict[str, EventFieldErrorType]]
self._warning_events = [] # type: List[Dict[str, EventFieldErrorType]]
self._processed_services = set() # type: Set[str]
self._failed_services = set() # type: Set[str]
self._needs_reboot = False
self._command = ""
self._output_content = {}
# By default, the event logger will be on CLI mode,
# printing every event it receives.
self._event_logger_mode = EventLoggerMode.CLI
def reset(self):
"""Reset the state of the event logger attributes."""
self._error_events = []
self._warning_events = []
self._processed_services = set()
self._failed_services = set()
self._needs_reboot = False
self._command = ""
self._output_content = {}
self._event_logger_mode = EventLoggerMode.CLI
def set_event_mode(self, event_mode: EventLoggerMode):
"""Set the event logger mode.
We currently support the CLI, JSON and YAML modes.
"""
self._event_logger_mode = event_mode
def set_command(self, command: str):
"""Set the event logger command.
The command will tell the process_events method which output method
to use.
"""
self._command = command
def set_output_content(self, output_content: Dict):
"""Set the event logger output content.
The command will tell the process_events method which content
to use.
"""
self._output_content = output_content
def info(self, info_msg: str, file_type=None, end: Optional[str] = None):
"""
Print the info message if the event logger is on CLI mode.
"""
if not file_type:
file_type = sys.stdout
if self._event_logger_mode == EventLoggerMode.CLI:
print(info_msg, file=file_type, end=end)
def _record_dict_event(
self,
msg: str,
service: Optional[str],
event_dict: List[Dict[str, EventFieldErrorType]],
code: Optional[str] = None,
event_type: Optional[str] = None,
additional_info: Optional[Dict[str, str]] = None,
):
if event_type is None:
event_type = "service" if service else "system"
event_entry = {
"type": event_type,
"service": service,
"message": msg,
"message_code": code,
} # type: Dict[str, EventFieldErrorType]
if additional_info:
event_entry["additional_info"] = additional_info
event_dict.append(event_entry)
def error(
self,
error_msg: str,
error_code: Optional[str] = None,
service: Optional[str] = None,
error_type: Optional[str] = None,
additional_info: Optional[Dict[str, str]] = None,
):
"""
Store an error in the event logger.
However, the error will only be stored if the event logger
is not on CLI mode.
"""
if self._event_logger_mode != EventLoggerMode.CLI:
self._record_dict_event(
msg=error_msg,
service=service,
event_dict=self._error_events,
code=error_code,
event_type=error_type,
additional_info=additional_info,
)
def warning(self, warning_msg: str, service: Optional[str] = None):
"""
Store a warning in the event logger.
However, the warning will only be stored if the event logger
is not on CLI mode.
"""
if self._event_logger_mode != EventLoggerMode.CLI:
self._record_dict_event(
msg=warning_msg,
service=service,
event_dict=self._warning_events,
)
def service_processed(self, service: str):
self._processed_services.add(service)
def services_failed(self, services: List[str]):
self._failed_services.update(services)
def service_failed(self, service: str):
self._failed_services.add(service)
def needs_reboot(self, reboot_required: bool):
self._needs_reboot = reboot_required
def _generate_failed_services(self):
services_with_error = {
error["service"]
for error in self._error_events
if error["service"]
}
return list(set.union(self._failed_services, services_with_error))
def _process_events_services(self):
response = {
"_schema_version": JSON_SCHEMA_VERSION,
"result": "success" if not self._error_events else "failure",
"processed_services": sorted(self._processed_services),
"failed_services": sorted(self._generate_failed_services()),
"errors": self._error_events,
"warnings": self._warning_events,
"needs_reboot": self._needs_reboot,
}
from uaclient.util import DatetimeAwareJSONEncoder
print(
json.dumps(response, cls=DatetimeAwareJSONEncoder, sort_keys=True)
)
def _process_events_status(self):
output = format_machine_readable_output(self._output_content)
output["result"] = "success" if not self._error_events else "failure"
output["errors"] = self._error_events
output["warnings"] = self._warning_events
if self._event_logger_mode == EventLoggerMode.JSON:
from uaclient.util import DatetimeAwareJSONEncoder
print(
json.dumps(
output, cls=DatetimeAwareJSONEncoder, sort_keys=True
)
)
elif self._event_logger_mode == EventLoggerMode.YAML:
print(safe_dump(output, default_flow_style=False))
def process_events(self) -> None:
"""
Creates a json response based on all of the
events stored in the event logger.
The json response will only be created if the event logger
is not on CLI mode.
"""
if self._event_logger_mode != EventLoggerMode.CLI:
if self._command == "status":
self._process_events_status()
else:
self._process_events_services()
-=[ KCW uplo4d3r c0ded by cJ_n4p573r ]=-
Ⓒ2017 ҠЄГѦLѦ СүѣЄГ ЩѦГГіѺГՏ