Esempio n. 1
0
def add_release(body):
    # we must require scope which depends on XPI type
    xpi_type = _xpi_type(body["revision"], body["xpi_name"])
    required_permission = f"{SCOPE_PREFIX}/add_release/xpi/{xpi_type}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(401, f"required permission: {required_permission}, user permissions: {user_permissions}")
    session = current_app.db.session
    xpi = XPI(name=body["xpi_name"], revision=body["xpi_revision"], version=body["xpi_version"])
    # project name used in the TC routes and matches the repo name
    project = current_app.config.get("XPI_MANIFEST_REPO")
    release = XPIRelease(revision=body["revision"], xpi=xpi, build_number=body["build_number"], status="scheduled", xpi_type=xpi_type, project=project)
    try:
        common_input = {"build_number": release.build_number, "xpi_name": release.xpi_name, "revision": release.xpi_revision, "version": release.xpi_version}
        release.phases = generate_phases(release, common_input, verify_supported_flavors=False)
        session.add(release)
        session.commit()
    except UnsupportedFlavor as e:
        raise BadRequest(description=e.description)
    except (IntegrityError, TaskclusterRestFailure) as e:
        # Report back Taskcluster and SQL failures for better visibility of the
        # actual issue. Usually it happens when we cannot find the indexed
        # task or a duplicate release request accordingly.
        abort(400, str(e))

    logger.info("New release of %s", release.name)
    notify_via_matrix("xpi", f"New release of {release.name}")
    return release.json, 201
Esempio n. 2
0
def abandon_release(name):
    session = current_app.db.session
    try:
        release = session.query(Release).filter(Release.name == name).one()

        # we must require scope which depends on product
        required_permission = f"{SCOPE_PREFIX}/abandon_release/{release.product}"
        if not current_user.has_permissions(required_permission):
            user_permissions = ", ".join(current_user.get_permissions())
            abort(
                401,
                f"required permission: {required_permission}, user permissions: {user_permissions}"
            )

        # Cancel all submitted task groups first
        for phase in filter(lambda x: x.submitted and not x.skipped,
                            release.phases):
            try:
                actions = fetch_artifact(phase.task_id, "public/actions.json")
                parameters = fetch_artifact(phase.task_id,
                                            "public/parameters.yml")
            except ArtifactNotFound:
                logger.info("Ignoring not completed action task %s",
                            phase.task_id)
                continue

            hook = generate_action_hook(task_group_id=phase.task_id,
                                        action_name="cancel-all",
                                        actions=actions,
                                        parameters=parameters,
                                        input_={})
            hooks = get_service("hooks")
            client_id = hooks.options["credentials"]["clientId"].decode(
                "utf-8")
            hook["context"]["clientId"] = client_id
            hook_payload_rendered = render_action_hook(
                payload=hook["hook_payload"],
                context=hook["context"],
                delete_params=[
                    "existing_tasks", "release_history",
                    "release_partner_config"
                ])
            logger.info("Cancel phase %s by hook %s with payload: %s",
                        phase.name, hook["hook_id"], hook_payload_rendered)
            res = hooks.triggerHook(hook["hook_group_id"], hook["hook_id"],
                                    hook_payload_rendered)
            logger.debug("Done: %s", res)

        release.status = "aborted"
        session.commit()
        release_json = release.json
    except NoResultFound:
        abort(404)

    notify_via_irc(
        release.product,
        f"Release {release.product} {release.version} build{release.build_number} was just canceled."
    )

    return release_json
Esempio n. 3
0
def add_release(body):
    # we must require scope which depends on product
    required_permission = f'{SCOPE_PREFIX}/add_release/{body["product"]}'
    if not current_user.has_permissions(required_permission):
        user_permissions = ', '.join(current_user.get_permissions())
        abort(401, f'required permission: {required_permission}, user permissions: {user_permissions}')

    session = current_app.db.session
    product = body['product']
    r = Release(
        product=product,
        version=body['version'],
        branch=body['branch'],
        revision=body['revision'],
        build_number=body['build_number'],
        release_eta=body.get('release_eta'),
        status='scheduled',
        partial_updates=body.get('partial_updates'),
        product_key=body.get('product_key'),
    )
    try:
        r.generate_phases(
            partner_urls=current_app.config.get('PARTNERS_URL'),
            github_token=current_app.config.get('GITHUB_TOKEN'),
        )
        session.add(r)
        session.commit()
        release = r.json
    except UnsupportedFlavor as e:
        raise BadRequest(description=e.description)

    notify_via_irc(product, f'New release ({product} {r.version} build{r.build_number}) was just created.')

    return release, 201
Esempio n. 4
0
def schedule_phase(name, phase):
    session = current_app.db.session
    phase = session.query(XPIPhase).filter(
        XPIRelease.id == XPIPhase.release_id).filter(
            XPIRelease.name == name).filter(
                XPIPhase.name == phase).first_or_404()

    # we must require scope which depends on XPI type
    xpi_type = _xpi_type(phase.release.revision, phase.release.xpi_name)
    required_permission = f"{SCOPE_PREFIX}/schedule_phase/xpi/{xpi_type}/{phase.name}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    phase = do_schedule_phase(session, phase)
    url = taskcluster_urls.ui(get_root_url(), f"/tasks/groups/{phase.task_id}")
    logger.info("Phase %s of %s started by %s. - %s", phase.name,
                phase.release.name, phase.completed_by, url)
    notify_via_matrix(
        "xpi",
        f"Phase {phase.name} of {phase.release.name} started by {phase.completed_by}. - {url}"
    )
    return phase.json
Esempio n. 5
0
def abandon_release(name):
    session = current_app.db.session
    try:
        release = session.query(Release).filter(Release.name == name).one()

        # we must require scope which depends on product
        required_permission = f'{SCOPE_PREFIX}/abandon_release/{release.product}'
        if not current_user.has_permissions(required_permission):
            user_permissions = ', '.join(current_user.get_permissions())
            abort(
                401,
                f'required permission: {required_permission}, user permissions: {user_permissions}'
            )

        # Cancel all submitted task groups first
        for phase in filter(lambda x: x.submitted, release.phases):
            try:
                actions = fetch_actions_json(phase.task_id)
            except ActionsJsonNotFound:
                logger.info('Ignoring not completed action task %s',
                            phase.task_id)
                continue

            hook = generate_action_hook(
                task_group_id=phase.task_id,
                action_name='cancel-all',
                actions=actions,
                input_={},
            )
            hooks = get_service('hooks')
            client_id = hooks.options['credentials']['clientId'].decode(
                'utf-8')
            hook['context']['clientId'] = client_id
            hook_payload_rendered = render_action_hook(
                payload=hook['hook_payload'],
                context=hook['context'],
                delete_params=[
                    'existing_tasks', 'release_history',
                    'release_partner_config'
                ],
            )
            logger.info('Cancel phase %s by hook %s with payload: %s',
                        phase.name, hook['hook_id'], hook_payload_rendered)
            res = hooks.triggerHook(hook['hook_group_id'], hook['hook_id'],
                                    hook_payload_rendered)
            logger.debug('Done: %s', res)

        release.status = 'aborted'
        session.commit()
        release_json = release.json
    except NoResultFound:
        abort(404)

    notify_via_irc(
        release.product,
        f'Release {release.product} {release.version} build{release.build_number} was just canceled.'
    )

    return release_json
Esempio n. 6
0
File: api.py Progetto: tp-tc/shipit
def schedule_phase(name, phase):
    session = current_app.db.session
    try:
        phase = session.query(Phase).filter(
            Release.id == Phase.release_id).filter(
                Release.name == name).filter(Phase.name == phase).one()
    except NoResultFound:
        abort(404)

    # we must require scope which depends on product
    required_permission = f"{SCOPE_PREFIX}/schedule_phase/{phase.release.product}/{phase.name}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    if phase.submitted:
        abort(409, "Already submitted!")

    for signoff in phase.signoffs:
        if not signoff.signed:
            abort(400, "Pending signoffs")

    hook = phase.task_json

    if "hook_payload" not in hook:
        raise ValueError("Action tasks are not supported")

    hooks = get_service("hooks")
    client_id = hooks.options["credentials"]["clientId"].decode("utf-8")
    extra_context = {"clientId": client_id}
    result = hooks.triggerHook(
        hook["hook_group_id"], hook["hook_id"],
        phase.rendered_hook_payload(extra_context=extra_context))
    phase.task_id = result["status"]["taskId"]

    phase.submitted = True
    phase.completed_by = current_user.get_id()
    completed = datetime.datetime.utcnow()
    phase.completed = completed
    if all([ph.submitted for ph in phase.release.phases]):
        phase.release.status = "shipped"
        phase.release.completed = completed
    session.commit()

    root_url = hooks.options["rootUrl"]
    url = f"{root_url}/tasks/groups/{phase.task_id}"
    # TODO: Remove the condition after migration
    if root_url == "https://taskcluster.net":
        url = "https://tools.taskcluster.net/groups/{phase.task_id}"

    notify_via_irc(
        phase.release.product,
        f"Phase {phase.name} was just scheduled for release {phase.release.product} {phase.release.version} build{phase.release.build_number} - {url}",
    )

    return phase.json
Esempio n. 7
0
def abandon_release(name):
    session = current_app.db.session
    release = session.query(Release).filter(
        Release.name == name).first_or_404()

    # we must require scope which depends on product
    required_permission = f"{SCOPE_PREFIX}/abandon_release/{release.product}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    # Cancel all submitted task groups first
    for phase in filter(lambda x: x.submitted and not x.skipped,
                        release.phases):
        try:
            actions = get_actions(phase.task_id)
            parameters = get_parameters(phase.task_id)
            cancel_action = find_action("cancel-all", actions)
            if not cancel_action:
                logger.info(
                    "%s %s does not have `cancel-all` action, skipping...",
                    release.name, phase.name)
                continue
        except ArtifactNotFound:
            logger.info("Ignoring not completed action task %s", phase.task_id)
            continue

        hook = generate_action_hook(task_group_id=phase.task_id,
                                    action_name="cancel-all",
                                    actions=actions,
                                    parameters=parameters,
                                    input_={})
        hooks = get_service("hooks")
        client_id = hooks.options["credentials"]["clientId"].decode("utf-8")
        hook["context"]["clientId"] = client_id
        hook_payload_rendered = render_action_hook(
            payload=hook["hook_payload"],
            context=hook["context"],
            delete_params=[
                "existing_tasks", "release_history", "release_partner_config"
            ])
        logger.info("Cancel phase %s by hook %s with payload: %s", phase.name,
                    hook["hook_id"], hook_payload_rendered)
        try:
            result = hooks.triggerHook(hook["hook_group_id"], hook["hook_id"],
                                       hook_payload_rendered)
            logger.debug("Done: %s", result)
        except TaskclusterRestFailure as e:
            abort(400, str(e))

    release.status = "aborted"
    session.commit()
    logger.info("Canceled release %s", release.name)
    notify_via_matrix(release.product,
                      f"Release {release.name} was just canceled.")
    return release.json
Esempio n. 8
0
def _require_auth():
    required_permission = f"{SCOPE_PREFIX}/github"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )
Esempio n. 9
0
def schedule_phase(name, phase):
    session = current_app.db.session
    try:
        phase = session.query(Phase) \
            .filter(Release.id == Phase.release_id) \
            .filter(Release.name == name) \
            .filter(Phase.name == phase).one()
    except NoResultFound:
        abort(404)

    # we must require scope which depends on product
    required_permission = f'{SCOPE_PREFIX}/schedule_phase/{phase.release.product}/{phase.name}'
    if not current_user.has_permissions(required_permission):
        user_permissions = ', '.join(current_user.get_permissions())
        abort(
            401,
            f'required permission: {required_permission}, user permissions: {user_permissions}'
        )

    if phase.submitted:
        abort(409, 'Already submitted!')

    for signoff in phase.signoffs:
        if not signoff.signed:
            abort(400, 'Pending signoffs')

    hook = phase.task_json

    if 'hook_payload' not in hook:
        raise ValueError('Action tasks are not supported')

    hooks = get_service('hooks')
    client_id = hooks.options['credentials']['clientId'].decode('utf-8')
    extra_context = {'clientId': client_id}
    result = hooks.triggerHook(
        hook['hook_group_id'],
        hook['hook_id'],
        phase.rendered_hook_payload(extra_context=extra_context),
    )
    phase.task_id = result['status']['taskId']

    phase.submitted = True
    phase.completed_by = current_user.get_id()
    completed = datetime.datetime.utcnow()
    phase.completed = completed
    if all([ph.submitted for ph in phase.release.phases]):
        phase.release.status = 'shipped'
        phase.release.completed = completed
    session.commit()

    notify_via_irc(
        phase.release.product, f'Phase {phase.name} was just scheduled '
        f'for release {phase.release.product} {phase.release.version} '
        f'build{phase.release.build_number} - '
        f'(https://tools.taskcluster.net/groups/{phase.task_id})')

    return phase.json
Esempio n. 10
0
def phase_signoff(name, phase, body):
    session = current_app.db.session
    signoff = session.query(XPISignoff).filter(
        XPISignoff.uid == body).first_or_404()

    if signoff.signed:
        abort(409, "Already signed off")

    phase_obj = (session.query(XPIPhase).filter(
        XPIRelease.id == XPIPhase.release_id).filter(
            XPIRelease.name == name).filter(
                XPIPhase.name == phase).first_or_404())
    # we must require scope which depends on product and phase name
    xpi_type = _xpi_type(phase_obj.release.revision,
                         phase_obj.release.xpi_name)
    required_permission = f"{SCOPE_PREFIX}/phase_signoff/xpi/{xpi_type}/{signoff.phase.name}"
    if not current_user.has_permissions(
            required_permission) and not XPI_LAX_SIGN_OFF:
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    # Prevent the same user signing off for multiple signoffs
    users_ldap = current_user.get_ldap_groups()
    users_email = current_user.get_id()
    if users_email in [s.completed_by
                       for s in phase_obj.signoffs] and not XPI_LAX_SIGN_OFF:
        abort(409, f"Already signed off by {users_email}")

    # signoff.permissions corresponds to the group in settings.py
    ldap_groups = current_app.config.get("LDAP_GROUPS", {})
    ldap_group = ldap_groups.get(signoff.permissions, [])
    if not set(users_ldap).intersection(
            set(ldap_group)) and not XPI_LAX_SIGN_OFF:
        abort(401,
              f"User `{users_email}` is not in the `{signoff.permissions}`")

    signoff.completed = datetime.datetime.utcnow()
    signoff.signed = True
    signoff.completed_by = users_email

    session.commit()
    signoffs = [s.json for s in phase_obj.signoffs]

    # Schedule the phase when all signoffs are done
    if all([s.signed for s in phase_obj.signoffs]):
        schedule_phase(name, phase)

    release = phase_obj.release
    logger.info("Phase %s of %s signed off by %s", phase, release.name,
                users_email)
    notify_via_matrix(
        "xpi", f"Phase {phase} of {release.name} signed off by {users_email}")

    return dict(signoffs=signoffs)
Esempio n. 11
0
    def _require_scopes(self, scopes):
        response = self._require_login()
        if response is not None:
            return response

        with current_app.app_context():
            user_scopes = current_user.get_permissions()
            if not scope_match(user_scopes, scopes):
                diffs = [', '.join(set(s).difference(user_scopes)) for s in scopes]  # noqa
                logger.error('User {} misses some scopes: {}'.format(current_user.get_id(), ' OR '.join(diffs)))  # noqa
                return abort(401)
Esempio n. 12
0
def add_release(body):
    # we must require scope which depends on product
    required_permission = f'{SCOPE_PREFIX}/add_release/{body["product"]}'
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    product = body["product"]
    branch = body["branch"]

    product_disabled = branch in get_disabled_products().get(product, [])
    if current_user.type == AuthType.TASKCLUSTER and product_disabled:
        abort(401, "Taskcluster based submissions are disabled")

    session = current_app.db.session
    partial_updates = body.get("partial_updates")
    if partial_updates == "auto":
        if product not in [
                Product.FIREFOX.value, Product.DEVEDITION.value
        ] or branch not in ["try", "releases/mozilla-beta", "projects/maple"]:
            abort(400, "Partial suggestion works for automated betas only")

        partial_updates = _suggest_partials(product=product, branch=branch)
    release = Release(
        branch=branch,
        build_number=body["build_number"],
        partial_updates=partial_updates,
        product_key=body.get("product_key"),
        product=product,
        release_eta=body.get("release_eta"),
        repo_url=body.get("repo_url", ""),
        revision=body["revision"],
        status="scheduled",
        version=body["version"],
    )
    try:
        release.generate_phases()
        session.add(release)
        session.commit()
    except UnsupportedFlavor as e:
        raise BadRequest(description=e.description)
    except TaskclusterRestFailure as e:
        # Report back Taskcluster failure for better visibility of the actual
        # issue. Usually it happens when we cannot find the indexed task.
        abort(400, str(e))

    logger.info("New release of %s", release.name)
    notify_via_irc(product, f"New release of {release.name}")

    return release.json, 201
Esempio n. 13
0
def add_release(body):
    # we must require scope which depends on product
    required_permission = f'{SCOPE_PREFIX}/add_release/{body["product"]}'
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    product = body["product"]
    branch = body["branch"]

    product_disabled = branch in get_disabled_products().get(product, [])
    if current_user.type == AuthType.TASKCLUSTER and product_disabled:
        abort(401, "Taskcluster based submissions are disabled")

    session = current_app.db.session
    partial_updates = body.get("partial_updates")
    if partial_updates == "auto":
        if product not in [
                Product.FIREFOX.value, Product.DEVEDITION.value
        ] or branch not in ["try", "releases/mozilla-beta", "projects/maple"]:
            abort(400, "Partial suggestion works for automated betas only")

        partial_updates = _suggest_partials(product=product, branch=branch)
    r = Release(
        product=product,
        version=body["version"],
        branch=branch,
        revision=body["revision"],
        build_number=body["build_number"],
        release_eta=body.get("release_eta"),
        status="scheduled",
        partial_updates=partial_updates,
        product_key=body.get("product_key"),
    )
    try:
        r.generate_phases(partner_urls=current_app.config.get("PARTNERS_URL"),
                          github_token=current_app.config.get("GITHUB_TOKEN"))
        session.add(r)
        session.commit()
        release = r.json
    except UnsupportedFlavor as e:
        raise BadRequest(description=e.description)

    notify_via_irc(
        product,
        f"New release ({product} {r.version} build{r.build_number}) was just created."
    )

    return release, 201
Esempio n. 14
0
def phase_signoff(name, phase, uid):
    session = current_app.db.session
    try:
        signoff = session.query(Signoff) \
            .filter(Signoff.uid == uid).one()
    except NoResultFound:
        abort(404, 'Sign off does not exist')

    if signoff.signed:
        abort(409, 'Already signed off')

    # we must require scope which depends on product and phase name
    required_permission = f'{SCOPE_PREFIX}/phase_signoff/{signoff.phase.release.product}/{signoff.phase.name}'
    if not current_user.has_permissions(required_permission):
        user_permissions = ', '.join(current_user.get_permissions())
        abort(
            401,
            f'required permission: {required_permission}, user permissions: {user_permissions}'
        )

    try:
        # Prevent the same user signing off for multiple signoffs
        phase_obj = session.query(Phase) \
            .filter(Release.id == Phase.release_id) \
            .filter(Release.name == name) \
            .filter(Phase.name == phase).one()
    except NoResultFound:
        abort(404, 'Phase not found')

    who = current_user.get_id()
    if who in [s.completed_by for s in phase_obj.signoffs]:
        abort(409, f'Already signed off by {who}')

    signoff.completed = datetime.datetime.utcnow()
    signoff.signed = True
    signoff.completed_by = who

    session.commit()
    signoffs = [s.json for s in phase_obj.signoffs]

    # Schedule the phase when all signoffs are done
    if all([s.signed for s in phase_obj.signoffs]):
        schedule_phase(name, phase)

    r = phase_obj.release
    notify_via_irc(
        r.product,
        f'{phase} of {r.product} {r.version} build{r.build_number} signed off by {who}.'
    )

    return dict(signoffs=signoffs)
Esempio n. 15
0
def abandon_release_xpi(name):
    session = current_app.db.session
    release = session.query(XPIRelease).filter(XPIRelease.name == name).first_or_404()
    # we must require scope which depends on XPI type
    xpi_type = _xpi_type(release.revision, release.xpi_name)
    required_permission = f"{SCOPE_PREFIX}/abandon_release/xpi/{xpi_type}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(401, f"required permission: {required_permission}, user permissions: {user_permissions}")

    # XPI doesn't support the "cancel-all" action task, just mark as aborted
    release.status = "aborted"
    session.commit()
    logger.info("Canceled release %s", release.name)
    return release.json
Esempio n. 16
0
def phase_signoff(name, phase, body):
    session = current_app.db.session
    signoff = session.query(Signoff).filter(Signoff.uid == body).first_or_404()

    if signoff.signed:
        abort(409, "Already signed off")

    # we must require scope which depends on product and phase name
    required_permission = f"{SCOPE_PREFIX}/phase_signoff/{signoff.phase.release.product}/{signoff.phase.name}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    # Prevent the same user signing off for multiple signoffs
    phase_obj = session.query(Phase).filter(
        Release.id == Phase.release_id).filter(Release.name == name).filter(
            Phase.name == phase).first_or_404()

    who = current_user.get_id()
    if who in [s.completed_by for s in phase_obj.signoffs]:
        abort(409, f"Already signed off by {who}")

    # signoff.permissions corresponds to the group in settings.py
    groups = current_app.config.get("GROUPS", {})
    group = groups.get(signoff.permissions, [])
    if who not in group:
        abort(401, f"User `{who}` is not in the `{signoff.permissions}` group")

    signoff.completed = datetime.datetime.utcnow()
    signoff.signed = True
    signoff.completed_by = who

    session.commit()
    signoffs = [s.json for s in phase_obj.signoffs]

    # Schedule the phase when all signoffs are done
    if all([s.signed for s in phase_obj.signoffs]):
        schedule_phase(name, phase)

    release = phase_obj.release
    logger.info("Phase %s of %s signed off by %s", phase, release.name, who)
    notify_via_matrix(release.product,
                      f"{phase} of {release.name} signed off by {who}.")

    return dict(signoffs=signoffs)
Esempio n. 17
0
def enable_product(product, branch):
    session = current_app.db.session

    required_permission = f"{SCOPE_PREFIX}/enable_product/{product}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    dp = session.query(DisabledProduct).filter(
        DisabledProduct.product == product).filter(
            DisabledProduct.branch == branch).first_or_404()
    session.delete(dp)
    session.commit()
    logger.info("Enabled %s on branch %s", product, branch)
    return 200
Esempio n. 18
0
    def _authenticate(self):
        app = self.get_app()
        assert app, "Please initialize your application into Flask-RBAC."
        assert self._role_model, "Please set role model before authenticate."
        assert self._user_model, "Please set user model before authenticate."
        assert self._user_loader, "Please set user loader before authenticate."

        current_user = self._user_loader()

        # Compatible with flask-login anonymous user
        if hasattr(current_user, '_get_current_object'):
            current_user = current_user._get_current_object()

        if (current_user is not None
                and not isinstance(current_user,
                                   (self._user_model, anonymous_model))):
            raise TypeError("%s is not an instance of %s" %
                            (current_user, self._user_model.__class__))

        endpoint = request.endpoint
        resource = app.view_functions.get(endpoint, None)

        if not resource:
            abort(404)

        method = request.method

        if not self._is_user_permission:
            if not hasattr(current_user, 'get_roles'):
                roles = [anonymous]
            else:
                roles = current_user.get_roles()

            permit = self._check_permission(roles, method, resource)
        else:  # permission
            if not hasattr(current_user, 'get_permissions'):
                permissions = []
            else:
                permissions = current_user.get_permissions()

            permit = self._check_permission2(permissions, endpoint)

        if not permit:
            return self._deny_hook()
Esempio n. 19
0
def disable_product(body):
    product = body["product"]
    branch = body["branch"]

    required_permission = f"{SCOPE_PREFIX}/disable_product/{product}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    session = current_app.db.session

    dp = DisabledProduct(product=product, branch=branch)
    session.add(dp)
    session.commit()

    return 200
Esempio n. 20
0
def enable_product(product, branch):
    session = current_app.db.session

    required_permission = f"{SCOPE_PREFIX}/enable_product/{product}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    try:
        dp = session.query(DisabledProduct).filter(
            DisabledProduct.product == product).filter(
                DisabledProduct.branch == branch).one()
        session.delete(dp)
        session.commit()
        return 200
    except NoResultFound:
        abort(404)
Esempio n. 21
0
def add_release(body):
    # we must require scope which depends on XPI type
    xpi_type = _xpi_type(body["revision"], body["xpi_name"])
    required_permission = f"{SCOPE_PREFIX}/add_release/xpi/{xpi_type}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(401, f"required permission: {required_permission}, user permissions: {user_permissions}")
    session = current_app.db.session
    xpi = XPI(name=body["xpi_name"], revision=body["xpi_revision"], version=body["xpi_version"])
    # project name used in the TC routes and matches the repo name
    project = current_app.config.get("XPI_MANIFEST_REPO")
    release = XPIRelease(revision=body["revision"], xpi=xpi, build_number=body["build_number"], status="scheduled", xpi_type=xpi_type, project=project)
    try:
        release.generate_phases()
        session.add(release)
        session.commit()
    except UnsupportedFlavor as e:
        raise BadRequest(description=e.description)

    logger.info("New release of %s", release.name)
    return release.json, 201
Esempio n. 22
0
def disable_product(body):
    product = body["product"]
    branch = body["branch"]

    required_permission = f"{SCOPE_PREFIX}/disable_product/{product}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    session = current_app.db.session

    dp = DisabledProduct(product=product, branch=branch)
    session.add(dp)
    session.commit()
    logger.info("Disabled %s on branch %s", product, branch)
    notify_via_matrix(
        product, f"Automatic releases disabled for {product} on {branch}")

    return 200
Esempio n. 23
0
def schedule_phase(name, phase):
    session = current_app.db.session
    phases = session.query(XPIPhase).filter(
        XPIRelease.id == XPIPhase.release_id).filter(
            XPIRelease.name == name).all()
    phase_to_schedule = list(
        filter(lambda _phase: _phase.name == phase, phases))

    # Get email for all signoffs from previous phases and phase scheduler
    additional_shipit_emails = get_signoff_emails(phases)

    if not phase_to_schedule:
        abort(404, f"phase {phase} not found")

    phase_to_schedule = phase_to_schedule[0]
    # we must require scope which depends on XPI type
    xpi_type = _xpi_type(phase_to_schedule.release.revision,
                         phase_to_schedule.release.xpi_name)
    required_permission = f"{SCOPE_PREFIX}/schedule_phase/xpi/{xpi_type}/{phase_to_schedule.name}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    scheduled_phase = do_schedule_phase(session, phase_to_schedule,
                                        additional_shipit_emails)
    url = taskcluster_urls.ui(get_root_url(),
                              f"/tasks/groups/{scheduled_phase.task_id}")
    logger.info("Phase %s of %s started by %s. - %s", scheduled_phase.name,
                scheduled_phase.release.name, scheduled_phase.completed_by,
                url)
    notify_via_matrix(
        "xpi",
        f"Phase {scheduled_phase.name} of {scheduled_phase.release.name} started by {scheduled_phase.completed_by}. - {url}"
    )
    return scheduled_phase.json
Esempio n. 24
0
def phase_signoff(name, phase, body):
    session = current_app.db.session
    signoff = session.query(XPISignoff).filter(XPISignoff.uid == body).first_or_404()

    if signoff.signed:
        abort(409, "Already signed off")

    phase_obj = (
        session.query(XPIPhase).filter(XPIRelease.id == XPIPhase.release_id).filter(XPIRelease.name == name).filter(XPIPhase.name == phase).first_or_404()
    )
    # we must require scope which depends on product and phase name
    xpi_type = _xpi_type(phase_obj.release.revision, phase_obj.release.xpi_name)
    required_permission = f"{SCOPE_PREFIX}/phase_signoff/xpi/{xpi_type}/{signoff.phase.name}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(401, f"required permission: {required_permission}, user permissions: {user_permissions}")

    # Prevent the same user signing off for multiple signoffs
    who = current_user.get_id()
    if who in [s.completed_by for s in phase_obj.signoffs]:
        abort(409, f"Already signed off by {who}")

    signoff.completed = datetime.datetime.utcnow()
    signoff.signed = True
    signoff.completed_by = who

    session.commit()
    signoffs = [s.json for s in phase_obj.signoffs]

    # Schedule the phase when all signoffs are done
    if all([s.signed for s in phase_obj.signoffs]):
        schedule_phase(name, phase)

    release = phase_obj.release
    logger.info("Phase %s of %s signed off by %s", phase, release.name, who)
    return dict(signoffs=signoffs)
Esempio n. 25
0
def schedule_phase(name, phase):
    session = current_app.db.session
    phase = session.query(Phase).filter(Release.id == Phase.release_id).filter(
        Release.name == name).filter(Phase.name == phase).first_or_404()

    # we must require scope which depends on product
    required_permission = f"{SCOPE_PREFIX}/schedule_phase/{phase.release.product}/{phase.name}"
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    phase = do_schedule_phase(session, phase)
    url = taskcluster_urls.ui(get_root_url(), f"/tasks/groups/{phase.task_id}")
    logger.info("Phase %s of %s started by %s. - %s", phase.name,
                phase.release.name, phase.completed_by, url)
    notify_via_matrix(
        phase.release.product,
        f"Phase {phase.name} was just scheduled for {phase.release.name} - {url}"
    )

    return phase.json
Esempio n. 26
0
def add_release(body):
    # we must require scope which depends on product
    required_permission = f'{SCOPE_PREFIX}/add_release/{body["product"]}'
    if not current_user.has_permissions(required_permission):
        user_permissions = ", ".join(current_user.get_permissions())
        abort(
            401,
            f"required permission: {required_permission}, user permissions: {user_permissions}"
        )

    product = body["product"]
    branch = body["branch"]

    product_disabled = branch in get_disabled_products().get(product, [])
    if current_user.type == AuthType.TASKCLUSTER and product_disabled:
        abort(401, "Taskcluster based submissions are disabled")

    session = current_app.db.session
    partial_updates = body.get("partial_updates")
    if partial_updates == "auto":
        if product not in [
                Product.FIREFOX.value, Product.DEVEDITION.value,
                Product.PINEBUILD.value
        ] or branch not in [
                "try",
                "releases/mozilla-beta",
                "projects/maple",
        ]:
            abort(400, "Partial suggestion works for automated betas only")

        partial_updates = _suggest_partials(product=product, branch=branch)
    release = Release(
        branch=branch,
        build_number=body["build_number"],
        partial_updates=partial_updates,
        product_key=body.get("product_key"),
        product=product,
        release_eta=body.get("release_eta"),
        repo_url=body.get("repo_url", ""),
        revision=body["revision"],
        status="scheduled",
        version=body["version"],
    )
    try:
        next_version = bump_version(release.product, release.version)
        common_input = {
            "build_number":
            release.build_number,
            "next_version":
            next_version,
            # specify version rather than relying on in-tree version,
            # so if a version bump happens between the build and an action task
            # revision, we still use the correct version.
            "version":
            release.version,
            "release_eta":
            release.release_eta,
            "release_enable_emefree":
            is_eme_free_enabled(release.product, release.version),
        }
        partners_enabled = is_partner_enabled(release.product, release.version)
        if int(release.version.split(".")[0]) >= 81:
            common_input["release_enable_partner_repack"] = partners_enabled
            common_input[
                "release_enable_partner_attribution"] = partners_enabled
        else:
            common_input["release_enable_partners"] = partners_enabled
        if release.partial_updates:
            common_input["partial_updates"] = release.partial_updates

        release.phases = generate_phases(release,
                                         common_input,
                                         verify_supported_flavors=True)
        session.add(release)
        session.commit()
    except UnsupportedFlavor as e:
        raise BadRequest(description=e.description)
    except (IntegrityError, TaskclusterRestFailure) as e:
        # Report back Taskcluster and SQL failures for better visibility of the
        # actual issue. Usually it happens when we cannot find the indexed
        # task or a duplicate release request accordingly.
        abort(400, str(e))

    logger.info("New release of %s", release.name)
    notify_via_matrix(product, f"New release of {release.name}")

    return release.json, 201