def put(self, user_id: str): """ Return information on a user. """ if user_id == "me": user = auth.get_current_user() if not user: return self.error("not authenticated", 401) else: raise NotImplementedError result = self.schema_from_request(user_schema, partial=True) if result.errors: return self.respond(result.errors, 403) for name, values in result.data.get("options", {}).items(): for subname, value in values.items(): create_or_update( ItemOption, where={ "item_id": user.id, "name": "{}.{}".format(name, subname) }, values={"value": value}, ) db.session.commit() user.options = list( ItemOption.query.filter(ItemOption.item_id == user.id)) return self.respond_with_schema(user_schema, user)
def get(self, repo: Repository): """ Return a list of builds for the given repository. """ query = (Build.query.options( joinedload("revision"), subqueryload_all("authors"), subqueryload_all("revision.authors"), subqueryload_all("stats"), ).filter(Build.repository_id == repo.id).order_by(Build.number.desc())) user = request.args.get("user") if user: if user == "me": user = auth.get_current_user() else: user = User.query.get(user) if not user: return self.respond([]) query = query.filter( Build.authors.any( Author.email.in_( db.session.query(Email.email).filter( Email.user_id == user.id, Email.verified == True # NOQA )))) return self.paginate_with_schema(builds_schema, query)
def post(self): """ Create a new API token for the user. """ user = auth.get_current_user() if not user: return self.error('not authenticated', 401) token = UserApiToken.query \ .filter(UserApiToken.user == user) \ .one_or_none() if token: token.key = UserApiToken.generate_token() else: token = UserApiToken(user=user) try: db.session.add(token) db.session.commit() except IntegrityError: db.session.rollback() return self.respond(status=422) return self.respond_with_schema(token_schema, token)
def get(self, user_id): """ Return a list of builds for the given user. """ if user_id == 'me': user = auth.get_current_user() if not user: return self.error('not authenticated', 401) else: user = User.query.get(user_id) query = Build.query.options( joinedload('repository'), contains_eager('source'), joinedload('source').joinedload('author'), joinedload('source').joinedload('revision'), joinedload('source').joinedload('patch'), subqueryload_all('stats'), ).join( Source, Build.source_id == Source.id, ).filter( Source.author_id.in_( db.session.query(Author.id).filter( Author.email.in_( db.session.query(Email.email).filter( Email.user_id == user.id, Email.verified == True, # NOQA ))))).order_by(Build.date_created.desc()) return self.paginate_with_schema(builds_schema, query)
def get(self, repo: Repository): """ Return a list of builds for the given repository. """ user = auth.get_current_user() query = Build.query.options( contains_eager('source'), joinedload('source').joinedload('author'), joinedload('source').joinedload('revision'), joinedload('source').joinedload('patch'), subqueryload_all('stats'), ).join( Source, Build.source_id == Source.id, ).filter( Build.repository_id == repo.id, ).order_by(Build.number.desc()) show = request.args.get('show') if show == 'mine': query = query.filter( Source.author_id.in_( db.session.query(Author.id).filter(Author.email.in_( db.session.query(Email.email).filter( Email.user_id == user.id ) )) ) ) return self.paginate_with_schema(builds_schema, query)
def get(self, repo: Repository): """ Return a repository. """ schema = RepositorySchema(strict=True, context={"user": auth.get_current_user()}) return self.respond_with_schema(schema, repo)
def get(self, repo: Repository): """ Return a list of builds for the given repository. """ user = auth.get_current_user() query = (Build.query.options( joinedload("source", innerjoin=True), joinedload("source", innerjoin=True).joinedload("author"), joinedload("source", innerjoin=True).joinedload("revision", innerjoin=True), subqueryload_all("stats"), ).filter(Build.repository_id == repo.id).order_by(Build.number.desc())) show = request.args.get("show") if show == "mine": query = query.filter( Build.author_id.in_( db.session.query(Author.id).filter( Author.email.in_( db.session.query(Email.email).filter( Email.user_id == user.id, Email.verified == True # NOQA ))))) return self.paginate_with_schema(builds_schema, query)
def get(self): """ Return a list of GitHub organizations avaiable to the current user. """ user = auth.get_current_user() provider = GitHubRepositoryProvider() return provider.get_owners(user)
def get(self): """ Return a list of GitHub repositories avaiable to the current user. """ user = auth.get_current_user() try: github, identity = self.get_github_client(user) except IdentityNeedsUpgrade as exc: return self.respond( { 'error': 'identity_needs_upgrade', 'url': exc.get_upgrade_url(), }, 401) no_cache = request.args.get('noCache') in ('1', 'true') # the results of these API calls are going to need cached owner_name = request.args.get('orgName') cache = GitHubCache(github) response = cache.get_repos(owner_name, no_cache=no_cache) active_repo_ids = frozenset( r[0] for r in db.session.query(Repository.external_id).filter( Repository.provider == RepositoryProvider.github, )) return [{ 'name': r['full_name'], 'active': str(r['id']) in active_repo_ids, } for r in response]
def track_user(response): from zeus import auth from zeus.utils import timezone user = auth.get_current_user(fetch=False) if user and user.date_active < timezone.now() - timedelta(minutes=5): user.date_active = timezone.now() db.session.add(user) db.session.commit() return response
def get(self): """ Return a list of GitHub organizations avaiable to the current user. """ user = auth.get_current_user() github, identity = self.get_github_client(user) response = github.get('/user/orgs') return [{ 'name': r['login'], } for r in response]
def get(self): """ Return the API token for the user. """ user = auth.get_current_user() if not user: return self.error("not authenticated", 401) token = UserApiToken.query.filter( UserApiToken.user == user).one_or_none() return self.respond_with_schema(token_schema, token)
def debug_notification(): if not auth.get_current_user(): auth.bind_redirect_target(request.path) return redirect("/login") build = (Build.query.filter(Build.result == Result.failed).order_by( Build.date_created.desc()).limit(1).first()) assert build msg = build_message(build, force=True) assert msg preview = MailPreview(msg) return preview.render()
def put(self, repo: Repository): """ Return a repository. """ schema = RepositorySchema( partial=True, context={"repository": repo, "user": auth.get_current_user()} ) self.schema_from_request(schema) if db.session.is_modified(repo): db.session.add(repo) db.session.commit() return self.respond_with_schema(schema, repo)
def get(self): """ Return a list of repositories. """ tenant = auth.get_current_tenant() if not tenant.repository_ids: return self.respond([]) query = (Repository.query.filter( Repository.id.in_(tenant.repository_ids)).order_by( Repository.owner_name.asc(), Repository.name.asc()).limit(100)) schema = RepositorySchema(many=True, context={"user": auth.get_current_user()}) return self.paginate_with_schema(schema, query)
def get(self, user_id): """ Return a list of builds for the given user. """ if user_id == "me": user = auth.get_current_user() if not user: return self.error("not authenticated", 401) else: raise NotImplementedError query = Email.query.filter(Email.user_id == user.id).order_by(Email.email.asc()) return self.paginate_with_schema(emails_schema, query)
def get(self, user_id: str): """ Return information on a user. """ if user_id == "me": user = auth.get_current_user() if not user: return self.error("not authenticated", 401) else: raise NotImplementedError user.options = list(ItemOption.query.filter(ItemOption.item_id == user.id)) return self.respond_with_schema(user_schema, user)
def get(self): """ Return a list of builds. """ # tenants automatically restrict this query but we dont want # to include public repos tenant = auth.get_current_tenant() if not tenant.repository_ids: return self.respond([]) query = ( Build.query.options( joinedload("repository"), joinedload("revision"), subqueryload_all("revision.authors"), subqueryload_all("stats"), subqueryload_all("authors"), ) .filter(Build.repository_id.in_(tenant.repository_ids)) .order_by(Build.date_created.desc()) ) user = request.args.get("user") if user: if user == "me": user = auth.get_current_user() else: user = User.query.get(user) if not user: return self.respond([]) query = query.filter( Build.authors.any( Author.email.in_( db.session.query(Email.email).filter( Email.user_id == user.id, Email.verified == True # NOQA ) ) ) ) repository = request.args.get("repository") if repository: repo = Repository.from_full_name(repository) if not repo: return self.respond([]) query = query.filter(Build.repository_id == repo.id) return self.paginate_with_schema(builds_schema, query)
def get(self): """ Return a list of GitHub repositories avaiable to the current user. """ no_cache = request.args.get("noCache") in ("1", "true") include_private = request.args.get("private") in ("1", "true") owner_name = request.args.get("orgName") user = auth.get_current_user() provider = GitHubRepositoryProvider(cache=not no_cache) try: repo_list = provider.get_repos_for_owner( user=user, owner_name=owner_name, include_private_repos=include_private) except IdentityNeedsUpgrade as exc: return self.respond( { "provider": "github", "error": "identity_needs_upgrade", "url": exc.get_upgrade_url(), }, 401, ) known_repo_ids = { r[0]: r[1].name for r in db.session.query( Repository.external_id, Repository.status).join( RepositoryAccess, RepositoryAccess.repository_id == Repository.id).filter( Repository.provider == RepositoryProvider.github, RepositoryAccess.user_id == user.id, ) } return [{ "name": r["config"]["full_name"], "permissions": { "read": Permission.read in r["permission"], "write": Permission.write in r["permission"], "admin": Permission.admin in r["permission"], }, "status": known_repo_ids.get(str(r["id"])), } for r in repo_list]
def get(self): """ Return a list of change requests. """ tenant = auth.get_current_tenant() if not tenant.repository_ids: return self.respond([]) query = (ChangeRequest.query.options( joinedload("head_revision"), joinedload("parent_revision", innerjoin=True), joinedload("author"), ).filter(ChangeRequest.repository_id.in_( tenant.repository_ids)).order_by( ChangeRequest.date_created.desc())) user = request.args.get("user") if user: if user == "me": user = auth.get_current_user() else: user = User.query.get(user) if not user: return self.respond([]) query = query.filter( ChangeRequest.author_id.in_( db.session.query(Author.id).filter( Author.email.in_( db.session.query(Email.email).filter( Email.user_id == user.id, Email.verified == True # NOQA ))))) repository = request.args.get("repository") if repository: repo = Repository.from_full_name(repository) if not repo: return self.respond([]) query = query.filter(ChangeRequest.repository_id == repo.id) schema = ChangeRequestWithBuildSchema(many=True, strict=True) return self.paginate_with_schema(schema, query)
def get(self, repo: Repository): """ Return a list of builds for the given repository. """ user = auth.get_current_user() query = ChangeRequest.query.options( joinedload("head_revision"), joinedload("parent_revision"), joinedload("author"), ).filter(ChangeRequest.repository_id == repo.id).order_by( ChangeRequest.number.desc()) show = request.args.get("show") if show == "mine": query = query.filter( ChangeRequest.author_id.in_( db.session.query(Author.id).filter( Author.email.in_( db.session.query(Email.email).filter( Email.user_id == user.id))))) return self.paginate_with_schema(change_requests_schema, query)
def grant_access_to_existing_repos(user): provider = GitHubRepositoryProvider(cache=True) owner_list = [o['name'] for o in provider.get_owners(user)] if owner_list: matching_repos = Repository.query.unrestricted_unsafe().filter( Repository.provider == RepositoryProvider.github, Repository.owner_name.in_(owner_list), ~Repository.id.in_( db.session.query(RepositoryAccess.repository_id, ).filter( RepositoryAccess.user_id == user.id, ))) for repo in matching_repos: if provider.has_access(auth.get_current_user(), repo): try: with db.session.begin_nested(): db.session.add( RepositoryAccess( repository_id=repo.id, user_id=user.id, )) db.session.flush() except IntegrityError: pass db.session.commit()
def get(self, user_id): """ Return a list of builds for the given user. """ if user_id == 'me': user = auth.get_current_user() else: user = User.query.get(user_id) query = Build.query.options( contains_eager('source'), joinedload('source').joinedload('revision'), joinedload('source').joinedload('patch'), subqueryload_all('stats'), ).join( Source, Build.source_id == Source.id, ).filter( Source.author_id.in_( db.session.query( Author.id).filter(Author.email == user.email))).order_by( Build.number.desc()).limit(100) return self.respond_with_schema(builds_schema, query)
def get(self): """ Return a list of GitHub repositories avaiable to the current user. """ no_cache = request.args.get('noCache') in ('1', 'true') include_private = request.args.get('private') in ('1', 'true') owner_name = request.args.get('orgName') user = auth.get_current_user() provider = GitHubRepositoryProvider(cache=not no_cache) try: repo_list = provider.get_repos_for_owner( user=user, owner_name=owner_name, include_private_repos=include_private) except IdentityNeedsUpgrade as exc: return self.respond( { 'provider': 'github', 'error': 'identity_needs_upgrade', 'url': exc.get_upgrade_url(), }, 401) active_repo_ids = frozenset( r[0] for r in db.session.query(Repository.external_id).join( RepositoryAccess, RepositoryAccess.repository_id == Repository.id, ).filter( Repository.provider == RepositoryProvider.github, RepositoryAccess.user_id == user.id, )) return [{ 'name': r['name'], 'admin': r['admin'], 'active': str(r['id']) in active_repo_ids, } for r in repo_list]
def post(self): """ Activate a GitHub repository. """ repo_name = (request.get_json() or {}).get('name') if not repo_name: return self.error('missing repo_name parameter') user = auth.get_current_user() try: github, _ = self.get_github_client(user) except IdentityNeedsUpgrade as exc: return self.respond( { 'error': 'identity_needs_upgrade', 'url': exc.get_upgrade_url(), }, 401) # fetch repository details using their credentials repo_data = github.get('/repos/{}'.format(repo_name)) owner_name, repo_name = repo_data['full_name'].split('/', 1) repo = Repository.query.filter( Repository.provider == RepositoryProvider.github, Repository.external_id == str(repo_data['id']), ).first() if repo is None: # bind various github specific attributes repo = Repository( backend=RepositoryBackend.git, provider=RepositoryProvider.github, status=RepositoryStatus.active, external_id=str(repo_data['id']), owner_name=owner_name, name=repo_name, url=repo_data['clone_url'], data={'github': { 'full_name': repo_data['full_name'] }}, ) db.session.add(repo) # generate a new private key for use on github key = ssh.generate_key() db.session.add( ItemOption( item_id=repo.id, name='auth.private-key', value=key.private_key, )) # register key with github github.post('/repos/{}/keys'.format( repo.data['github']['full_name']), json={ 'title': 'zeus', 'key': key.public_key, 'read_only': True, }) # we need to commit before firing off the task db.session.commit() import_repo.delay(repo_id=repo.id) try: with db.session.begin_nested(): db.session.add( RepositoryAccess( repository_id=repo.id, user_id=user.id, )) db.session.flush() except IntegrityError: pass db.session.commit() return self.respond_with_schema(repo_schema, repo, 201)
def post(self): """ Activate a GitHub repository. """ repo_name = (request.get_json() or {}).get('name') if not repo_name: return self.error('missing repo_name parameter') owner_name, repo_name = repo_name.split('/', 1) user = auth.get_current_user() provider = GitHubRepositoryProvider(cache=False) try: repo_data = provider.get_repo(user=user, owner_name=owner_name, repo_name=repo_name) except IdentityNeedsUpgrade as exc: return self.respond( { 'provider': 'github', 'error': 'identity_needs_upgrade', 'url': exc.get_upgrade_url(), }, 401) if not repo_data['admin']: return self.respond( { 'message': 'Insufficient permissions to activate repository', }, 403) lock_key = 'repo:{provider}/{owner_name}/{repo_name}'.format( provider='github', owner_name=owner_name, repo_name=repo_name, ) with redis.lock(lock_key): try: with db.session.begin_nested(): # bind various github specific attributes repo = Repository( backend=RepositoryBackend.git, provider=RepositoryProvider.github, status=RepositoryStatus.active, external_id=str(repo_data['id']), owner_name=owner_name, name=repo_name, url=repo_data['url'], data=repo_data['config'], ) db.session.add(repo) db.session.flush() except IntegrityError: repo = Repository.query.unrestricted_unsafe().filter( Repository.provider == RepositoryProvider.github, Repository.external_id == str(repo_data['id']), ).first() # it's possible to get here if the "full name" already exists assert repo needs_configured = repo.status == RepositoryStatus.inactive if needs_configured: repo.status = RepositoryStatus.active db.session.add(repo) else: needs_configured = True if needs_configured: # generate a new private key for use on github key = ssh.generate_key() db.session.add( ItemOption( item_id=repo.id, name='auth.private-key', value=key.private_key, )) # register key with github provider.add_key( user=user, repo_name=repo_name, owner_name=owner_name, key=key, ) # we need to commit before firing off the task db.session.commit() import_repo.delay(repo_id=repo.id) try: with db.session.begin_nested(): db.session.add( RepositoryAccess( repository_id=repo.id, user_id=user.id, )) db.session.flush() except IntegrityError: pass db.session.commit() return self.respond_with_schema(repo_schema, repo, 201)
def authenticate(self): user = auth.get_current_user() if not user: return None return auth.Tenant.from_user(user)
def get(self): # TODO(dcramer): handle errors oauth = get_oauth_session(state=session.pop("oauth_state", None)) try: resp = oauth.fetch_token( GITHUB_TOKEN_URI, client_secret=current_app.config["GITHUB_CLIENT_SECRET"], authorization_response=request.url, ) except OAuth2Error: current_app.logger.exception("oauth.error") # redirect, as this is likely temporary based on server data return redirect(auth.get_redirect_target(clear=True) or "/") if resp is None or resp.get("access_token") is None: return Response("Access denied: reason=%s error=%s resp=%s" % (request.args["error"], request.args["error_description"], resp)) assert resp.get("token_type") == "bearer" scopes = resp["scope"][0].split(",") if "user:email" not in scopes: raise NotImplementedError # fetch user details client = GitHubClient(token=resp["access_token"]) user_data = client.get("/user") identity_config = { "access_token": resp["access_token"], "refresh_token": resp.get("refresh_token"), "login": user_data["login"], } email_list = client.get("/user/emails") email_list.append({ "email": "{}@users.noreply.github.com".format(user_data["login"]), "verified": True, }) primary_email = user_data.get("email") # HACK(dcramer): capture github's anonymous email addresses when they're not listed # (we haven't actually confirmed they're not listed) if not primary_email: primary_email = next((e["email"] for e in email_list if e["verified"] and e["primary"])) try: # we first attempt to create a new user + identity with db.session.begin_nested(): user = User(email=primary_email) db.session.add(user) identity = Identity( user=user, external_id=str(user_data["id"]), provider="github", scopes=scopes, config=identity_config, ) db.session.add(identity) user_id = user.id new_user = True except IntegrityError: # if that fails, assume the identity exists identity = Identity.query.filter( Identity.external_id == str(user_data["id"]), Identity.provider == "github", ).first() # and if it doesnt, attempt to find a matching user, # as it means the failure above was due to that if not identity: user = User.query.filter(User.email == primary_email).first() assert ( user ) # this should not be possible unless we've got a race condition identity = Identity( user=user, external_id=str(user_data["id"]), provider="github", scopes=scopes, config=identity_config, ) db.session.add(identity) user_id = user.id else: identity.config = identity_config identity.scopes = scopes db.session.add(identity) user_id = identity.user_id new_user = False db.session.flush() for email in email_list: try: with db.session.begin_nested(): db.session.add( Email( user_id=user_id, email=email["email"], verified=email["verified"], )) except IntegrityError: pass db.session.commit() # forcefully expire a session after permanent_session_lifetime # Note: this is enforced in zeus.auth auth.login_user(user_id) user = auth.get_current_user() if new_user: # update synchronously so the new user has a better experience sync_github_access(user_id=user.id) else: sync_github_access.delay(user_id=user.id) next_uri = auth.get_redirect_target(clear=True) or "/" if "/login" in next_uri or "/auth/github" in next_uri: next_uri = "/" return redirect(next_uri)
def build_message(build: Build, force=False) -> Message: author = Author.query.join( Source, Source.author_id == Author.id, ).filter(Source.id == build.source_id, ).first() if not author: current_app.logger.info('mail.missing-author', extra={ 'build_id': build.id, }) return users = find_linked_accounts(build) if not users and not force: current_app.logger.info('mail.no-linked-accounts', extra={ 'build_id': build.id, }) return elif not users: users = [auth.get_current_user()] # filter it down to the users that have notifications enabled user_options = dict( db.session.query(ItemOption.item_id, ItemOption.value).filter( ItemOption.item_id.in_([u.id for u in users]), ItemOption.name == 'mail.notify_author', )) users = [u for u in users if user_options.get(u.id, '1') == '1'] if not users: current_app.logger.info('mail.no-enabed-accounts', extra={ 'build_id': build.id, }) return source = Source.query.get(build.source_id) assert source repo = Repository.query.get(build.repository_id) assert repo revision = Revision.query.filter( Revision.sha == source.revision_sha, Revision.repository_id == build.repository_id, ).first() assert revision job_list = sorted(Job.query.filter(Job.build_id == build.id), key=lambda x: [x.result != Result.failed, x.number]) job_ids = [j.id for j in job_list] recipients = [u.email for u in users] subject = 'Build {} - {}/{} #{}'.format( str(build.result).title(), repo.owner_name, repo.name, build.number, ) if job_ids: failing_tests_query = TestCase.query.options( undefer('message')).filter( TestCase.job_id.in_(job_ids), TestCase.result == Result.failed, ) failing_tests_count = failing_tests_query.count() failing_tests = failing_tests_query.limit(10) style_violations_query = StyleViolation.query.filter( StyleViolation.job_id.in_(job_ids), ) style_violations_count = style_violations_query.count() style_violations = style_violations_query.limit(10) else: failing_tests = () failing_tests_count = 0 style_violations = () style_violations_count = 0 context = { 'title': subject, 'uri': '{proto}://{domain}/{repo}/builds/{build_no}'.format( proto='https' if current_app.config['SSL'] else 'http', domain=current_app.config['DOMAIN'], repo=repo.get_full_name(), build_no=build.number, ), 'build': { 'number': build.number, 'result': { 'id': str(build.result), 'name': str(build.result).title(), }, 'label': build.label, }, 'repo': { 'owner_name': repo.owner_name, 'name': repo.name, 'full_name': repo.get_full_name, }, 'author': { 'name': author.name, 'email': author.email, }, 'revision': { 'sha': revision.sha, 'short_sha': revision.sha[:7], 'message': revision.message, }, 'job_list': [{ 'number': job.number, 'result': { 'id': str(job.result), 'name': str(job.result).title(), }, 'url': job.url, 'label': job.label, } for job in job_list], 'job_failure_count': sum((1 for job in job_list if job.result == Result.failed)), 'date_created': build.date_created, 'failing_tests': [{ 'name': test.name } for test in failing_tests], 'failing_tests_count': failing_tests_count, 'style_violations': [{ 'message': violation.message, 'filename': violation.filename } for violation in style_violations], 'style_violations_count': style_violations_count, } msg = Message(subject, recipients=recipients, extra_headers={ 'Reply-To': ', '.join(sanitize_address(r) for r in recipients), }) msg.body = render_template('notifications/email.txt', **context) msg.html = inline_css( render_template('notifications/email.html', **context)) return msg
def build_message(build: Build, force=False) -> Message: author = Author.query.join(Source, Source.author_id == Author.id).filter( Source.id == build.source_id ).first() if not author: current_app.logger.info("mail.missing-author", extra={"build_id": build.id}) return emails = find_linked_emails(build) if not emails and not force: current_app.logger.info("mail.no-linked-accounts", extra={"build_id": build.id}) return elif not emails: current_user = auth.get_current_user() emails = [[current_user.id, current_user.email]] # filter it down to the users that have notifications enabled user_options = dict( db.session.query(ItemOption.item_id, ItemOption.value).filter( ItemOption.item_id.in_([uid for uid, _ in emails]), ItemOption.name == "mail.notify_author", ) ) emails = [r for r in emails if user_options.get(r[0], "1") == "1"] if not emails: current_app.logger.info("mail.no-enabed-accounts", extra={"build_id": build.id}) return source = Source.query.get(build.source_id) assert source repo = Repository.query.get(build.repository_id) assert repo revision = Revision.query.filter( Revision.sha == source.revision_sha, Revision.repository_id == build.repository_id, ).first() assert revision job_list = sorted( Job.query.filter(Job.build_id == build.id), key=lambda x: [x.result != Result.failed, x.number], ) job_ids = [j.id for j in job_list] recipients = [r[1] for r in emails] subject = "Build {} - {}/{} #{}".format( str(build.result).title(), repo.owner_name, repo.name, build.number ) if job_ids: failing_tests_query = TestCase.query.options(undefer("message")).filter( TestCase.job_id.in_(job_ids), TestCase.result == Result.failed ) failing_tests_count = failing_tests_query.count() failing_tests = failing_tests_query.limit(10) style_violations_query = StyleViolation.query.filter( StyleViolation.job_id.in_(job_ids) ).order_by( (StyleViolation.severity == Severity.error).desc(), StyleViolation.filename.asc(), StyleViolation.lineno.asc(), StyleViolation.colno.asc(), ) style_violations_count = style_violations_query.count() style_violations = style_violations_query.limit(10) else: failing_tests = () failing_tests_count = 0 style_violations = () style_violations_count = 0 context = { "title": subject, "uri": "{proto}://{domain}/{repo}/builds/{build_no}".format( proto="https" if current_app.config["SSL"] else "http", domain=current_app.config["DOMAIN"], repo=repo.get_full_name(), build_no=build.number, ), "build": { "number": build.number, "result": {"id": str(build.result), "name": str(build.result).title()}, "label": build.label, }, "repo": { "owner_name": repo.owner_name, "name": repo.name, "full_name": repo.get_full_name, }, "author": {"name": author.name, "email": author.email}, "revision": { "sha": revision.sha, "short_sha": revision.sha[:7], "message": revision.message, }, "job_list": [ { "number": job.number, "result": {"id": str(job.result), "name": str(job.result).title()}, "url": job.url, "label": job.label, } for job in job_list ], "job_failure_count": sum( (1 for job in job_list if job.result == Result.failed) ), "date_created": build.date_created, "failing_tests": [{"name": test.name} for test in failing_tests], "failing_tests_count": failing_tests_count, "style_violations": [ {"message": violation.message, "filename": violation.filename} for violation in style_violations ], "style_violations_count": style_violations_count, } msg = Message( subject, recipients=recipients, extra_headers={"Reply-To": ", ".join(sanitize_address(r) for r in recipients)}, ) msg.body = render_template("notifications/email.txt", **context) msg.html = inline_css(render_template("notifications/email.html", **context)) return msg