def alert_if_domain_expired(_: utils.LambdaEvent) -> List[dict]: """Check WHOIS data for each domain in WHOIS_DOMAINS env variable, send alert if errors.""" domains = env["WHOIS_DOMAINS"].replace(" ", "").split(",") futures = helpers.exec_in_thread_and_wait( (_domain_is_expiring, (domain, )) for domain in domains) results = [future.result() for future in futures.done] output = [{ "domain": result[0], "expiringSoon": result[1], "expiresDateUTC": result[2], } for result in results] if any(item["expiringSoon"] for item in output): title = "Domain(s) expiring soon" msg = ", ".join(f"{item['domain']}={item['expiresDateUTC']}" for item in output if item["expiringSoon"]) utils.Log.warning("%s: %s", title, msg) utils.Log.info("Sending alert notification via %s", env["LAMBDA_NOTIFICATIONS"]) aws.invoke_lambda(name=env["LAMBDA_NOTIFICATIONS"], payload={ "title": title, "payload": msg, }) return output
def trigger_lambdas(event: utils.LambdaEvent) -> Mapping: # pylint: disable=unused-argument """Trigger EPUB creator lambdas.""" response = {"new_articles": [], "count": 0} articles, since = retrieve() if not articles: utils.Log.info( "Found no new articles since %d, skipping 'since' storage", since) return response response["new_articles"] = [article["url"] for article in articles] response["count"] = len(articles) utils.Log.debug("Fan-out %d new article(s) to lambda %s", response["count"], env["LAMBDA_PUBLISHER"]) for article in articles: utils.Log.info("Triggering Lambda %s for %s", env["LAMBDA_PUBLISHER"], article) aws.invoke_lambda(name=env["LAMBDA_PUBLISHER"], payload=article, invoke_type="Event") utils.Log.info("Store new since to LogStream: %d", since) aws.send_event_to_logstream( log_group=env["SINCE_LOG_GROUP"], log_stream=env["SINCE_LOG_GROUP"], message={ "since": since, "items": sorted(int(article["item_id"]) for article in articles), }) return response
def invoke_publish(_: utils.LambdaEvent): """Poll blog's RSS/Atom feed and trigger LAMBDA_PUBLISH for each article added yesterday.""" posts = _poll_new_posts() for index, post in enumerate(posts, start=1): utils.Log.warning("Triggering lambda %s with payload URL %s", env["LAMBDA_PUBLISH"], post) aws.invoke_lambda(env["LAMBDA_PUBLISH"], payload={"url": post}) if index < len(posts): time.sleep( 10) # In case of multiple posts, delay 10 seconds to allow
def check_backup_buckets(_: utils.LambdaEvent) -> List[str]: """Scan for S3 backup bucket config in BUCKETS_TO_MONITOR env var and run checks on content. BUCKETS_TO_MONITOR is a ;-separated string in the format <bucket_name>,<retention_days>,<regexp>,<start_day_isoformat>,<tolerance>;<bucket_name>... where `bucket_name_x` is the S3 bucket name `retention_days` is an int with the max amount of days for any backup to be considered valid. Check will also fail if the bucket contains more than `retention_days + 1` keys. The + 1 is because the way S3 lifecycle polices remove expired keys is non deterministic, so there might be moments during the day when the actual amount of keys eccedes of 1 the expected value. `regexp` is a regular expression to be used to match the bucket keys, use NoRegexp to disable `start_day_isoformat` is the date in iso format of the first backup ever (needed for new buckets only), use NoStartDay to disable `tolerance` is an int with the the maximum expected change in file size compared to the previous backup, i.e. the check will fail if the size of today's backup is <tolerance percent> bigger or smaller than yesterday backup, use NoTolerance to disable """ configs = [] output = [] errors = False for config in env["BUCKETS_TO_MONITOR"].split(";"): bucket_name, retention_days, regexp, start_day_isoformat, tolerance = config.split( ",") configs.append([ bucket_name, int(retention_days), None if regexp == "NoRegexp" else regexp, None if start_day_isoformat == "NoStartDay" else start_day_isoformat, None if tolerance == "NoTolerance" else int(tolerance) ]) futures = utils.helpers.exec_in_thread_and_wait( ((_check_bucket_validity, config) for config in configs), False) for future in futures.done: try: output.append(future.result()) except utils.HandledError as error: output.append(str(error)) errors = True if errors: aws.invoke_lambda(name=env["LAMBDA_NOTIFICATIONS"], payload={ "title": "backups_monitor: errors", "payload": str(output), }) return output
def _send_attachment_to_kindle(key: str, bucket: str, item_id: int = None) -> utils.Response: utils.Log.info("Send attachment to %s via %s email notification service", env["KINDLE_EMAIL"], env["LAMBDA_NOTIFICATIONS"]) extension = None if key.endswith(".mobi"): extension = "mobi" if key.endswith(".html"): extension = "html" if extension not in ["mobi", "html"]: raise utils.HandledError( message= "Invalid document extension: must be either '.mobi' or '.html'", status_code=401) return aws.invoke_lambda( name=env["LAMBDA_NOTIFICATIONS"], payload={ "mail_to": env["KINDLE_EMAIL"], "attachments": [{ # https://www.iana.org/assignments/media-types/ "ContentType": "application/vnd.amazon.mobi8-ebook" \ if extension == "mobi" else "text/html", "Key": key, "Bucket": bucket, "Filename": f"pocket-{item_id}.{extension}" \ if item_id else f"{uuid4()}.{extension}", }], }, invoke_type="Event")
def _send_email(subject: str, content: str) -> utils.Response: utils.Log.info("Send report via %s email notification service", env["LAMBDA_NOTIFICATIONS"]) return aws.invoke_lambda(name=env["LAMBDA_NOTIFICATIONS"], payload={ "subject": subject, "text": content, }, invoke_type="Event")
def contact(event: utils.LambdaEvent) -> str: """ Send event payload to Notifications lambda for delivery. Expects these keys in event mapping: - source - name - email - description """ lambda_notifications = env["LAMBDA_NOTIFICATIONS"] body = event["body"] utils.Log.debug("Processing body payload: %s", body) try: utils.Log.debug("Loading JSON content from body") utils.Log.info("json.loads should be safe to use: " "https://stackoverflow.com/a/45483187/2274124") msg = """Source: {source} Name: {name} Mail: {email} Desc: {description} """.format(**json.loads(body)) except (TypeError, json.JSONDecodeError) as error: raise utils.HandledError("JSON body is malformatted: %s" % error) except KeyError as error: raise utils.HandledError("Missing JSON key: %s" % error) utils.Log.debug("### Message content below ###") utils.Log.debug(msg) utils.Log.debug("#############################") return aws.invoke_lambda( name=lambda_notifications, payload={ "title": "New /contact submission received", "payload": msg, }).text
def _send_attachment_to_kindle( key: str, bucket: str, item_id: Optional[int] = None) -> utils.Response: utils.Log.info("Send attachment to %s via %s email notification service", env["KINDLE_EMAIL"], env["LAMBDA_NOTIFICATIONS"]) return aws.invoke_lambda( name=env["LAMBDA_NOTIFICATIONS"], payload={ "mail_to": env["KINDLE_EMAIL"], "attachments": [{ # https://www.iana.org/assignments/media-types/application/vnd.amazon.mobi8-ebook "ContentType": "application/vnd.amazon.mobi8-ebook", "Key": key, "Bucket": bucket, "Filename": f"pocket-{item_id}.mobi" if item_id else f"{uuid4()}.mobi", }], }, invoke_type="Event")