Documentation
¶
Overview ¶
Package reconcile makes Frontier platform resources match a desired-state file through the admin API. Each resource kind implements Reconciler and registers under its kind, so new kinds plug in without changing the command or file format. PlatformUser is the first kind.
Index ¶
- Constants
- func Export(ctx context.Context, registry map[string]Reconciler, kind string) ([]byte, error)
- type Op
- type PermissionAPI
- type PermissionReconciler
- type PermissionSpec
- type PlatformUserAPI
- type PlatformUserReconciler
- type PlatformUserSpec
- type PreferenceAPI
- type PreferenceReconciler
- type PreferenceSpec
- type Reconciler
- type Report
- type RoleAPI
- type RoleReconciler
- type RoleSpec
- type WebhookAPI
- type WebhookReconciler
- type WebhookSpec
Constants ¶
const KindPermission = "Permission"
KindPermission is the desired-state document kind for custom permissions.
const KindPlatformUser = "PlatformUser"
KindPlatformUser is the desired-state document kind for platform users.
const KindPreference = "Preference"
KindPreference is the desired-state document kind for platform preferences.
const KindRole = "Role"
KindRole is the desired-state document kind for platform-level roles.
const KindWebhook = "Webhook"
KindWebhook is the desired-state document kind for webhook endpoints.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Op ¶
Op is a single planned change to one (principal, relation). Ref is the desired entry's ref for an add (email or id) and the current principal's id for a remove.
type PermissionAPI ¶ added in v0.110.0
type PermissionAPI interface {
ListPermissions(context.Context, *connect.Request[frontierv1beta1.ListPermissionsRequest]) (*connect.Response[frontierv1beta1.ListPermissionsResponse], error)
CreatePermission(context.Context, *connect.Request[frontierv1beta1.CreatePermissionRequest]) (*connect.Response[frontierv1beta1.CreatePermissionResponse], error)
DeletePermission(context.Context, *connect.Request[frontierv1beta1.DeletePermissionRequest]) (*connect.Response[frontierv1beta1.DeletePermissionResponse], error)
}
PermissionAPI is the API subset the permission reconciler needs. Reads live on FrontierService, writes on AdminService; the caller provides one value that serves both.
type PermissionReconciler ¶ added in v0.110.0
type PermissionReconciler struct {
// contains filtered or unexported fields
}
PermissionReconciler makes custom permissions match the desired spec. Base-schema permissions (app namespaces) are server-managed and ignored.
func NewPermissionReconciler ¶ added in v0.110.0
func NewPermissionReconciler(client PermissionAPI, header string) *PermissionReconciler
func (*PermissionReconciler) Export ¶ added in v0.110.0
func (r *PermissionReconciler) Export(ctx context.Context) (any, error)
Export returns the current custom permissions as a desired-state spec, sorted so repeated exports produce identical files.
func (*PermissionReconciler) Kind ¶ added in v0.110.0
func (r *PermissionReconciler) Kind() string
func (*PermissionReconciler) Validate ¶ added in v0.111.0
func (r *PermissionReconciler) Validate(spec []byte) error
Validate checks every entry, and the in-file slug conflicts, without touching the server, so a bad entry stops the whole file before anything applies.
type PermissionSpec ¶ added in v0.110.0
type PermissionSpec struct {
Namespace string `yaml:"namespace"`
Name string `yaml:"name"`
Delete bool `yaml:"delete,omitempty"`
}
PermissionSpec is one desired permission. A permission is identity only (namespace + name): it is added or deleted, never updated. Deleting needs the explicit flag; a permission that just disappears from the file fails the plan instead.
func (PermissionSpec) String ¶ added in v0.110.0
func (s PermissionSpec) String() string
type PlatformUserAPI ¶
type PlatformUserAPI interface {
ListPlatformUsers(context.Context, *connect.Request[frontierv1beta1.ListPlatformUsersRequest]) (*connect.Response[frontierv1beta1.ListPlatformUsersResponse], error)
AddPlatformUser(context.Context, *connect.Request[frontierv1beta1.AddPlatformUserRequest]) (*connect.Response[frontierv1beta1.AddPlatformUserResponse], error)
RemovePlatformUser(context.Context, *connect.Request[frontierv1beta1.RemovePlatformUserRequest]) (*connect.Response[frontierv1beta1.RemovePlatformUserResponse], error)
}
PlatformUserAPI is the subset of the admin API the platform-user reconciler needs. frontierv1beta1connect.AdminServiceClient satisfies it.
type PlatformUserReconciler ¶
type PlatformUserReconciler struct {
// contains filtered or unexported fields
}
PlatformUserReconciler makes platform admins and members match the desired spec.
func NewPlatformUserReconciler ¶
func NewPlatformUserReconciler(client PlatformUserAPI, header string) *PlatformUserReconciler
func (*PlatformUserReconciler) Export ¶ added in v0.110.0
func (r *PlatformUserReconciler) Export(ctx context.Context) (any, error)
Export returns the current platform users as a desired-state spec: one entry per (principal, relation), users referenced by email address when they have a parseable one, else by id. Entries are sorted so repeated exports produce identical files.
func (*PlatformUserReconciler) Kind ¶
func (r *PlatformUserReconciler) Kind() string
func (*PlatformUserReconciler) Validate ¶ added in v0.111.0
func (r *PlatformUserReconciler) Validate(spec []byte) error
Validate checks every entry without touching the server, so a bad entry stops the whole file before anything applies.
type PlatformUserSpec ¶
type PlatformUserSpec struct {
Type string `yaml:"type"` // "user" | "serviceuser"
Ref string `yaml:"ref"` // email or uuid for a user; id for a service user
Relation string `yaml:"relation"` // "admin" | "member"
}
PlatformUserSpec is one desired platform-user entry from the YAML spec. Relation is "admin" or "member" — a SpiceDB relation, not an RBAC "role" (a separate concept in Frontier), hence the field name.
type PreferenceAPI ¶ added in v0.110.0
type PreferenceAPI interface {
ListPreferences(context.Context, *connect.Request[frontierv1beta1.ListPreferencesRequest]) (*connect.Response[frontierv1beta1.ListPreferencesResponse], error)
DescribePreferences(context.Context, *connect.Request[frontierv1beta1.DescribePreferencesRequest]) (*connect.Response[frontierv1beta1.DescribePreferencesResponse], error)
CreatePreferences(context.Context, *connect.Request[frontierv1beta1.CreatePreferencesRequest]) (*connect.Response[frontierv1beta1.CreatePreferencesResponse], error)
}
PreferenceAPI is the API subset the preference reconciler needs. The reads live on different services (platform preferences on AdminService, the trait list on FrontierService); the caller provides one value that serves both.
type PreferenceReconciler ¶ added in v0.110.0
type PreferenceReconciler struct {
// contains filtered or unexported fields
}
PreferenceReconciler makes platform preferences match the desired spec. A preference is a name and a value; a missing entry resets to the trait default, because settings always have a default to fall back to.
func NewPreferenceReconciler ¶ added in v0.110.0
func NewPreferenceReconciler(client PreferenceAPI, header string) *PreferenceReconciler
func (*PreferenceReconciler) Export ¶ added in v0.110.0
func (r *PreferenceReconciler) Export(ctx context.Context) (any, error)
Export returns the platform preferences whose value differs from the trait default, sorted by name. Preferences at their default stay out of the file, so reconciling an export plans no changes.
func (*PreferenceReconciler) Kind ¶ added in v0.110.0
func (r *PreferenceReconciler) Kind() string
func (*PreferenceReconciler) Validate ¶ added in v0.111.0
func (r *PreferenceReconciler) Validate(spec []byte) error
Validate checks the server-free parts of every entry (name present, no duplicates) so a bad entry stops the whole file before anything applies. The unknown-name check needs the server's trait list, so it stays in the diff.
type PreferenceSpec ¶ added in v0.110.0
PreferenceSpec is one desired platform preference. Name is a trait name the server knows; value is the string value to set. Preferences are strings end to end, so a boolean-like trait is "true" or "false".
type Reconciler ¶
type Reconciler interface {
Kind() string
Validate(spec []byte) error
Reconcile(ctx context.Context, spec []byte, dryRun bool) (Report, error)
Export(ctx context.Context) (spec any, err error)
}
Reconciler makes a single resource kind match its desired-state spec. Validate checks a spec without touching the server or applying anything, so the whole file can be checked before the first change is made. Export is the reverse direction and is part of the contract: it reads the current server state and returns it as a spec value, ready to be marshalled into a desired-state document. Reconciling an exported document must plan no changes.
type Report ¶
type Report struct {
Kind string
DryRun bool
Planned []string // the plan, human-readable
Applied int // number actually applied (0 when dryRun)
}
Report summarises what a reconcile did, or would do when dryRun.
func Run ¶
func Run(ctx context.Context, registry map[string]Reconciler, data []byte, dryRun bool) ([]Report, error)
Run applies a (possibly multi-document) desired-state file. The whole file is parsed and checked first, so a malformed later document stops the run before anything applies. Documents then dispatch in file order — dependency order is the file author's job — and the first error stops the run and returns the reports gathered so far.
type RoleAPI ¶ added in v0.110.0
type RoleAPI interface {
ListRoles(context.Context, *connect.Request[frontierv1beta1.ListRolesRequest]) (*connect.Response[frontierv1beta1.ListRolesResponse], error)
CreateRole(context.Context, *connect.Request[frontierv1beta1.CreateRoleRequest]) (*connect.Response[frontierv1beta1.CreateRoleResponse], error)
UpdateRole(context.Context, *connect.Request[frontierv1beta1.UpdateRoleRequest]) (*connect.Response[frontierv1beta1.UpdateRoleResponse], error)
DeleteRole(context.Context, *connect.Request[frontierv1beta1.DeleteRoleRequest]) (*connect.Response[frontierv1beta1.DeleteRoleResponse], error)
}
RoleAPI is the API subset the role reconciler needs. Reads live on FrontierService, writes on AdminService; the caller provides one value that serves both.
type RoleReconciler ¶ added in v0.110.0
type RoleReconciler struct {
// contains filtered or unexported fields
}
RoleReconciler makes platform-level roles match the desired spec. The role name is the identity; title, permissions, and scopes are the managed fields.
func NewRoleReconciler ¶ added in v0.110.0
func NewRoleReconciler(client RoleAPI, header string) *RoleReconciler
func (*RoleReconciler) Export ¶ added in v0.110.0
func (r *RoleReconciler) Export(ctx context.Context) (any, error)
Export returns the current platform roles as a desired-state spec. Each server role is compared to its default (empty for a custom role, the shipped definition for a predefined one) and only the fields that differ are emitted. A custom role always emits an entry, because a custom role must appear in the file to be kept; a predefined role emits an entry only when some field differs, so converged ones stay out. An empty server value that differs from the default is written as an explicit empty (`title: ""`, `scopes: []`), so the value round-trips exactly and reconciling an export plans no changes.
func (*RoleReconciler) Kind ¶ added in v0.110.0
func (r *RoleReconciler) Kind() string
func (*RoleReconciler) Validate ¶ added in v0.111.0
func (r *RoleReconciler) Validate(spec []byte) error
Validate checks every entry, and rejects duplicate role names, without touching the server, so a bad entry stops the whole file before anything applies.
type RoleSpec ¶ added in v0.110.0
type RoleSpec struct {
Name string `yaml:"name"`
Title *string `yaml:"title,omitempty"`
Description *string `yaml:"description,omitempty"`
Permissions *[]string `yaml:"permissions,omitempty"`
Scopes *[]string `yaml:"scopes,omitempty"`
Delete bool `yaml:"delete,omitempty"`
}
RoleSpec is one desired platform-level role. Name is the identity and never changes. Every other field uses presence tracking: a pointer is nil when the field is omitted, and non-nil when the file lists it (including an explicit empty value like `title: ""` or `scopes: []`).
A present field is the whole desired value for that field. An omitted field takes the role's default: empty for a custom role, and the shipped definition's value for a predefined role. So custom and predefined roles run through the same rule; they differ only in their defaults and in that a predefined role is never created or deleted by this flow.
type WebhookAPI ¶ added in v0.111.0
type WebhookAPI interface {
ListWebhooks(context.Context, *connect.Request[frontierv1beta1.ListWebhooksRequest]) (*connect.Response[frontierv1beta1.ListWebhooksResponse], error)
CreateWebhook(context.Context, *connect.Request[frontierv1beta1.CreateWebhookRequest]) (*connect.Response[frontierv1beta1.CreateWebhookResponse], error)
UpdateWebhook(context.Context, *connect.Request[frontierv1beta1.UpdateWebhookRequest]) (*connect.Response[frontierv1beta1.UpdateWebhookResponse], error)
DeleteWebhook(context.Context, *connect.Request[frontierv1beta1.DeleteWebhookRequest]) (*connect.Response[frontierv1beta1.DeleteWebhookResponse], error)
}
WebhookAPI is the API subset the webhook reconciler needs. Every webhook operation lives on the admin service.
type WebhookReconciler ¶ added in v0.111.0
type WebhookReconciler struct {
// contains filtered or unexported fields
}
WebhookReconciler makes webhook endpoints match the desired spec. The URL is the identity; description, subscribed events, and state are the managed fields. A missing endpoint fails the plan, and deletion needs an explicit delete flag.
func NewWebhookReconciler ¶ added in v0.111.0
func NewWebhookReconciler(client WebhookAPI, header string) *WebhookReconciler
func (*WebhookReconciler) Export ¶ added in v0.111.0
func (r *WebhookReconciler) Export(ctx context.Context) (any, error)
Export returns the current webhook endpoints as a desired-state spec, sorted by url. State is written only when it is not the default ("enabled") and an empty description is left out. Because reconcile converges an omitted field to its default, dropping a default-valued field keeps the export minimal and still round-trips to no changes. Secrets are never read from the server, so they can never appear in an export.
func (*WebhookReconciler) Kind ¶ added in v0.111.0
func (r *WebhookReconciler) Kind() string
func (*WebhookReconciler) Validate ¶ added in v0.111.0
func (r *WebhookReconciler) Validate(spec []byte) error
type WebhookSpec ¶ added in v0.111.0
type WebhookSpec struct {
URL string `yaml:"url"`
Description string `yaml:"description,omitempty"`
SubscribedEvents []string `yaml:"subscribed_events"`
State string `yaml:"state,omitempty"`
Delete bool `yaml:"delete,omitempty"`
}
WebhookSpec is one desired webhook endpoint. The URL is the identity and never changes. Every other field states the whole desired value under the one field model: a field written in the file is used as-is, and an omitted field takes its default, nothing is merged from the server. Subscribed events are the full desired set: an empty list (or none) means the endpoint receives every event, which is the server's own default, and export always writes the field so an all-events endpoint reads as an explicit `subscribed_events: []`. Description defaults to empty, so omitting it clears any description on the server. State defaults to enabled, the state the server gives a new endpoint, so omitting it converges the endpoint to enabled. Signing secrets are server-owned: the server generates one on create and never returns it on read, so they are not part of the spec.