vinegar.request_handler.sqlite_update

Request handler that allows for making changes to an SQLite database.

This request handler is backed by an instance of vinegar.utils.sqlite_store.DataStore for actually accessing the database.

This can be useful when a flag for a system has to be updated dynamically. For example, there might be a flag that indicates whether a system should boot into an installer when booting from the network. Once the system installation has finished, this flag would need to be reset to avoid running the installer again and again. With this handler, this can be achieved by making a HTTP request to an instance of this handler once the installation process has finished. This handler can then reset the flag in the database, ensuring that the system will not boot into the installer again.

This handler only handles POST requests. The semantics of GET requests are that they are idempotent, meaning that they do not change any state. As this request handler makes changes to the state of the database, allowing GET requests would violate the semantics of the HTTP protocol.

It is very easy to trigger a POST request from the command line. Depending on which tools are available on the system, either of the following two commands can be used:

curl -X POST http://vinegar.example.com/sqlite-prefix/system-id
wget -O - --post-data= http://vinegar.example.com/sqlite-prefix/system-id

When using the set_json_value_from_request_body or set_text_value_from_request_body action, the value that shall be set can be passed like this:

curl -X POST --data-binary myvalue         http://vinegar.example.com/sqlite-prefix/system-id
wget -O - --post-data=myvalue         http://vinegar.example.com/sqlite-prefix/system-id

Access restrictions

By default, this request handler will allow any client to update the data for all systems. This might not be desirable, in particular when the network in which the Vinegar server is running cannot be deemed secure.

Therefore, it is possible to limit access to the data for each system to specific clients. This is achieved through the client_address_key configuration option. This option specifies a key into the system data. The key can consist of multiple components separated by colons (:) to point into a nested dictionary.

For example, suppose that client_address_key is set to net:ip_addr. If the handler gets a request for the system ID myid, it will ask the data source for the system data for this system by calling data_source.get_data('myid', {}, '').

If the system data returned for this system does not contain a value for the specified key, the request is denied with HTTP status code 403 (forbidden) and the database is not modified.

For this example, let us assume that the data source returns the following data for the system myid (expressed as YAML):

net:
    ip_addr: 192.0.2.1

In this case, the request handler will only allow the request, if it comes from the IP address 192.0.2.1. In all other cases, it will reject the request.

Instead of a single IP address, the system data may also contain a list or set of IP addresses. For example, if get_data returned the following system data

net:
    ip_addr:
        - 192.0.2.1
        - 2001:db8::1

the request would be allowed if it came from 192.0.2.1 or 2001:db8::1.

In addition to single IP addresses, IP subnets using CIDR notation are allowed as well. For example,

net:
    ip_addr:
        - 192.0.2.0/24
        - 2001:db8::/64

will allow access for all clients from the IP subnets 192.0.2.0/24 and 2001:db8::/64.

As an alternative to the client_address_key option, the client_address_list option may be specified. This option takes a list of IP addresses or IP subnets and allows requests from clients that match one of these entries. This can be useful in order to allow full access from certain administrative clients.

When both client_address_key and client_address_list are specified, they are combined, meaning that a request is allowed when it either matches one of the entries from client_address_list or the entries from the data tree for the client_address_key.

When the client_address_key option is used, this request handler needs a data source in order to get information about the system. This request handler implements the DataSourceAware interface, so when it is used inside a container, that container will typically take care of setting the data source. If instantiated directly, the data source has to be set explicitly by calling the handler’s set_data_source method.

Configuration options

The sqlite_update request handler have several configuration options that can be used to control its behavior.

action (mandatory):

Action to be taken when this handler is triggers. If set to delete_data, all data stored for the specified system is deleted. If set to delete_value, only the key and value specified by the key option are deleted. If set to set_value the value for the key specified by the key option is set to the value specified by the value option. If set to set_json_value_from_request_body, the value is read from the body of the HTTP request and decoded as JSON. If set to set_text_value_from_request_body, the value is read from the body of the HTTP request (it must be encoded as UTF-8) and stored as a string.

db_file (mandatory):

Path to the database file that contains the SQLite database that will be updated by this handler. This database must be a database in the format expected by vinegar.utils.sqlite.data_store.

request_path (mandatory):

request path for which this request handler shall be used. The specified path must start with a /. It will match any request that has a path that starts with the configured request path and is followed by a string. For example, when the request_path is set to /prefix the handler will match /prefix/name. In this example, name is the string that is used as the system ID.

client_address_key (optional):

Key into the system data that points to the place in the data where the allowed client address or addresses are stored. If this option is not set (the default), each client can access this handler for arbitrary system IDs and thus modify the data for arbitrary systems. When this is not desired, this option can be used to limit the allowe client (IP) addresses for each system. The key can point into a nested dictionary, using the colon (:) to separate key components for the various levels. The value can be a string (matching exactly one IP address or IP subnet) or a list or set of IP addresses or IP subnets (matching any of the addresses or subnets in the list or set). Please refer to Access restrictions for a more detailed discussion of how this option can be used.

client_address_list (optional):

List of IP addresses or IP subnets from which requests are allowed. Please refer to Access restrictions for a more detailed discussion of how this option can be used.

key (optional):

Name of the key in the database that shall be deleted or updated. If the action is set to delete_value, set_value, set_json_value_from_request_body, or set_text_value_from_request_body, this option must be specified.

value (optional):

Value to be set for the key denoted by the key. When the action is set to set_value, this option must be specified.

class vinegar.request_handler.sqlite_update.HttpSQLiteUpdateRequestHandler(config: Mapping[Any, Any])

HTTP request handler that applies updates to an SQLite database.

For information about the configuration options supported by this request handler, please refer to the module documentation.

can_handle(uri: str, context: Any) bool

Tell whether the request can be handled by this request handler.

Returns True if the request can be handled and False if it cannot be handled and the next request handler should be tried.

Parameters:
  • uri – URI that has been requested by the client. The URI includes the full request path including the query string (if present) and has not been URL decoded.

  • context – context object that was returned by prepare_context.

Returns:

True if this request handler can handle the specified request, False if the request should be deferred to the next handler.

close()

Close the data store backing this request handle. All operations that involve the data store will fail by raising an exception after calling this method.

For most applications, where request handlers are long-lived, relying on Python’s garbage collection is fine for closing the underlying data store. However, if an application rapidly creates and discards request handler instances (e.g. for automated tests), closing the request handler explicitly can be beneficial because it helps to release resources early on.

handle(request_info: HttpRequestInfo, body: BufferedIOBase, context: Any) Tuple[HTTPStatus, Mapping[str, str] | None, BufferedIOBase | None]

Handle the request.

This method returns a tuple of three items. The first item is the HTTP status code, the second item are the headers that shall be sent to the client, and the third is a file-like object from which the data for the requested file can be read. The returned file-like object must supply its data in binary form.

Parameters:
  • request_info – information about the request that shall be handled.

  • body – file-like object that provides the request body sent by the client. This file-like object returns bytes when reading.

  • context – context object that was returned by prepare_context.

Returns:

tuple of the HTTP status code, the response headers, and a file-like object that provides the data that is transferred to the client. The file-like object must provide binary data. The response headers may be None, which has the same effect as supplying an empty dict (no headers are added to the response). The file-like object may also be None, which means that the body of the response is empty. Typically, this is only useful when indicating an error (status code >= 400). In this case, a body with an appropriate error message for the status code is generated by the server.

prepare_context(uri: str) Any

Prepare a context object for use by can_handle and handle. This method is called for each request before calling can_handle.

This is useful when both function need to do some processing on the URI. This processing can be implemented in prepare_context and passed to the two other methods through the context so that it does not have to be done twice.

The return value of this method is passed to can_handle and handle. The default implementation simply returns None.

Parameters:

uri – URI that has been requested by the client. The URI includes the full request path including the query string (if present) and has not been URL decoded.

Returns:

context object that is passed to can_handle and handle.

set_data_source(data_source: DataSource) None

Set the data source to be used by this component.

Calling inject_data_source might be preferable to calling this method directly.

In general, code using this method should not assume that this method is thread safe, even if the object that implements it is considered safe in general. This means that this method should typically be called only once, directly after creating an object.

Parameters:

data_source – data source to be injected.

vinegar.request_handler.sqlite_update.get_instance_http(config: Mapping[Any, Any]) HttpSQLiteUpdateRequestHandler

Create a HTTP request handler that applies updates to an SQLite database.

Parameters:

config – configuration for this request handler. Please refer to the module documentation for a list of supported options.

Returns:

HTTP request handler applying updates to an SQLite database.