I had some more time to work on this to be a bit more concrete. This is about smart proxy registration in Foreman.
Current
The smart-proxy exposes a GET /features
API endpoint returning a simple list of features:
[
"dns",
"logs"
]
On registration (or save actually) Foreman calls this API and stores the features (code). It does this in a has_and_belongs_to_many :features
relation. This means we have table smart_proxies
and a table features
. These are joined via a table with 2 foreign keys.
Problem
It is impossible to know what a feature actually supports.
My concrete use case is that I want to enable domain CRUD management to the smart proxy DNS. I could implement a dnsv2 feature, but not every DNS backend can actually support it. That means we now need to choose between feature dnsv2
and dns
.
Another is various DNS types. The libvirt DNS provider doesn’t support PTR records. Currently it’s implemented as a noop but lying like this isn’t very nice. As we want to add more records this could only get more complicated. If we are exposing a UI to create SRV or CNAME records, then it would be nice if we could statically determine that the field should be disabled with a message telling the user it’s unsupported.
Pulp is another application where content types (RPM, Deb, Puppet, OS tree) are pluggable. Capabilities could be used to tell Katello which UI options to enable.
Proposal
Smart proxy side
I’d propose we add a new GET /v2/features
which changes to a mapping. Every feature is a mapping:
{
"dns": {
"capabilities": [
"TYPE_A",
"TYPE_AAAA",
"TYPE_CNAME",
"TYPE_PTR",
"TYPE_SRV"
],
"settings": {}
},
"tftp": {
"capabilities": [],
"settings": {
"tftp_servername": null
}
},
"logs": {
"capabilities": [],
"settings": {}
}
}
capabilities
A simple list of strings that are flags. Intended module DSL:
module Proxy::Dns
class Plugin < ::Proxy::Plugin
# ...
capability :TYPE_A
capability :TYPE_AAAA
capability :TYPE_CNAME
capability :TYPE_PTR
capability :TYPE_SRV
# ...
end
end
settings
A mapping of settings that every module can expose. Intended module DSL:
module Proxy::TFTP
class Plugin < ::Proxy::Plugin
expose_setting :tftp_servername
end
end
The benefit of this was not in my initial idea but it turns out TFTP exposes the tftp_servername setting via GET /tftp/serverName
. The pulp plugin exposes GET /status/puppet
where it exposes the puppet_content_dir
setting.
Formalizing this in the features API means we can create a unified API within Foreman to retrieve this. We also have fewer REST endpoints.
There is a difference that this is currently queried live while the features API is typically only called at registration. This can be a benefit in performance and reliability at the cost of some flexibility.
Foreman side
Registration
The registration wouldn’t be very different on a conceptual level.
First we get the v2 features API. If this returns a 404 we fall back to the v1 API. We’d assume no capabilities and settings are present.
To store capabilities and settings we change the join table to a real table. In rails terms a has_many :features, :through => :smart_proxy_features
(I think).
The SmartProxyFeature model would have:
- id Primary Key
- smart_proxy Foreign Key
- feature Foreign Key
- capabilities Array (serializing to a string in sqlite/mysql,
text[]
in postgresql)
- settings store/hash (serialized to a JSON string in sqlite/mysql, jsonb in postgresql)
Model functions
We’d introduce some helpers. Don’t pin me on these names:
feature_capability?(feature, capability)
feature_setting(feature, setting)