Exemple #1
0
 def __init__(self, registry, clients, plugin_manager, request_validator):
     self.logger = logging.getLogger(__name__)
     self.registry = registry
     self.clients = clients
     self.plugin_manager = plugin_manager
     self.request_validator = request_validator
     self.parser = SchemaParser()
    def test_patch_add_role(self, http_client, base_url, mongo_principal,
                            mongo_role):
        mongo_role.save()
        mongo_principal.save()
        new_role = Role(name="new_role",
                        description="Some desc",
                        roles=[],
                        permissions=["bg-all"])
        new_role.save()

        body = PatchOperation(operation="add", path="/roles", value="new_role")

        url = base_url + "/api/v1/users/" + str(mongo_principal.id)
        request = HTTPRequest(
            url,
            method="PATCH",
            headers={"content-type": "application/json"},
            body=SchemaParser.serialize_patch(body),
        )
        response = yield http_client.fetch(request, raise_error=False)

        assert response.code == 200
        updated = SchemaParser.parse_principal(response.body.decode("utf-8"),
                                               from_string=True)
        assert len(updated.roles) == 2
Exemple #3
0
 def test_parsed_start(self, model, assertion, data):
     assertion(
         SchemaParser.parse(SchemaParser.serialize(data, to_string=False),
                            model,
                            from_string=False),
         data,
     )
    def test_patch_role_description(
        self,
        http_client,
        base_url,
        mongo_role,
        operation,
        value,
        expected_value,
        succeed,
    ):
        mongo_role.save()

        body = PatchOperation(operation=operation,
                              path="/description",
                              value=value)

        request = HTTPRequest(
            base_url + "/api/v1/roles/" + str(mongo_role.id),
            method="PATCH",
            headers={"content-type": "application/json"},
            body=SchemaParser.serialize_patch(body),
        )
        response = yield http_client.fetch(request, raise_error=False)

        if succeed:
            assert response.code == 200
            updated = SchemaParser.parse_role(response.body.decode("utf-8"),
                                              from_string=True)
            assert updated.description == expected_value

        else:
            assert response.code >= 400
Exemple #5
0
    def test_success(self, plugin, request_args):
        # Need to reset the real parser
        plugin.parser = SchemaParser()

        assert_request_equal(
            plugin._pre_process(json.dumps(request_args)),
            SchemaParser.parse_request(request_args),
        )
Exemple #6
0
    def test_publish_create_request(self):
        """Publish a Request over STOMP and verify it via HTTP."""

        stomp_connection = self.create_stomp_connection()

        request_model = self.create_request("test_publish_create_request")

        sample_operation_request = Operation(
            operation_type="REQUEST_CREATE",
            model=request_model,
            model_type="Request",
            target_garden_name="docker",
        )

        listener = MessageListener()
        stomp_connection.set_listener("", listener)

        stomp_connection.subscribe(
            destination="Beer_Garden_Events",
            id="event_listener",
            ack="auto",
            headers={
                "subscription-type": "MULTICAST",
                "durable-subscription-name": "events",
            },
        )

        stomp_connection.send(
            body=SchemaParser.serialize_operation(sample_operation_request,
                                                  to_string=True),
            headers={
                "model_class": sample_operation_request.__class__.__name__,
            },
            destination="Beer_Garden_Operations",
        )

        time.sleep(10)

        requests = self.easy_client.find_requests()

        found_request = False

        print(len(requests))
        for request in requests:
            print(SchemaParser.serialize_request(request, to_string=True))
            if ("generated-by" in request.metadata
                    and request.metadata["generated-by"]
                    == "test_publish_create_request"):
                found_request = True
                break

        assert found_request

        assert listener.create_event_captured

        if stomp_connection.is_connected():
            stomp_connection.disconnect()
Exemple #7
0
    def test_patch_serialized_start(self, patch_dict_no_envelop):
        """Patches are always parsed into a list, so they need a tweak to test"""
        serialized = SchemaParser.serialize(
            SchemaParser.parse_patch(patch_dict_no_envelop, from_string=False),
            to_string=False,
        )

        assert len(serialized) == 1
        assert serialized[0] == patch_dict_no_envelop
Exemple #8
0
    def test_patch_model_start(self, bg_patch):
        """Patches are always parsed into a list, so they need a tweak to test"""
        parsed = SchemaParser.parse(
            SchemaParser.serialize(bg_patch, to_string=False),
            brewtils.models.PatchOperation,
            from_string=False,
        )

        assert len(parsed) == 1
        assert_patch_equal(parsed[0], bg_patch)
def test_serialize_excludes(bg_system, system_dict, keys, excludes):
    for key in keys:
        system_dict.pop(key)

    parser = SchemaParser()
    actual = parser.serialize_system(bg_system,
                                     to_string=False,
                                     include_commands=False,
                                     exclude=excludes)
    assert actual == system_dict
    def test_publish_create_request(self):
        """Published the Request over STOMP and verifies of HTTP"""

        stomp_connection = self.create_stomp_connection()

        request_model = self.create_request("test_publish_create_request")

        sample_operation_request = Operation(
            operation_type="REQUEST_CREATE",
            model=request_model,
            model_type="Request",
        )

        listener = MessageListener()
        stomp_connection.set_listener('', listener)

        stomp_connection.subscribe(destination='Beer_Garden_Events',
                                   id='event_listener',
                                   ack='auto',
                                   headers={
                                       'subscription-type': 'MULTICAST',
                                       'durable-subscription-name': 'events'
                                   })

        stomp_connection.send(
            body=SchemaParser.serialize_operation(sample_operation_request,
                                                  to_string=True),
            headers={
                "model_class": sample_operation_request.__class__.__name__,
            },
            destination="Beer_Garden_Operations",
        )

        time.sleep(10)

        requests = self.easy_client.find_requests()

        found_request = False

        print(len(requests))
        for request in requests:
            print(SchemaParser.serialize_request(request, to_string=True))
            if "generated-by" in request.metadata and request.metadata[
                    "generated-by"] == "test_publish_create_request":
                found_request = True
                break

        assert found_request

        assert listener.create_event_captured

        if stomp_connection.is_connected():
            stomp_connection.disconnect()
Exemple #11
0
    def test_patch_commands(
        self,
        http_client,
        base_url,
        mongo_system,
        system_id,
        bg_command,
        field,
        value,
        dev,
        succeed,
    ):
        if dev:
            mongo_system.version += ".dev"
        mongo_system.deep_save()

        # Make changes to the new command
        if field:
            if field == "parameters":
                value = [value]
            setattr(bg_command, field, value)

        # Also delete the id, otherwise mongo gets really confused
        delattr(bg_command, "id")

        body = PatchOperation(
            operation="replace",
            path="/commands",
            value=SchemaParser.serialize_command(
                [bg_command], to_string=False, many=True
            ),
        )

        request = HTTPRequest(
            base_url + "/api/v1/systems/" + system_id,
            method="PATCH",
            headers={"content-type": "application/json"},
            body=SchemaParser.serialize_patch(body),
        )
        response = yield http_client.fetch(request, raise_error=False)

        if succeed:
            assert response.code == 200

            updated = SchemaParser.parse_system(
                response.body.decode("utf-8"), from_string=True
            )
            assert_command_equal(bg_command, updated.commands[0])
        else:
            assert response.code == 400
Exemple #12
0
def put(request: Request, headers: dict = None, **kwargs) -> None:
    """Put a Request on a queue

    If a routing_key is specified in the kwargs, it will be used. If not, system and
    instance info on the Request will be used, along with the ``is_admin`` kwarg.

    If the Request has an ID it will be added to the headers as 'request_id'.

    Args:
        request: The Request to publish
        headers: Headers to use when publishing
        **kwargs:
            is_admin: Will be passed to get_routing_key
            Other arguments will be passed to the client publish method

    Returns:
        None
    """
    kwargs["headers"] = headers or {}
    if request.id:
        kwargs["headers"]["request_id"] = request.id

    if "routing_key" not in kwargs:
        kwargs["routing_key"] = get_routing_key(
            request.namespace,
            request.system,
            request.system_version,
            request.instance_name,
            is_admin=kwargs.get("is_admin", False),
        )

    clients["pika"].publish(SchemaParser.serialize_request(request), **kwargs)
Exemple #13
0
    def publish(cls, event):
        if event.name in WEBSOCKET_EVENT_TYPE_BLOCKLIST:
            return

        if len(cls.listeners) > 0:
            message = SchemaParser.serialize(event, to_string=True)

            for listener in cls.listeners:
                if _auth_enabled():
                    user = listener.get_current_user()

                    if user is None:
                        listener.request_authorization("Valid access token required")
                        continue

                    if not _user_can_receive_messages_for_event(user, event):
                        logger.debug(
                            "Skipping websocket publish of event %s to user %s due to "
                            "lack of access",
                            event.name,
                            user.username,
                        )
                        continue

                listener.write_message(message)
Exemple #14
0
    def publish_event(self, *args, **kwargs):
        """Publish a new event

        Args:
            *args: If a positional argument is given it's assumed to be an
                Event and will be used
            **kwargs: Will be used to construct a new Event to publish if no
                Event is given in the positional arguments

        Keyword Args:
            _publishers (Optional[List[str]]): List of publisher names.
                If given the Event will only be published to the specified
                publishers. Otherwise all publishers known to Beergarden will
                be used.

        Returns:
            bool: True if the publish was successful

        """
        publishers = kwargs.pop("_publishers", None)

        event = args[0] if args else Event(**kwargs)

        return self.client.post_event(SchemaParser.serialize_event(event),
                                      publishers=publishers)
Exemple #15
0
def post_bg_request(base_url, bg_request):
    return HTTPRequest(
        base_url + "/api/v1/requests/",
        method="POST",
        headers=RestClient.JSON_HEADERS,
        body=SchemaParser.serialize_request(bg_request, to_string=True),
    )
Exemple #16
0
    async def post(self):
        """
        ---
        summary: Forward a request from a parent or child BG instance
        description: |
            When a Beer Garden needs to forward a request, this API will support routing
            to all CRUD actions exposed by the entry points.
        parameters:
          - name: forward
            in: body
            required: true
            description: The Forward Object
            schema:
                $ref: '#/definitions/Forward'
        responses:
          200:
            description: Forward Request Accepted
          400:
            $ref: '#/definitions/400Error'
          50x:
            $ref: '#/definitions/50xError'
        tags:
          - Forward
        """
        operation = SchemaParser.parse_operation(self.request.decoded_body,
                                                 from_string=True)

        response = await self.client(operation)

        self.set_header("Content-Type", "application/json; charset=UTF-8")
        self.write(response)
Exemple #17
0
    def test_patch_many(self, patch_many_dict, bg_patch, bg_patch2, kwargs):
        """Parametrize for the 'many' kwarg because the parser should ignore it"""
        patches = SchemaParser.parse_patch(patch_many_dict, **kwargs)
        sorted_patches = sorted(patches, key=lambda x: x.operation)

        for index, patch in enumerate([bg_patch, bg_patch2]):
            assert_patch_equal(patch, sorted_patches[index])
Exemple #18
0
    def __init__(self,
                 bg_host=None,
                 bg_port=None,
                 ssl_enabled=False,
                 api_version=None,
                 ca_cert=None,
                 client_cert=None,
                 parser=None,
                 logger=None,
                 url_prefix=None,
                 ca_verify=True,
                 **kwargs):
        bg_host = bg_host or kwargs.get('host')
        bg_port = bg_port or kwargs.get('port')

        self.logger = logger or logging.getLogger(__name__)
        self.parser = parser or SchemaParser()

        self.client = RestClient(bg_host=bg_host,
                                 bg_port=bg_port,
                                 ssl_enabled=ssl_enabled,
                                 api_version=api_version,
                                 ca_cert=ca_cert,
                                 client_cert=client_cert,
                                 url_prefix=url_prefix,
                                 ca_verify=ca_verify,
                                 **kwargs)
Exemple #19
0
    def update_request(self,
                       request_id,
                       status=None,
                       output=None,
                       error_class=None):
        """Update a Request

        Args:
            request_id (str): The Request ID
            status (Optional[str]): New Request status
            output (Optional[str]): New Request output
            error_class (Optional[str]): New Request error class

        Returns:
            Response: The updated response

        """
        operations = []

        if status:
            operations.append(PatchOperation("replace", "/status", status))
        if output:
            operations.append(PatchOperation("replace", "/output", output))
        if error_class:
            operations.append(
                PatchOperation("replace", "/error_class", error_class))

        return self.client.patch_request(
            request_id, SchemaParser.serialize_patch(operations, many=True))
Exemple #20
0
    async def patch(self):
        """
        ---
        summary: Initiate administrative actions
        description: |
          The body of the request needs to contain a set of instructions
          detailing the operations to perform.

          Currently the supported operations are `rescan`:
          ```JSON
          [
            { "operation": "rescan" }
          ]
          ```
          * Will remove from the registry and database any currently stopped
            plugins who's directory has been removed.
          * Will add and start any new plugin directories.

          And reloading the plugin logging configuration:
          ```JSON
          [
            {
              "operation": "reload",
              "path": "/config/logging/plugin"
            }
          ]
          ```
        parameters:
          - name: patch
            in: body
            required: true
            description: Instructions for operations
            schema:
              $ref: '#/definitions/Patch'
        responses:
          204:
            description: Operation successfully initiated
          50x:
            $ref: '#/definitions/50xError'
        tags:
          - Admin
        """
        self.verify_user_permission_for_object(GARDEN_UPDATE, local_garden())

        operations = SchemaParser.parse_patch(
            self.request.decoded_body, many=True, from_string=True
        )

        for op in operations:
            if op.operation == "rescan":
                await self.client(Operation(operation_type="RUNNER_RESCAN"))
            elif op.operation == "reload":
                if op.path == "/config/logging/plugin":
                    await self.client(Operation(operation_type="PLUGIN_LOG_RELOAD"))
                else:
                    raise ModelValidationError(f"Unsupported path '{op.path}'")
            else:
                raise ModelValidationError(f"Unsupported operation '{op.operation}'")

        self.set_status(204)
Exemple #21
0
def to_brewtils(
    obj: Union[MongoModel, List[MongoModel], QuerySet]
) -> Union[ModelItem, List[ModelItem], None]:
    """Convert an item from its Mongo model to its Brewtils one

    Args:
        obj: The Mongo model item or QuerySet

    Returns:
        The Brewtils model item

    """
    if obj is None:
        return obj

    if isinstance(obj, (list, QuerySet)):
        if len(obj) == 0:
            return []

        model_class = obj[0].brewtils_model
        many = True
    else:
        model_class = obj.brewtils_model
        many = False

    if getattr(obj, "pre_serialize", None):
        obj.pre_serialize()

    serialized = MongoParser.serialize(obj, to_string=True)
    parsed = SchemaParser.parse(serialized,
                                model_class,
                                from_string=True,
                                many=many)

    return parsed
Exemple #22
0
    def on_message(self, headers: Dict[str, Any], message: Any) -> None:
        """Set a property when a MESSAGE frame is received by the STOMP connection.

        If the needed key is in the headers dict and has the correct value, signal
        that we've been successful.

        Args:
            headers: a dictionary containing all headers sent by the server
            as key/value pairs.

            message: the frame's payload - the message body.
        """
        try:
            if "model_class" in headers:
                if headers["model_class"] == "Operation":
                    parsed = SchemaParser.parse_operation(message,
                                                          from_string=True)

                    if parsed.model and parsed.model.name:
                        if parsed.model.name.startswith("REQUEST"):
                            self.create_event_captured = True
                        else:
                            print("ERROR: no 'REQUEST' in parsed model")
                    else:
                        print("ERROR: no parsed model found")
                elif headers["model_class"] == "error_message":
                    print(f"ERROR: Message returned: {message!r}")
            else:
                print(
                    f"ERROR: 'model_class' not in headers, message={message}")
        except Exception:
            print(f"ERROR: unable to parse, message={message}")
def websocket_publish(item):
    """Will serialize an event and publish it to all event websocket endpoints"""
    try:
        beer_garden.api.http.io_loop.add_callback(
            EventSocket.publish, SchemaParser.serialize(item, to_string=True))
    except Exception as ex:
        logger.exception(f"Error publishing event to websocket: {ex}")
Exemple #24
0
    def clear_queue(self, queue_name: str) -> None:
        """Remove all messages in a queue.

        Args:
            queue_name: The queue name
        """
        self.logger.info("Clearing Queue: %s", queue_name)

        queue_dictionary = self._client.get_queue(self._virtual_host,
                                                  queue_name)
        number_of_messages = queue_dictionary.get("messages_ready", 0)

        while number_of_messages > 0:
            self.logger.debug("Getting the Next Message")
            messages = self._client.get_messages(self._virtual_host,
                                                 queue_name,
                                                 count=1,
                                                 requeue=False)
            if messages and len(messages) > 0:
                message = messages[0]
                try:
                    request = SchemaParser.parse_request(message["payload"],
                                                         from_string=True)
                    beer_garden.requests.cancel_request(request.id)
                except Exception as ex:
                    self.logger.exception(f"Error canceling message: {ex}")
            else:
                self.logger.debug(
                    "Race condition: The while loop thought there were more messages "
                    "to ingest but no more messages could be received.")
                break

            number_of_messages -= 1
Exemple #25
0
 def test_non_strict_failure(self, system_dict):
     system_dict["name"] = 1234
     value = SchemaParser.parse_system(system_dict,
                                       from_string=False,
                                       strict=False)
     assert value.get("name") is None
     assert value["version"] == system_dict["version"]
Exemple #26
0
def process(body) -> Tuple[str, dict]:
    """Processes a message body prior to sending

    We always want to send Operations. So if the given message is an Event we'll wrap
    it in an Operation.

    Args:
        body: the message body to process

    Returns:
        Tuple of the serialized message and headers dict

    """
    many = isinstance(body, list)

    if body.__class__.__name__ == "Event":
        body = Operation(operation_type="PUBLISH_EVENT",
                         model=body,
                         model_type="Event")

    model_class = (body[0] if many else body).__class__.__name__

    if not isinstance(body, str):
        body = SchemaParser.serialize(body, to_string=True, many=many)

    return body, {"model_class": model_class, "many": many}
Exemple #27
0
    async def post(self):
        """
        ---
        summary: Create a new File
        parameters:
          - name: body
            in: body
            required: true
            description: The data
        responses:
          201:
            description: A new File is created
            schema:
              $ref: '#/definitions/FileStatus'
          400:
            $ref: '#/definitions/400Error'
          50x:
            $ref: '#/definitions/50xError'
        tags:
          - Files
        """
        db_file = RawFile()
        db_file.file.put(io.BytesIO(self.request.body))
        db_file.save()

        resolvable = Resolvable(id=str(db_file.id), type="bytes", storage="gridfs")
        response = SchemaParser.serialize(resolvable, to_string=True)

        self.set_header("Content-Type", "application/json; charset=UTF-8")
        self.write(response)
Exemple #28
0
class EventPublisherAPI(BaseHandler):

    parser = SchemaParser()

    @authenticated(permissions=[Permissions.CREATE])
    def post(self):
        """
        ---
        summary: Publish a new event
        parameters:
          - name: bg-namespace
            in: header
            required: false
            description: Namespace to use
            type: string
          - name: event
            in: body
            description: The the Event object
            schema:
              $ref: '#/definitions/Event'
        responses:
          204:
            description: An Event has been published
          400:
            $ref: '#/definitions/400Error'
          50x:
            $ref: '#/definitions/50xError'
        tags:
          - Event
        """
        publish(SchemaParser.parse_event(self.request.decoded_body, from_string=True))

        self.set_status(204)
Exemple #29
0
    def update_instance(self, instance_id, **kwargs):
        """Update an Instance status

        Args:
            instance_id (str): The Instance ID

        Keyword Args:
            new_status (str): The new status
            metadata (dict): Will be added to existing instance metadata

        Returns:
            Instance: The updated Instance

        """
        operations = []
        new_status = kwargs.pop("new_status", None)
        metadata = kwargs.pop("metadata", {})

        if new_status:
            operations.append(PatchOperation("replace", "/status", new_status))

        if metadata:
            operations.append(PatchOperation("update", "/metadata", metadata))

        return self.client.patch_instance(
            instance_id, SchemaParser.serialize_patch(operations, many=True))
Exemple #30
0
    async def patch(self):
        """
        ---
        summary: Partially update a Garden
        description: |
          The body of the request needs to contain a set of instructions detailing the
          updates to apply. Currently the only operations are:

          * sync

          ```JSON
          [
            { "operation": "" }
          ]
          ```
        parameters:
          - name: garden_name
            in: path
            required: true
            description: Garden to use
            type: string
          - name: patch
            in: body
            required: true
            description: Instructions for how to update the Garden
            schema:
              $ref: '#/definitions/Patch'
        responses:
          200:
            description: Execute Patch action against Gardens
            schema:
              $ref: '#/definitions/Garden'
          400:
            $ref: '#/definitions/400Error'
          404:
            $ref: '#/definitions/404Error'
          50x:
            $ref: '#/definitions/50xError'
        tags:
          - Garden
        """

        patch = SchemaParser.parse_patch(self.request.decoded_body, from_string=True)

        for op in patch:
            operation = op.operation.lower()

            if operation == "sync":
                response = await self.client(
                    Operation(
                        operation_type="GARDEN_SYNC",
                    )
                )

            else:
                raise ModelValidationError(f"Unsupported operation '{op.operation}'")

        self.set_header("Content-Type", "application/json; charset=UTF-8")
        self.write(response)