Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Freistilbox utils
#!/usr/bin/env python3
import os
import sys
import re
import argparse
from datetime import timedelta
from datetime import datetime
deploy_log_line_pattern = re.compile(r'^[A-Z], \[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})#(\d+)\] \s*([\w]+) \S+ : (.+)$')
job_success_msg = 'Deployment finished.'
job_fail_msg = 'Aborting deployment.'
JOB_STATUS_RUNNING = 0
JOB_STATUS_SUCCEEDED = 1
JOB_STATUS_FAILED = 2
JOB_STATUS_LABEL = {
JOB_STATUS_RUNNING: 'running',
JOB_STATUS_FAILED: 'fail',
JOB_STATUS_SUCCEEDED: 'success'
}
def read_file(file_path, continuous=False):
if not continuous:
with open(file_path,'r') as file_handle:
for line in file_handle:
line = line.strip()
if line:
yield line
else:
with open(file_path,'r') as file_handle:
while not file_handle.closed:
line = file_handle.readline().strip()
if line:
yield line
class ParsingError(Exception):
def __init__(self, raw_line):
super(ParsingError, self).__init__("Parsing error!! Raw input line: %s" % raw_line)
class DeployJob:
def __init__(self, job_id, status, begin, end, log_entries):
"""
"""
self.job_id = job_id
self.status = status
self.begin = begin
self.end = end
self.log = log_entries
@property
def completed(self) -> bool:
return self.status in [JOB_STATUS_FAILED, JOB_STATUS_SUCCEEDED]
@property
def duration(self) -> timedelta:
if self.begin and self.end:
return self.end - self.begin
else:
return None
def read_last_job(file_path, since=None, commit_hash=None, continuous=False):
last_job_id = None
last_job_log_entries = None
last_job_status = None
last_job_begin = None
last_job_end = None
last_job_commit_hash_found = False
for line in read_file(file_path, continuous):
if not line or line.startswith('#'):
# skip empty or commented lines
continue
parsed_line = deploy_log_line_pattern.match(line)
if not parsed_line:
raise ParsingError(line)
log_time = datetime.strptime(parsed_line.group(1), r'%Y-%m-%d %H:%M:%S')
job_id = parsed_line.group(2)
log_level = parsed_line.group(3)
log_msg = parsed_line.group(4)
if since and log_time < since:
# skip entry if it's out of scope
continue
if job_id != last_job_id:
# found a new job
last_job_id = job_id
last_job_log_entries = []
last_job_status = JOB_STATUS_RUNNING
last_job_begin = log_time
last_job_log_entries.append({'time': log_time, 'level': log_level, 'msg': log_msg})
if log_msg == job_fail_msg:
last_job_status = JOB_STATUS_FAILED
last_job_end = log_time
if last_job_commit_hash_found or (not commit_hash and continuous):
break;
elif log_msg == job_success_msg:
last_job_status = JOB_STATUS_SUCCEEDED
last_job_end = log_time
if last_job_commit_hash_found or (not commit_hash and continuous):
break;
elif commit_hash and log_msg.startswith("Current release is %s" % commit_hash):
last_job_commit_hash_found = True
if not last_job_id:
return None
elif commit_hash and not last_job_commit_hash_found:
return None
else:
return DeployJob(last_job_id, last_job_status, last_job_begin, last_job_end, last_job_log_entries)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Freistilbox deployment log interpreter.')
parser.add_argument('--file', '-f', default='~/.deploy/deploy.log', help='Path to log file.')
parser.add_argument('--quiet', '-q', action='store_true', help='Don\'t write to stdout.')
parser.add_argument('--verbose', '-v', action='store_true', help='Show job details. (Ignored if --quiet is set)')
parser.add_argument('--since', '-s', type=int, metavar='TIMESTAMP', help='Don\'t interpret log entries before unix timestamp.')
parser.add_argument('--commit', '-c', metavar='HASH', help='Look for job triggered by specific git commit.')
parser.add_argument('--wait', '-w', action='store_true', help='Keep log open until last job completes. This is ignored if last job already completed.')
# TODO: add wait-timeout argument (and an implementation for it)
args = parser.parse_args()
file_path = os.path.expanduser(args.file)
if not os.path.exists(file_path):
parser.error("File not found: %s" % file_path)
if args.quiet:
def stdout(msg=""):
pass
else:
def stdout(msg=""):
print(msg)
def stderr(msg):
print(msg, file=sys.stderr)
try:
since = None
if args.since:
since = datetime.fromtimestamp(args.since)
job = read_last_job(file_path, since, args.commit)
if args.wait:
if args.commit and job and job.completed:
# Already got the completed job triggered by args.commit.
# No need to wait any longer - continue right away!
pass
elif job and job.completed:
# Got time of last completed job in past.
# Now wait for next job and continue once it wrote its "job complete" message into the log file.
job = read_last_job(file_path, job.end + timedelta(microseconds=1), None, True)
else:
# Last job in past did not yet complete (or no job found at all since log file was empty).
# Wait for the next "job complete" message in the log file and then continue.
job = read_last_job(file_path, since, args.commit, True)
if not job:
msg = "No deployment job found"
if args.commit:
msg += " for git commit %s" % args.commit
if since:
msg += " since %s" % since.strftime("%Y-%m-%d %H:%M:%S")
stderr("%s." % msg)
sys.exit(23)
if args.verbose:
if since:
stdout("Checked log entries since %s." % since.strftime("%Y-%m-%d %H:%M:%S"))
stdout()
if args.commit:
stdout("Job triggered by git commit %s" % args.commit)
else:
stdout("Most recent job")
stdout(" id: %s" % job.job_id)
stdout(" status: %s" % JOB_STATUS_LABEL.get(job.status, 'unknown').upper())
if job.completed:
since = job.begin.strftime("%Y-%m-%d %H:%M:%S")
till = job.end.strftime("%H:%M:%S")
stdout(" from %s to %s (%s)" % (since, till, job.duration))
else:
since = job.begin.strftime("%Y-%m-%d %H:%M:%S")
duration = (datetime.now() - job.begin)
stdout(" running since %s (%s)" % (since, duration))
stdout()
for log_entry in job.log:
stdout("%s - %s: %s" % (log_entry['time'].strftime('%c'), log_entry['level'], log_entry['msg']))
if job.status == JOB_STATUS_SUCCEEDED:
sys.exit(0)
elif job.status == JOB_STATUS_RUNNING:
sys.exit(21)
elif job.status == JOB_STATUS_FAILED:
sys.exit(22)
else:
sys.exit(24)
except PermissionError as e:
stderr(e)
sys.exit(1)
except ParsingError as e:
stderr(e)
sys.exit(13)
except KeyboardInterrupt as e:
stdout()
sys.exit(1)
#!/bin/bash
set -e
set -o pipefail
[ "$#" -gt 0 ] || { >&2 echo "no Freistilbox cluster ID defined."; exit 1; }
CLUSTER=$1
shift
[ "$#" -gt 0 ] || { >&2 echo "no Freistilbox site ID defined."; exit 1; }
SITE=$1
shift
SSH_HOST="${CLUSTER}s.freistilbox.net"
DRUSH_CMD="/usr/local/bin/drush"
FSB_DOCROOT="/srv/www/freistilbox/clients/c11000/${SITE}/current/docroot/"
DUMP_OPTIONS="--extra-dump=--opt --extra-dump=--hex-blob --extra-dump=--skip-comments"
echo "Creating MySQL Dump of site ${SITE} on Freistilbox cluster ${CLUSTER}"
ssh -C ${SITE}@${SSH_HOST} "${DRUSH_CMD} --root=$FSB_DOCROOT sql:dump $DUMP_OPTIONS" | gzip --no-name > ${SITE}_dump.sql.gz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.