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 todelete_value, only the key and value specified by thekeyoption are deleted. If set toset_valuethe value for the key specified by thekeyoption is set to the value specified by thevalueoption. If set toset_json_value_from_request_body, the value is read from the body of the HTTP request and decoded as JSON. If set toset_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 therequest_pathis set to/prefixthe handler will match/prefix/name. In this example,nameis 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
actionis set todelete_value,set_value,set_json_value_from_request_body, orset_text_value_from_request_body, this option must be specified.value(optional):Value to be set for the key denoted by the
key. When theactionis set toset_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
Trueif the request can be handled andFalseif 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:
Trueif this request handler can handle the specified request,Falseif 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
byteswhen 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 beNone, 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_handleandhandle. This method is called for each request before callingcan_handle.This is useful when both function need to do some processing on the URI. This processing can be implemented in
prepare_contextand 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_handleandhandle. The default implementation simply returnsNone.- 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_handleandhandle.
- set_data_source(data_source: DataSource) None
Set the data source to be used by this component.
Calling
inject_data_sourcemight 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 documentationfor a list of supported options.- Returns:
HTTP request handler applying updates to an SQLite database.