Ejemplo n.º 1
0
    def delete(self, namespace_name, repo_name, trigger_uuid):
        """ Delete the specified build trigger. """
        trigger = get_trigger(trigger_uuid)

        handler = BuildTriggerHandler.get_handler(trigger)
        if handler.is_active():
            try:
                handler.deactivate()
            except TriggerException as ex:
                # We are just going to eat this error
                logger.warning("Trigger deactivation problem: %s", ex)

            log_action(
                "delete_repo_trigger",
                namespace_name,
                {
                    "repo": repo_name,
                    "trigger_id": trigger_uuid,
                    "service": trigger.service.name
                },
                repo=model.repository.get_repository(namespace_name,
                                                     repo_name),
            )

        trigger.delete_instance(recursive=True)

        if trigger.write_token is not None:
            trigger.write_token.delete_instance()

        return "No Content", 204
Ejemplo n.º 2
0
def attach_bitbucket_trigger(namespace_name, repo_name):
  permission = AdministerRepositoryPermission(namespace_name, repo_name)
  if permission.can():
    repo = model.repository.get_repository(namespace_name, repo_name)
    if not repo:
      msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
      abort(404, message=msg)
    elif repo.kind.name != 'image':
      abort(501)

    trigger = model.build.create_build_trigger(repo, BitbucketBuildTrigger.service_name(), None,
                                               current_user.db_user())

    try:
      oauth_info = BuildTriggerHandler.get_handler(trigger).get_oauth_url()
    except TriggerProviderException:
      trigger.delete_instance()
      logger.debug('Could not retrieve Bitbucket OAuth URL')
      abort(500)

    config = {
      'access_token': oauth_info['access_token']
    }

    access_token_secret = oauth_info['access_token_secret']
    model.build.update_build_trigger(trigger, config, auth_token=access_token_secret)

    return redirect(oauth_info['url'])

  abort(403)
Ejemplo n.º 3
0
  def post(self, namespace_name, repo_name, trigger_uuid):
    """ Analyze the specified build trigger configuration. """
    trigger = get_trigger(trigger_uuid)

    if trigger.repository.namespace_user.username != namespace_name:
      raise NotFound()

    if trigger.repository.name != repo_name:
      raise NotFound()

    new_config_dict = request.get_json()['config']
    handler = BuildTriggerHandler.get_handler(trigger, new_config_dict)
    server_hostname = app.config['SERVER_HOSTNAME']
    try:
      trigger_analyzer = TriggerAnalyzer(handler,
                                         namespace_name,
                                         server_hostname,
                                         new_config_dict,
                                         AdministerOrganizationPermission(namespace_name).can())
      return trigger_analyzer.analyze_trigger()
    except TriggerException as rre:
      return {
        'status': 'error',
        'message': 'Could not analyze the repository: %s' % rre.message,
      }
    except NotImplementedError:
      return {
        'status': 'notimplemented',
      }
Ejemplo n.º 4
0
def trigger_view(trigger, can_read=False, can_admin=False, for_build=False):
    if trigger and trigger.uuid:
        build_trigger = BuildTriggerHandler.get_handler(trigger)
        build_source = build_trigger.config.get("build_source")

        repo_url = build_trigger.get_repository_url() if build_source else None
        can_read = can_read or can_admin

        trigger_data = {
            "id":
            trigger.uuid,
            "service":
            trigger.service.name,
            "is_active":
            build_trigger.is_active(),
            "build_source":
            build_source if can_read else None,
            "repository_url":
            repo_url if can_read else None,
            "config":
            build_trigger.config if can_admin else {},
            "can_invoke":
            can_admin,
            "enabled":
            trigger.enabled,
            "disabled_reason":
            trigger.disabled_reason.name if trigger.disabled_reason else None,
        }

        if not for_build and can_admin and trigger.pull_robot:
            trigger_data["pull_robot"] = user_view(trigger.pull_robot)

        return trigger_data

    return None
Ejemplo n.º 5
0
  def post(self, namespace_name, repo_name, trigger_uuid):
    """ List the subdirectories available for the specified build trigger and source. """
    trigger = get_trigger(trigger_uuid)

    user_permission = UserAdminPermission(trigger.connected_user.username)
    if user_permission.can():
      new_config_dict = request.get_json()
      handler = BuildTriggerHandler.get_handler(trigger, new_config_dict)

      try:
        subdirs = handler.list_build_subdirs()
        context_map = {}
        for file in subdirs:
          context_map = handler.get_parent_directory_mappings(file, context_map)

        return {
          'dockerfile_paths': ['/' + subdir for subdir in subdirs],
          'contextMap': context_map,
          'status': 'success',
        }
      except EmptyRepositoryException as exc:
        return {
          'status': 'success',
          'contextMap': {},
          'dockerfile_paths': [],
        }
      except TriggerException as exc:
        return {
          'status': 'error',
          'message': exc.message,
        }
    else:
      raise Unauthorized()
Ejemplo n.º 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)
Ejemplo n.º 7
0
def trigger_view(trigger, can_read=False, can_admin=False, for_build=False):
    if trigger and trigger.uuid:
        build_trigger = BuildTriggerHandler.get_handler(trigger)
        build_source = build_trigger.config.get('build_source')

        repo_url = build_trigger.get_repository_url() if build_source else None
        can_read = can_read or can_admin

        trigger_data = {
            'id':
            trigger.uuid,
            'service':
            trigger.service.name,
            'is_active':
            build_trigger.is_active(),
            'build_source':
            build_source if can_read else None,
            'repository_url':
            repo_url if can_read else None,
            'config':
            build_trigger.config if can_admin else {},
            'can_invoke':
            can_admin,
            'enabled':
            trigger.enabled,
            'disabled_reason':
            trigger.disabled_reason.name if trigger.disabled_reason else None,
        }

        if not for_build and can_admin and trigger.pull_robot:
            trigger_data['pull_robot'] = user_view(trigger.pull_robot)

        return trigger_data

    return None
Ejemplo n.º 8
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
Ejemplo n.º 9
0
    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(rre.message)
        else:
            raise Unauthorized()
Ejemplo n.º 10
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)
Ejemplo n.º 11
0
    def post(self, namespace_name, repo_name, trigger_uuid, field_name):
        """ List the field values for a custom run field. """
        trigger = get_trigger(trigger_uuid)

        config = request.get_json() or None
        if AdministerRepositoryPermission(namespace_name, repo_name).can():
            handler = BuildTriggerHandler.get_handler(trigger, config)
            values = handler.list_field_values(field_name,
                                               limit=FIELD_VALUE_LIMIT)

            if values is None:
                raise NotFound()

            return {"values": values}
        else:
            raise Unauthorized()
Ejemplo n.º 12
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(str(rre)) from rre
        else:
            raise Unauthorized()
Ejemplo n.º 13
0
def attach_bitbucket_build_trigger(trigger_uuid):
    trigger = model.build.get_build_trigger(trigger_uuid)
    if not trigger or trigger.service.name != BitbucketBuildTrigger.service_name():
        abort(404)

    if trigger.connected_user != current_user.db_user():
        abort(404)

    verifier = request.args.get("oauth_verifier")
    handler = BuildTriggerHandler.get_handler(trigger)
    result = handler.exchange_verifier(verifier)
    if not result:
        trigger.delete_instance()
        return "Token has expired"

    namespace = trigger.repository.namespace_user.username
    repository = trigger.repository.name

    repo_path = "%s/%s" % (namespace, repository)
    full_url = url_for("web.buildtrigger", path=repo_path, trigger=trigger.uuid)

    logger.debug("Redirecting to full url: %s", full_url)
    return redirect(full_url)
Ejemplo n.º 14
0
    def to_dict(self):
        if not self.uuid:
            return None

        build_trigger = BuildTriggerHandler.get_handler(self)
        build_source = build_trigger.config.get("build_source")

        repo_url = build_trigger.get_repository_url() if build_source else None
        can_read = self.can_read or self.can_admin

        trigger_data = {
            "id": self.uuid,
            "service": self.service_name,
            "is_active": build_trigger.is_active(),
            "build_source": build_source if can_read else None,
            "repository_url": repo_url if can_read else None,
            "config": build_trigger.config if self.can_admin else {},
            "can_invoke": self.can_admin,
        }

        if not self.for_build and self.can_admin and self.pull_robot:
            trigger_data["pull_robot"] = user_view(self.pull_robot)

        return trigger_data
Ejemplo n.º 15
0
    def to_dict(self):
        if not self.uuid:
            return None

        build_trigger = BuildTriggerHandler.get_handler(self)
        build_source = build_trigger.config.get('build_source')

        repo_url = build_trigger.get_repository_url() if build_source else None
        can_read = self.can_read or self.can_admin

        trigger_data = {
            'id': self.uuid,
            'service': self.service_name,
            'is_active': build_trigger.is_active(),
            'build_source': build_source if can_read else None,
            'repository_url': repo_url if can_read else None,
            'config': build_trigger.config if self.can_admin else {},
            'can_invoke': self.can_admin,
        }

        if not self.for_build and self.can_admin and self.pull_robot:
            trigger_data['pull_robot'] = user_view(self.pull_robot)

        return trigger_data
Ejemplo n.º 16
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()
Ejemplo n.º 17
0
def build_trigger_webhook(trigger_uuid, **kwargs):
    logger.debug("Webhook received with uuid %s", trigger_uuid)

    try:
        trigger = model.build.get_build_trigger(trigger_uuid)
    except model.InvalidBuildTriggerException:
        # It is ok to return 404 here, since letting an attacker know that a trigger UUID is valid
        # doesn't leak anything
        abort(404)

    # Ensure we are not currently in read-only mode.
    if app.config.get("REGISTRY_STATE", "normal") == "readonly":
        abort(503, "System is currently in read-only mode")

    # Ensure the trigger has permission.
    namespace = trigger.repository.namespace_user.username
    repository = trigger.repository.name
    if ModifyRepositoryPermission(namespace, repository).can():
        handler = BuildTriggerHandler.get_handler(trigger)

        if trigger.repository.kind.name != "image":
            abort(
                501,
                "Build triggers cannot be invoked on application repositories")

        if trigger.repository.state != RepositoryState.NORMAL:
            abort(503, "Repository is currently in read only or mirror mode")

        logger.debug("Passing webhook request to handler %s", handler)
        try:
            prepared = handler.handle_trigger_request(request)
        except ValidationRequestException:
            logger.debug("Handler reported a validation exception: %s",
                         handler)
            # This was just a validation request, we don't need to build anything
            return make_response("Okay")
        except SkipRequestException:
            logger.debug("Handler reported to skip the build: %s", handler)
            # The build was requested to be skipped
            return make_response("Okay")
        except InvalidPayloadException as ipe:
            logger.exception("Invalid payload")
            # The payload was malformed
            abort(400, message=str(ipe))

        pull_robot_name = model.build.get_pull_robot_name(trigger)
        repo = model.repository.get_repository(namespace, repository)
        try:
            start_build(repo, prepared, pull_robot_name=pull_robot_name)
        except MaximumBuildsQueuedException:
            abort(429, message="Maximum queued build rate exceeded.")
        except BuildTriggerDisabledException:
            logger.debug("Build trigger %s is disabled", trigger_uuid)
            abort(
                400,
                message=
                "This build trigger is currently disabled. Please re-enable to continue.",
            )

        return make_response("Okay")

    abort(403)
Ejemplo n.º 18
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'])

          # 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()