Documentation
¶
Overview ¶
Package feature reports what a build grants, for display.
It is the sixth seam, and it clears the bar docs/extensibility.md sets: a behaviour that genuinely differs between editions and that no existing seam can express. authz decides a request and notify carries an event; neither can answer "what does this build grant" — which is the question a licence badge, a hidden nav item and a disabled control are all asking.
A report, never an enforcement point ¶
Nothing in this repository reads a Set to decide whether to do something, and nothing may. A Reporter is supplied by the very module a gate would be constraining, so gating on one asks the licensed module for permission to limit it: a companion that reported everything true would have granted itself everything, and the check would read like a licence check while being the opposite of one. Entitlement is enforced where seam's "Entitlement gating is not a re-registration" says it is — from inside the implementation whose entitlement lapsed, which is the only place that holds both the licence and the operation.
So api is the only consumer of Get, and feature_test.go asserts that from the source rather than trusting this paragraph: the comment is what somebody reads once, and the test is what fails the day somebody does not.
A Slot, not a List ¶
Against seam's own stated test for the two shapes:
- There is exactly one answer to "what does this build grant". A List needs a composition rule, and the only rule available for a set of booleans is OR — under which one module's report turns on another module's feature. That is an entitlement hole, and it is why authz is a Slot too.
- A List has no removal, and a report must be able to say no: a licensed reporter whose licence lapses has to be able to go back to reporting community.
- A Slot is settled by the end of init(), so api.New reads Get once and keeps the result, exactly as it already does for the authorizer. What settles is *who answers*; the answer itself is recomputed per request, because Report is called per request and never cached.
The default below registers from this package's own init(), so the swarms/local exception — "a default that is not in the seam package does not overwrite" — does not apply here: a companion has to import this package to name Register, so its init() is guaranteed to run after this one and win.
Index ¶
Constants ¶
const EditionCommunity = "community"
EditionCommunity is what the default below reports, and what a licensed build reports until its licence verifies.
Variables ¶
This section is empty.
Functions ¶
func Active ¶
func Active() string
Active names the reporter in force, for startup logging.
It is the diagnostic the capability document's seams.feature carries: under D20 the licensed reporter is always linked, so this reads "licence" while the edition still reads "community" until a licence verifies. That pair is what tells "the module is not loaded" apart from "the module is loaded and the licence is missing", which no single field can.
Types ¶
type Licence ¶
type Licence struct {
// Tier is the licence's product tier, as the companion names it: "be",
// "trial". Free text to this repository, which never branches on it.
Tier string `json:"tier"`
// Status is what the badge says.
Status Status `json:"status"`
// ExpiresAt is when the licence stops being valid, or nil when it is
// perpetual or when there is no licence to expire.
//
// No omitempty: an explicit null is what tells a badge "this licence does
// not expire" apart from a document it failed to parse.
ExpiresAt *time.Time `json:"expiresAt"`
}
Licence is what a badge renders, and deliberately not the licence record.
The full record — who it was issued to, which swarm it is bound to, what it entitles — is the companion's to serve through the reserved /api/v1/licence path. What is here is what a badge needs to draw itself and nothing an operator has to be authorised twice for.
type Name ¶
type Name string
Name identifies one capability a build may grant.
A string type rather than an enumeration with a fixed range, for the reason authz.Action gives: the list grows, and a reporter implemented outside this repository must keep compiling when it does.
const ( // MultiSwarm is resolving destinations to more than the swarm the // controller runs in. MultiSwarm Name = "multi-swarm" // SSO is authenticating a browser against an identity provider rather than // against the shared admin token. SSO Name = "sso" // Projects is scoping applications to tenants, and the RBAC that goes with // it. Projects Name = "projects" // Audit is the recorded, queryable history of who asked for what. Audit Name = "audit" // Notifications is delivering events somewhere other than the log and the // event stream. Notifications Name = "notifications" )
func All ¶
func All() []Name
All names every feature this repository knows about, in the order a document should list them.
It exists so that the key set of the capability document is decided here rather than by whatever a reporter happened to put in its map. A UI that hides a control on features["sso"] has to be able to tell false from absent, and a document whose keys move with the reporter cannot: a reporter that dropped a key would read exactly like one that reported the feature off, and the control would disappear rather than grey out.
type Report ¶
type Report struct {
// Edition is what the build calls itself: "community" for the Apache-2.0
// default, "business" once a licence verifies.
Edition string
// Features is what is granted. A consumer must not take its keys as the
// list of features — see All.
Features Set
// Licence is nil in a build with no licensed module linked, which is a
// different thing from a licensed build with no licence installed: that one
// reports StatusAbsent.
Licence *Licence
}
Report is one answer to "what does this build grant".
type Reporter ¶
type Reporter interface {
// Report is called per request and its result is never cached, so an
// entitlement that lapses stops being reported without a restart. An
// implementation that has to reach for the licence should hold its own
// cache and say so; a consumer cannot hold one for it, because it has no
// way to know when it went stale.
Report(ctx context.Context) Report
}
Reporter answers what the build grants.
type Set ¶
Set is what a build grants, by name.
A nil Set reads false for every name, which is exactly what a build granting nothing is — so a reporter with nothing to grant need not build a map.
type Status ¶
type Status string
Status is the state of a licence, as a badge renders it.
Five values, per D25. The set is frozen here even though the mapping onto it lives in the companion: the moment this package ships, Status is public API that a UI in this repository switches on.
const ( // StatusValid is a licence that verified and has not expired. Nothing to // do. StatusValid Status = "valid" // StatusGrace is a licence that has expired but is still granting features // for a bounded period. Renew before ExpiresAt plus that period, or the // build silently becomes the community one. // // Its own value rather than folded into valid, because collapsing it means // a badge cannot say "expired four days ago, stops working tomorrow" — // which is the single most useful thing a licence badge can say — and // leaving the UI to infer the urgency from ExpiresAt alone relies on every // consumer doing that date arithmetic correctly, forever. StatusGrace Status = "grace" // StatusExpired is a licence that verified and is past every grace it had. // The features are already off; renew to get them back. StatusExpired Status = "expired" // StatusInvalid is a licence that did not verify — a bad signature, a // corrupt file, or one issued for a different swarm. The remedy is a // licence from the vendor rather than a change to this deployment, and a // companion can say which of those it was through the reserved // /api/v1/licence path. // // A licence bound to another cluster is folded in here on purpose: its // remedy genuinely differs, but a sixth value that the Apache-2.0 build can // never produce is a worse cost than the ambiguity. StatusInvalid Status = "invalid" // StatusAbsent is a build with a licensed module linked and no licence // installed at all. Install one; nothing is wrong. StatusAbsent Status = "absent" )