| #!/usr/bin/python |
| |
| '''CONFIG_JSON is a json encoded string base64 environment variable. It is used |
| to clone docker-ci database, generate docker-ci report and submit it by email. |
| CONFIG_JSON data comes from the file /report/credentials.json inserted in this |
| container by deployment.py: |
| |
| { "DOCKER_CI_PUB": "$(cat docker-ci_ssh_public_key.pub)", |
| "DOCKER_CI_KEY": "$(cat docker-ci_ssh_private_key.key)", |
| "DOCKER_CI_ADDRESS": "user@docker-ci_fqdn_server", |
| "SMTP_USER": "SMTP_server_user", |
| "SMTP_PWD": "SMTP_server_password", |
| "EMAIL_SENDER": "Buildbot_mailing_sender", |
| "EMAIL_RCP": "Buildbot_mailing_receipient" } ''' |
| |
| import os, re, json, sqlite3, datetime, base64 |
| import smtplib |
| from datetime import timedelta |
| from subprocess import call |
| from os import environ as env |
| |
| TODAY = datetime.date.today() |
| |
| # Load credentials to the environment |
| env['CONFIG_JSON'] = base64.b64decode(open('/report/credentials.json').read()) |
| |
| # Remove SSH private key as it needs more processing |
| CONFIG = json.loads(re.sub(r'("DOCKER_CI_KEY".+?"(.+?)",)','', |
| env['CONFIG_JSON'], flags=re.DOTALL)) |
| |
| # Populate environment variables |
| for key in CONFIG: |
| env[key] = CONFIG[key] |
| |
| # Load SSH private key |
| env['DOCKER_CI_KEY'] = re.sub('^.+"DOCKER_CI_KEY".+?"(.+?)".+','\\1', |
| env['CONFIG_JSON'],flags=re.DOTALL) |
| |
| # Prevent rsync to validate host on first connection to docker-ci |
| os.makedirs('/root/.ssh') |
| open('/root/.ssh/id_rsa','w').write(env['DOCKER_CI_KEY']) |
| os.chmod('/root/.ssh/id_rsa',0600) |
| open('/root/.ssh/config','w').write('StrictHostKeyChecking no\n') |
| |
| |
| # Sync buildbot database from docker-ci |
| call('rsync {}:/data/buildbot/master/state.sqlite .'.format( |
| env['DOCKER_CI_ADDRESS']), shell=True) |
| |
| class SQL: |
| def __init__(self, database_name): |
| sql = sqlite3.connect(database_name) |
| # Use column names as keys for fetchall rows |
| sql.row_factory = sqlite3.Row |
| sql = sql.cursor() |
| self.sql = sql |
| |
| def query(self,query_statement): |
| return self.sql.execute(query_statement).fetchall() |
| |
| sql = SQL("state.sqlite") |
| |
| |
| class Report(): |
| |
| def __init__(self,period='',date=''): |
| self.data = [] |
| self.period = 'date' if not period else period |
| self.date = str(TODAY) if not date else date |
| self.compute() |
| |
| def compute(self): |
| '''Compute report''' |
| if self.period == 'week': |
| self.week_report(self.date) |
| else: |
| self.date_report(self.date) |
| |
| |
| def date_report(self,date): |
| '''Create a date test report''' |
| builds = [] |
| # Get a queryset with all builds from date |
| rows = sql.query('SELECT * FROM builds JOIN buildrequests' |
| ' WHERE builds.brid=buildrequests.id and' |
| ' date(start_time, "unixepoch", "localtime") = "{0}"' |
| ' GROUP BY number'.format(date)) |
| build_names = sorted(set([row['buildername'] for row in rows])) |
| # Create a report build line for a given build |
| for build_name in build_names: |
| tried = len([row['buildername'] |
| for row in rows if row['buildername'] == build_name]) |
| fail_tests = [row['buildername'] for row in rows if ( |
| row['buildername'] == build_name and row['results'] != 0)] |
| fail = len(fail_tests) |
| fail_details = '' |
| fail_pct = int(100.0*fail/tried) if tried != 0 else 100 |
| builds.append({'name': build_name, 'tried': tried, 'fail': fail, |
| 'fail_pct': fail_pct, 'fail_details':fail_details}) |
| if builds: |
| self.data.append({'date': date, 'builds': builds}) |
| |
| |
| def week_report(self,date): |
| '''Add the week's date test reports to report.data''' |
| date = datetime.datetime.strptime(date,'%Y-%m-%d').date() |
| last_monday = date - datetime.timedelta(days=date.weekday()) |
| week_dates = [last_monday + timedelta(days=x) for x in range(7,-1,-1)] |
| for date in week_dates: |
| self.date_report(str(date)) |
| |
| def render_text(self): |
| '''Return rendered report in text format''' |
| retval = '' |
| fail_tests = {} |
| for builds in self.data: |
| retval += 'Test date: {0}\n'.format(builds['date'],retval) |
| table = '' |
| for build in builds['builds']: |
| table += ('Build {name:15} Tried: {tried:4} ' |
| ' Failures: {fail:4} ({fail_pct}%)\n'.format(**build)) |
| if build['name'] in fail_tests: |
| fail_tests[build['name']] += build['fail_details'] |
| else: |
| fail_tests[build['name']] = build['fail_details'] |
| retval += '{0}\n'.format(table) |
| retval += '\n Builds failing' |
| for fail_name in fail_tests: |
| retval += '\n' + fail_name + '\n' |
| for (fail_id,fail_url,rn_tests,nr_errors,log_errors, |
| tracelog_errors) in fail_tests[fail_name]: |
| retval += fail_url + '\n' |
| retval += '\n\n' |
| return retval |
| |
| |
| # Send email |
| smtp_from = env['EMAIL_SENDER'] |
| subject = '[docker-ci] Daily report for {}'.format(str(TODAY)) |
| msg = "From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n".format( |
| smtp_from, env['EMAIL_RCP'], subject) |
| msg = msg + Report('week').render_text() |
| server = smtplib.SMTP_SSL('smtp.mailgun.org') |
| server.login(env['SMTP_USER'], env['SMTP_PWD']) |
| server.sendmail(smtp_from, env['EMAIL_RCP'], msg) |