コード例 #1
0
    def post(self, namespace_name, repo_name, trigger_uuid):
        """
        Manually start a build from the specified trigger.
        """
        trigger = get_trigger(trigger_uuid)
        if not trigger.enabled:
            raise InvalidRequest("Trigger is not enabled.")

        handler = BuildTriggerHandler.get_handler(trigger)
        if not handler.is_active():
            raise InvalidRequest("Trigger is not active.")

        try:
            repo = model.repository.get_repository(namespace_name, repo_name)
            pull_robot_name = model.build.get_pull_robot_name(trigger)

            run_parameters = request.get_json()
            prepared = handler.manual_start(run_parameters=run_parameters)
            build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name)
        except TriggerException as tse:
            raise InvalidRequest(tse.message)
        except MaximumBuildsQueuedException:
            abort(429, message="Maximum queued build rate exceeded.")
        except BuildTriggerDisabledException:
            abort(400, message="Build trigger is disabled")

        resp = build_status_view(build_request)
        repo_string = "%s/%s" % (namespace_name, repo_name)
        headers = {
            "Location": api.url_for(
                RepositoryBuildStatus, repository=repo_string, build_uuid=build_request.uuid
            ),
        }
        return resp, 201, headers
コード例 #2
0
    def post(self):
        """
        Create a new repository.
        """
        owner = get_authenticated_user()
        req = request.get_json()

        if owner is None and "namespace" not in "req":
            raise InvalidRequest(
                "Must provide a namespace or must be logged in.")

        namespace_name = req[
            "namespace"] if "namespace" in req else owner.username

        permission = CreateRepositoryPermission(namespace_name)
        if permission.can():
            repository_name = req["repository"]
            visibility = req["visibility"]

            if model.repo_exists(namespace_name, repository_name):
                raise request_error(message="Repository already exists")

            visibility = req["visibility"]
            if visibility == "private":
                check_allowed_private_repos(namespace_name)

            # Verify that the repository name is valid.
            if not REPOSITORY_NAME_REGEX.match(repository_name):
                raise InvalidRequest("Invalid repository name")

            kind = req.get("repo_kind", "image") or "image"
            created = model.create_repo(
                namespace_name,
                repository_name,
                owner,
                req["description"],
                visibility=visibility,
                repo_kind=kind,
            )
            if created is None:
                raise InvalidRequest("Could not create repository")

            log_action(
                "create_repo",
                namespace_name,
                {
                    "repo": repository_name,
                    "namespace": namespace_name
                },
                repo_name=repository_name,
            )
            return {
                "namespace": namespace_name,
                "name": repository_name,
                "kind": kind,
            }, 201

        raise Unauthorized()
コード例 #3
0
ファイル: __init__.py プロジェクト: ynnt/quay
 def wrapped(self, *args, **kwargs):
     schema = self.schemas[schema_name]
     try:
         json_data = request.get_json()
         if json_data is None:
             if not optional:
                 raise InvalidRequest("Missing JSON body")
         else:
             validate(json_data, schema)
         return func(self, *args, **kwargs)
     except ValidationError as ex:
         raise InvalidRequest(str(ex))
コード例 #4
0
    def post(self, namespace, repository, tag):
        """
        Restores a repository tag back to a previous image in the repository.
        """
        repo_ref = registry_model.lookup_repository(namespace, repository)
        if repo_ref is None:
            raise NotFound()

        # Restore the tag back to the previous image.
        image_id = request.get_json().get("image", None)
        manifest_digest = request.get_json().get("manifest_digest", None)

        if image_id is None and manifest_digest is None:
            raise InvalidRequest("Missing manifest_digest")

        # Data for logging the reversion/restoration.
        username = get_authenticated_user().username
        log_data = {
            "username": username,
            "repo": repository,
            "tag": tag,
            "image": image_id,
            "manifest_digest": manifest_digest,
        }

        manifest_or_legacy_image = None
        if manifest_digest is not None:
            manifest_or_legacy_image = registry_model.lookup_manifest_by_digest(
                repo_ref,
                manifest_digest,
                allow_dead=True,
                require_available=True)
        elif image_id is not None:
            manifest_or_legacy_image = registry_model.get_legacy_image(
                repo_ref, image_id)

        if manifest_or_legacy_image is None:
            raise NotFound()

        if not registry_model.retarget_tag(
                repo_ref,
                tag,
                manifest_or_legacy_image,
                storage,
                docker_v2_signing_key,
                is_reversion=True,
        ):
            raise InvalidRequest("Could not restore tag")

        log_action("revert_tag", namespace, log_data, repo_name=repository)

        return {}
コード例 #5
0
ファイル: repository.py プロジェクト: xzwupeng/quay
    def post(self):
        """Create a new repository."""
        owner = get_authenticated_user()
        req = request.get_json()

        if owner is None and 'namespace' not in 'req':
            raise InvalidRequest(
                'Must provide a namespace or must be logged in.')

        namespace_name = req[
            'namespace'] if 'namespace' in req else owner.username

        permission = CreateRepositoryPermission(namespace_name)
        if permission.can():
            repository_name = req['repository']
            visibility = req['visibility']

            if model.repo_exists(namespace_name, repository_name):
                raise request_error(message='Repository already exists')

            visibility = req['visibility']
            if visibility == 'private':
                check_allowed_private_repos(namespace_name)

            # Verify that the repository name is valid.
            if not REPOSITORY_NAME_REGEX.match(repository_name):
                raise InvalidRequest('Invalid repository name')

            kind = req.get('repo_kind', 'image') or 'image'
            model.create_repo(namespace_name,
                              repository_name,
                              owner,
                              req['description'],
                              visibility=visibility,
                              repo_kind=kind)

            log_action('create_repo',
                       namespace_name, {
                           'repo': repository_name,
                           'namespace': namespace_name
                       },
                       repo_name=repository_name)
            return {
                'namespace': namespace_name,
                'name': repository_name,
                'kind': kind,
            }, 201

        raise Unauthorized()
コード例 #6
0
    def put(self, namespace_name, repo_name, trigger_uuid):
        """
        Updates the specified build trigger.
        """
        trigger = get_trigger(trigger_uuid)

        handler = BuildTriggerHandler.get_handler(trigger)
        if not handler.is_active():
            raise InvalidRequest("Cannot update an unactivated trigger")

        enable = request.get_json()["enabled"]
        model.build.toggle_build_trigger(trigger, enable)
        log_action(
            "toggle_repo_trigger",
            namespace_name,
            {
                "repo": repo_name,
                "trigger_id": trigger_uuid,
                "service": trigger.service.name,
                "enabled": enable,
            },
            repo=model.repository.get_repository(namespace_name, repo_name),
        )

        return trigger_view(trigger)
コード例 #7
0
ファイル: repository.py プロジェクト: xzwupeng/quay
    def get(self, page_token, parsed_args):
        """ Fetch the list of repositories visible to the current user under a variety of situations.
    """
        # Ensure that the user requests either filtered by a namespace, only starred repositories,
        # or public repositories. This ensures that the user is not requesting *all* visible repos,
        # which can cause a surge in DB CPU usage.
        if not parsed_args['namespace'] and not parsed_args[
                'starred'] and not parsed_args['public']:
            raise InvalidRequest(
                'namespace, starred or public are required for this API call')

        user = get_authenticated_user()
        username = user.username if user else None
        last_modified = parsed_args['last_modified']
        popularity = parsed_args['popularity']

        if parsed_args['starred'] and not username:
            # No repositories should be returned, as there is no user.
            abort(400)

        repos, next_page_token = model.get_repo_list(
            parsed_args['starred'], user, parsed_args['repo_kind'],
            parsed_args['namespace'], username, parsed_args['public'],
            page_token, last_modified, popularity)

        return {
            'repositories': [repo.to_dict() for repo in repos]
        }, next_page_token
コード例 #8
0
ファイル: __init__.py プロジェクト: ynnt/quay
def request_error(exception=None, **kwargs):
    data = kwargs.copy()
    message = "Request error."
    if exception:
        message = str(exception)

    message = data.pop("message", message)
    raise InvalidRequest(message, data)
コード例 #9
0
    def post(self, namespace_name, repo_name, trigger_uuid):
        """
        List the build sources for the trigger configuration thus far.
        """
        namespace = request.get_json().get("namespace")
        if namespace is None:
            raise InvalidRequest()

        trigger = get_trigger(trigger_uuid)

        user_permission = UserAdminPermission(trigger.connected_user.username)
        if user_permission.can():
            handler = BuildTriggerHandler.get_handler(trigger)

            try:
                return {"sources": handler.list_build_sources_for_namespace(namespace)}
            except TriggerException as rre:
                raise InvalidRequest(rre.message)
        else:
            raise Unauthorized()
コード例 #10
0
ファイル: team.py プロジェクト: ynnt/quay
        def wrapper(self, *args, **kwargs):
            # Team syncing can only be enabled if we have a federated service.
            if features.TEAM_SYNCING and authentication.federated_service:
                orgname = kwargs["orgname"]
                teamname = kwargs["teamname"]
                if model.team.get_team_sync_information(orgname, teamname):
                    if not except_robots or not parse_robot_username(
                            kwargs.get("membername", "")):
                        raise InvalidRequest(
                            "Cannot call this method on an auth-synced team")

            return func(self, *args, **kwargs)
コード例 #11
0
ファイル: build.py プロジェクト: xzwupeng/quay
    def delete(self, namespace, repository, build_uuid):
        """ Cancels a repository build. """
        try:
            build = model.build.get_repository_build(build_uuid)
        except model.build.InvalidRepositoryBuildException:
            raise NotFound()

        if build.repository.name != repository or build.repository.namespace_user.username != namespace:
            raise NotFound()

        if model.build.cancel_repository_build(build, dockerfile_build_queue):
            return 'Okay', 201
        else:
            raise InvalidRequest('Build is currently running or has finished')
コード例 #12
0
ファイル: trigger.py プロジェクト: xzwupeng/quay
  def put(self, namespace_name, repo_name, trigger_uuid):
    """ Updates the specified build trigger. """
    trigger = get_trigger(trigger_uuid)

    handler = BuildTriggerHandler.get_handler(trigger)
    if not handler.is_active():
      raise InvalidRequest('Cannot update an unactivated trigger')

    enable = request.get_json()['enabled']
    model.build.toggle_build_trigger(trigger, enable)
    log_action('toggle_repo_trigger', namespace_name,
                {'repo': repo_name, 'trigger_id': trigger_uuid,
                'service': trigger.service.name, 'enabled': enable},
                repo=model.repository.get_repository(namespace_name, repo_name))
                
    return trigger_view(trigger)
コード例 #13
0
ファイル: trigger.py プロジェクト: sabre1041/quay-1
    def get(self, namespace_name, repo_name, trigger_uuid):
        """
        List the build sources for the trigger configuration thus far.
        """
        trigger = get_trigger(trigger_uuid)

        user_permission = UserAdminPermission(trigger.connected_user.username)
        if user_permission.can():
            handler = BuildTriggerHandler.get_handler(trigger)

            try:
                return {"namespaces": handler.list_build_source_namespaces()}
            except TriggerException as rre:
                raise InvalidRequest(str(rre)) from rre
        else:
            raise Unauthorized()
コード例 #14
0
ファイル: user.py プロジェクト: xzwupeng/quay
    def post(self, service_id):
        """ Generates the auth URL and CSRF token explicitly for OIDC/OAuth-associated login. """
        login_service = oauth_login.get_service(service_id)
        if login_service is None:
            raise InvalidRequest()

        csrf_token = generate_csrf_token(OAUTH_CSRF_TOKEN_NAME)
        kind = request.get_json()['kind']
        redirect_suffix = '' if kind == 'login' else '/' + kind

        try:
            login_scopes = login_service.get_login_scopes()
            auth_url = login_service.get_auth_url(url_scheme_and_hostname,
                                                  redirect_suffix, csrf_token,
                                                  login_scopes)
            return {'auth_url': auth_url}
        except DiscoveryFailureException as dfe:
            logger.exception('Could not discovery OAuth endpoint information')
            raise DownstreamIssue(dfe.message)
コード例 #15
0
ファイル: team.py プロジェクト: ynnt/quay
    def post(self, orgname, teamname):
        if _syncing_setup_allowed(orgname):
            try:
                team = model.team.get_organization_team(orgname, teamname)
            except model.InvalidTeamException:
                raise NotFound()

            config = request.get_json()

            # Ensure that the specified config points to a valid group.
            status, err = authentication.check_group_lookup_args(config)
            if not status:
                raise InvalidRequest("Could not sync to group: %s" % err)

            # Set the team's syncing config.
            model.team.set_team_syncing(team, authentication.federated_service,
                                        config)

            return team_view(orgname, team)

        raise Unauthorized()
コード例 #16
0
ファイル: build.py プロジェクト: sabre1041/quay-1
    def post(self, namespace, repository):
        """
        Request that a repository be built and pushed from the specified input.
        """
        logger.debug("User requested repository initialization.")
        request_json = request.get_json()

        dockerfile_id = request_json.get("file_id", None)
        archive_url = request_json.get("archive_url", None)

        if not dockerfile_id and not archive_url:
            raise InvalidRequest("file_id or archive_url required")

        if archive_url:
            archive_match = None
            try:
                archive_match = urlparse(archive_url)
            except ValueError:
                pass

            if not archive_match:
                raise InvalidRequest(
                    "Invalid Archive URL: Must be a valid URI")

            scheme = archive_match.scheme
            if scheme != "http" and scheme != "https":
                raise InvalidRequest(
                    "Invalid Archive URL: Must be http or https")

        context, subdir = self.get_dockerfile_context(request_json)
        tags = request_json.get("docker_tags", ["latest"])
        pull_robot_name = request_json.get("pull_robot", None)

        # Verify the security behind the pull robot.
        if pull_robot_name:
            result = parse_robot_username(pull_robot_name)
            if result:
                try:
                    model.user.lookup_robot(pull_robot_name)
                except model.InvalidRobotException:
                    raise NotFound()

                # Make sure the user has administer permissions for the robot's namespace.
                (robot_namespace, _) = result
                if not AdministerOrganizationPermission(robot_namespace).can():
                    raise Unauthorized()
            else:
                raise Unauthorized()

        # Check if the dockerfile resource has already been used. If so, then it
        # can only be reused if the user has access to the repository in which the
        # dockerfile was previously built.
        if dockerfile_id:
            associated_repository = model.build.get_repository_for_resource(
                dockerfile_id)
            if associated_repository:
                if not ModifyRepositoryPermission(
                        associated_repository.namespace_user.username,
                        associated_repository.name):
                    raise Unauthorized()

        # Start the build.
        repo = model.repository.get_repository(namespace, repository)
        if repo is None:
            raise NotFound()

        try:
            build_name = (user_files.get_file_checksum(dockerfile_id)
                          if dockerfile_id else hashlib.sha224(
                              archive_url.encode("ascii")).hexdigest()[0:7])
        except IOError:
            raise InvalidRequest("File %s could not be found or is invalid" %
                                 dockerfile_id)

        prepared = PreparedBuild()
        prepared.build_name = build_name
        prepared.dockerfile_id = dockerfile_id
        prepared.archive_url = archive_url
        prepared.tags = tags
        prepared.subdirectory = subdir
        prepared.context = context
        prepared.is_manual = True
        prepared.metadata = {}
        try:
            build_request = start_build(repo,
                                        prepared,
                                        pull_robot_name=pull_robot_name)
        except MaximumBuildsQueuedException:
            abort(429, message="Maximum queued build rate exceeded.")
        except BuildTriggerDisabledException:
            abort(400, message="Build trigger is disabled")

        resp = build_status_view(build_request)
        repo_string = "%s/%s" % (namespace, repository)
        headers = {
            "Location":
            api.url_for(RepositoryBuildStatus,
                        repository=repo_string,
                        build_uuid=build_request.uuid),
        }
        return resp, 201, headers
コード例 #17
0
    def put(self, namespace, repository, tag):
        """
        Change which image a tag points to or create a new tag.
        """
        if not TAG_REGEX.match(tag):
            abort(400, TAG_ERROR)

        repo_ref = registry_model.lookup_repository(namespace, repository)
        if repo_ref is None:
            raise NotFound()

        if "expiration" in request.get_json():
            tag_ref = registry_model.get_repo_tag(repo_ref, tag)
            if tag_ref is None:
                raise NotFound()

            expiration = request.get_json().get("expiration")
            expiration_date = None
            if expiration is not None:
                try:
                    expiration_date = datetime.utcfromtimestamp(
                        float(expiration))
                except ValueError:
                    abort(400)

                if expiration_date <= datetime.now():
                    abort(400)

            existing_end_ts, ok = registry_model.change_repository_tag_expiration(
                tag_ref, expiration_date)
            if ok:
                if not (existing_end_ts is None and expiration_date is None):
                    log_action(
                        "change_tag_expiration",
                        namespace,
                        {
                            "username": get_authenticated_user().username,
                            "repo": repository,
                            "tag": tag,
                            "namespace": namespace,
                            "expiration_date": expiration_date,
                            "old_expiration_date": existing_end_ts,
                        },
                        repo_name=repository,
                    )
            else:
                raise InvalidRequest(
                    "Could not update tag expiration; Tag has probably changed"
                )

        if "image" in request.get_json(
        ) or "manifest_digest" in request.get_json():
            existing_tag = registry_model.get_repo_tag(
                repo_ref, tag, include_legacy_image=True)

            manifest_or_image = None
            image_id = None
            manifest_digest = None

            if "manifest_digest" in request.get_json():
                manifest_digest = request.get_json()["manifest_digest"]
                manifest_or_image = registry_model.lookup_manifest_by_digest(
                    repo_ref, manifest_digest, require_available=True)
            else:
                image_id = request.get_json()["image"]
                manifest_or_image = registry_model.get_legacy_image(
                    repo_ref, image_id)

            if manifest_or_image is None:
                raise NotFound()

            existing_manifest = (
                registry_model.get_manifest_for_tag(existing_tag)
                if existing_tag else None)
            existing_manifest_digest = existing_manifest.digest if existing_manifest else None

            if not registry_model.retarget_tag(repo_ref, tag,
                                               manifest_or_image, storage,
                                               docker_v2_signing_key):
                raise InvalidRequest("Could not move tag")

            username = get_authenticated_user().username

            log_action(
                "move_tag" if existing_tag else "create_tag",
                namespace,
                {
                    "username": username,
                    "repo": repository,
                    "tag": tag,
                    "namespace": namespace,
                    "image": image_id,
                    "manifest_digest": manifest_digest,
                    "original_manifest_digest": existing_manifest_digest,
                },
                repo_name=repository,
            )

        return "Updated", 201
コード例 #18
0
ファイル: trigger.py プロジェクト: xzwupeng/quay
  def post(self, namespace_name, repo_name, trigger_uuid):
    """ Activate the specified build trigger. """
    trigger = get_trigger(trigger_uuid)
    handler = BuildTriggerHandler.get_handler(trigger)
    if handler.is_active():
      raise InvalidRequest('Trigger config is not sufficient for activation.')

    user_permission = UserAdminPermission(trigger.connected_user.username)
    if user_permission.can():
      # Update the pull robot (if any).
      pull_robot_name = request.get_json().get('pull_robot', None)
      if pull_robot_name:
        try:
          pull_robot = model.user.lookup_robot(pull_robot_name)
        except model.InvalidRobotException:
          raise NotFound()

        # Make sure the user has administer permissions for the robot's namespace.
        (robot_namespace, _) = parse_robot_username(pull_robot_name)
        if not AdministerOrganizationPermission(robot_namespace).can():
          raise Unauthorized()

        # Make sure the namespace matches that of the trigger.
        if robot_namespace != namespace_name:
          raise Unauthorized()

        # Set the pull robot.
        trigger.pull_robot = pull_robot

      # Update the config.
      new_config_dict = request.get_json()['config']

      write_token_name = 'Build Trigger: %s' % trigger.service.name
      write_token = model.token.create_delegate_token(namespace_name, repo_name, write_token_name,
                                                      'write')

      try:
        path = url_for('webhooks.build_trigger_webhook', trigger_uuid=trigger.uuid)
        authed_url = _prepare_webhook_url(app.config['PREFERRED_URL_SCHEME'],
                                          '$token', write_token.get_code(),
                                          app.config['SERVER_HOSTNAME'], path)

        handler = BuildTriggerHandler.get_handler(trigger, new_config_dict)
        final_config, private_config = handler.activate(authed_url)

        if 'private_key' in private_config:
          trigger.secure_private_key = DecryptedValue(private_config['private_key'])

          # TODO(remove-unenc): Remove legacy field.
          if ActiveDataMigration.has_flag(ERTMigrationFlags.WRITE_OLD_FIELDS):
            trigger.private_key = private_config['private_key']

      except TriggerException as exc:
        write_token.delete_instance()
        raise request_error(message=exc.message)

      # Save the updated config.
      update_build_trigger(trigger, final_config, write_token=write_token)

      # Log the trigger setup.
      repo = model.repository.get_repository(namespace_name, repo_name)
      log_action('setup_repo_trigger', namespace_name,
                 {'repo': repo_name, 'namespace': namespace_name,
                  'trigger_id': trigger.uuid, 'service': trigger.service.name,
                  'pull_robot': trigger.pull_robot.username if trigger.pull_robot else None,
                  'config': final_config},
                 repo=repo)

      return trigger_view(trigger, can_admin=True)
    else:
      raise Unauthorized()
コード例 #19
0
ファイル: tag.py プロジェクト: xzwupeng/quay
    def put(self, namespace, repository, tag):
        """ Change which image a tag points to or create a new tag."""
        if not TAG_REGEX.match(tag):
            abort(400, TAG_ERROR)

        repo_ref = registry_model.lookup_repository(namespace, repository)
        if repo_ref is None:
            raise NotFound()

        if 'expiration' in request.get_json():
            tag_ref = registry_model.get_repo_tag(repo_ref, tag)
            if tag_ref is None:
                raise NotFound()

            expiration = request.get_json().get('expiration')
            expiration_date = None
            if expiration is not None:
                try:
                    expiration_date = datetime.utcfromtimestamp(
                        float(expiration))
                except ValueError:
                    abort(400)

                if expiration_date <= datetime.now():
                    abort(400)

            existing_end_ts, ok = registry_model.change_repository_tag_expiration(
                tag_ref, expiration_date)
            if ok:
                if not (existing_end_ts is None and expiration_date is None):
                    log_action(
                        'change_tag_expiration',
                        namespace, {
                            'username': get_authenticated_user().username,
                            'repo': repository,
                            'tag': tag,
                            'namespace': namespace,
                            'expiration_date': expiration_date,
                            'old_expiration_date': existing_end_ts
                        },
                        repo_name=repository)
            else:
                raise InvalidRequest(
                    'Could not update tag expiration; Tag has probably changed'
                )

        if 'image' in request.get_json(
        ) or 'manifest_digest' in request.get_json():
            existing_tag = registry_model.get_repo_tag(
                repo_ref, tag, include_legacy_image=True)

            manifest_or_image = None
            image_id = None
            manifest_digest = None

            if 'image' in request.get_json():
                image_id = request.get_json()['image']
                manifest_or_image = registry_model.get_legacy_image(
                    repo_ref, image_id)
            else:
                manifest_digest = request.get_json()['manifest_digest']
                manifest_or_image = registry_model.lookup_manifest_by_digest(
                    repo_ref, manifest_digest, require_available=True)

            if manifest_or_image is None:
                raise NotFound()

            # TODO: Remove this check once fully on V22
            existing_manifest_digest = None
            if existing_tag:
                existing_manifest = registry_model.get_manifest_for_tag(
                    existing_tag)
                existing_manifest_digest = existing_manifest.digest if existing_manifest else None

            if not registry_model.retarget_tag(repo_ref, tag,
                                               manifest_or_image, storage,
                                               docker_v2_signing_key):
                raise InvalidRequest('Could not move tag')

            username = get_authenticated_user().username

            log_action(
                'move_tag' if existing_tag else 'create_tag',
                namespace, {
                    'username':
                    username,
                    'repo':
                    repository,
                    'tag':
                    tag,
                    'namespace':
                    namespace,
                    'image':
                    image_id,
                    'manifest_digest':
                    manifest_digest,
                    'original_image':
                    (existing_tag.legacy_image.docker_image_id if existing_tag
                     and existing_tag.legacy_image_if_present else None),
                    'original_manifest_digest':
                    existing_manifest_digest,
                },
                repo_name=repository)

        return 'Updated', 201
コード例 #20
0
ファイル: __init__.py プロジェクト: ynnt/quay
        def wrapped(self, *args, **kwargs):
            if request.is_json and len(request.get_data()) > max_size:
                raise InvalidRequest()

            return func(self, *args, **kwargs)
コード例 #21
0
    def post(self, namespace_name, repo_name, trigger_uuid):
        """
        Activate the specified build trigger.
        """
        trigger = get_trigger(trigger_uuid)
        handler = BuildTriggerHandler.get_handler(trigger)
        if handler.is_active():
            raise InvalidRequest("Trigger config is not sufficient for activation.")

        user_permission = UserAdminPermission(trigger.connected_user.username)
        if user_permission.can():
            # Update the pull robot (if any).
            pull_robot_name = request.get_json().get("pull_robot", None)
            if pull_robot_name:
                try:
                    pull_robot = model.user.lookup_robot(pull_robot_name)
                except model.InvalidRobotException:
                    raise NotFound()

                # Make sure the user has administer permissions for the robot's namespace.
                (robot_namespace, _) = parse_robot_username(pull_robot_name)
                if not AdministerOrganizationPermission(robot_namespace).can():
                    raise Unauthorized()

                # Make sure the namespace matches that of the trigger.
                if robot_namespace != namespace_name:
                    raise Unauthorized()

                # Set the pull robot.
                trigger.pull_robot = pull_robot

            # Update the config.
            new_config_dict = request.get_json()["config"]

            write_token_name = "Build Trigger: %s" % trigger.service.name
            write_token = model.token.create_delegate_token(
                namespace_name, repo_name, write_token_name, "write"
            )

            try:
                path = url_for("webhooks.build_trigger_webhook", trigger_uuid=trigger.uuid)
                authed_url = _prepare_webhook_url(
                    app.config["PREFERRED_URL_SCHEME"],
                    "$token",
                    write_token.get_code(),
                    app.config["SERVER_HOSTNAME"],
                    path,
                )

                handler = BuildTriggerHandler.get_handler(trigger, new_config_dict)
                final_config, private_config = handler.activate(authed_url)

                if "private_key" in private_config:
                    trigger.secure_private_key = DecryptedValue(private_config["private_key"])

            except TriggerException as exc:
                write_token.delete_instance()
                raise request_error(message=exc.message)

            # Save the updated config.
            update_build_trigger(trigger, final_config, write_token=write_token)

            # Log the trigger setup.
            repo = model.repository.get_repository(namespace_name, repo_name)
            log_action(
                "setup_repo_trigger",
                namespace_name,
                {
                    "repo": repo_name,
                    "namespace": namespace_name,
                    "trigger_id": trigger.uuid,
                    "service": trigger.service.name,
                    "pull_robot": trigger.pull_robot.username if trigger.pull_robot else None,
                    "config": final_config,
                },
                repo=repo,
            )

            return trigger_view(trigger, can_admin=True)
        else:
            raise Unauthorized()