예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
0
    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)
예제 #6
0
 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)
예제 #7
0
    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)
예제 #8
0
 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)
예제 #9
0
    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]
예제 #10
0
    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
예제 #11
0
    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]
예제 #12
0
    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)
예제 #13
0
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()
예제 #14
0
    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)
예제 #15
0
    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)
예제 #16
0
    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)
예제 #17
0
    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)
예제 #18
0
    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)
예제 #19
0
    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]
예제 #20
0
    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)
예제 #21
0
    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)
예제 #22
0
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()
예제 #23
0
    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)
예제 #24
0
    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]
예제 #25
0
    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)
예제 #26
0
    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)
예제 #27
0
 def authenticate(self):
     user = auth.get_current_user()
     if not user:
         return None
     return auth.Tenant.from_user(user)
예제 #28
0
    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)
예제 #29
0
파일: email.py 프로젝트: keegancsmith/zeus
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
예제 #30
0
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