Documentation
¶
Index ¶
- Constants
- Variables
- func BoundHandles(config map[string]string) []string
- func DiscardSecrets(values map[string][]byte)
- func FormatIsolation(value pluginhost.Isolation) string
- func NormalizeTenant(tenant string) string
- func ParseIsolation(value string) (pluginhost.Isolation, error)
- func SanitizeDetail(s string) string
- func SaveAppliedCache(path string, cache AppliedCache) error
- type AppliedCache
- type Applier
- type ApplyUpdateOptions
- type Controller
- func (c *Controller) ApplyUpdateVersion(opts ApplyUpdateOptions) (InstanceState, error)
- func (c *Controller) Disable(tenant, instanceID string) (StateResult, error)
- func (c *Controller) Enable(opts EnableOptions) (StateResult, error)
- func (c *Controller) Remove(opts RemoveOptions) (RemoveResult, error)
- func (c *Controller) UpdateCheck(pluginID string, incoming *registry.Manifest, confirm bool) ([]string, error)
- type ControllerOptions
- type EnableOptions
- type FileSecrets
- type Host
- func (h *Host) ApplySnapshot(ctx context.Context, tenant string, instances []api.PluginDesiredInstanceData) ([]api.PluginApplyResultData, error)
- func (h *Host) InstallationStatus() ([]api.PluginInstallationStatusData, error)
- func (h *Host) InstanceObservations(tenant string) []api.PluginObservedInstanceData
- func (h *Host) Load(ctx context.Context) (LoadResult, error)
- func (h *Host) LoadTenant(ctx context.Context, tenant string) (LoadResult, error)
- func (h *Host) Observe(ctx context.Context, tenant string) ([]api.PluginInstallationStatusData, []api.PluginObservedInstanceData, error)
- func (h *Host) Run(ctx context.Context) error
- type HostManager
- type HostObserver
- type HostOptions
- type HostReconciler
- type InstanceState
- type LoadResult
- type MetricsReader
- type Observation
- type RemoveOptions
- type RemoveResult
- type SecretResolver
- type StateResult
- type Store
- type SyncOptions
- type Syncer
Constants ¶
const ( IsolationPerInstance = "per-instance" )
IsolationNames are the stable, persisted isolation values. They intentionally mirror pluginhost.Isolation.String so state files remain readable even if the internal numbering changes.
const DetailLimit = 240
DetailLimit 是所有出网 detail 字段的长度上限(字节):command_ack 的执行结果 摘要、plugin_ack 的逐实例结果、plugin_status 的实例 detail 共用同一上限。 detail 是「一行事实摘要」,不是日志:超长必须截断。
Variables ¶
var ( ErrNotFound = errors.New("plugincontrol: not found") ErrInvalidState = errors.New("plugincontrol: invalid instance state") ErrPermissionConfirmationRequired = errors.New("plugincontrol: permission confirmation required") )
Sentinel errors returned by the plugin control plane. Callers can compare with errors.Is to distinguish stable failure modes without matching text.
var ErrCacheCorrupt = errors.New("plugincontrol: applied cache corrupt")
ErrCacheCorrupt 表示本地 applied cache 存在但不可解析。
var ErrSecretForbidden = errors.New("plugincontrol: secret forbidden")
ErrSecretForbidden 表示实例配置绑定的 secret handle 不可用:未声明、不存在、 已吊销、路径不安全或文件权限不合规。映射到稳定错误码 api.PluginErrSecretForbidden,实例 reconcile 一律 fail-closed。
Functions ¶
func BoundHandles ¶
BoundHandles 返回 config 中显式绑定的 secret:// handle(排序、去重)。 只返回引用,永不返回值——这是「实例配置显式绑定」这一半校验的事实源, 也可安全用于日志与上报。
func DiscardSecrets ¶
DiscardSecrets 尽快清零并丢弃明文(缩短明文在内存中的存活期)。 v0.1 的 pluginhost 还没有把实例配置送进插件进程的通道,因此 Edge 侧解析 只用于「双校验 + fail-closed」,解析结果立即销毁,绝不落盘、绝不进上报。
func FormatIsolation ¶
func FormatIsolation(value pluginhost.Isolation) string
FormatIsolation converts a pluginhost isolation value into its stable name.
func NormalizeTenant ¶
NormalizeTenant 归一租户标识:空值一律落到 default,绝不落到「全部租户」。
func ParseIsolation ¶
func ParseIsolation(value string) (pluginhost.Isolation, error)
ParseIsolation converts a persisted isolation name into the pluginhost value.
func SanitizeDetail ¶
SanitizeDetail 把适配器/插件给出的摘要收敛成可安全上报的单行事实。 它是 Edge 全部出网 detail 的统一兜底(设备命令 ack 与插件控制面共用一份实现, 不各造一套脱敏规则):
- 折行与控制字符压成空格(禁止把进程 stdout/stderr 原文当 detail 回传);
- 复用 pluginhost.Redact 抹掉 secret / token / password / authorization 形态的值 (单一实现,不另造一份脱敏正则);
- 丢弃 stdout / stderr / log 标记之后的全部内容(进程输出原文一律不出网);
- 抹掉本机绝对路径(Windows 盘符路径与 POSIX 家目录/系统目录路径); 端口名(COM3、/dev/ttyUSB0)保留——它是设备元数据里本来就公开的 port 字段;
- UTF-8 安全截断到 DetailLimit。
上报红线(control-plane-sync.md 不变量 6 与本轮验收要求)由此函数统一兜底: 即使适配器返回了不合规摘要,出网的 detail 也已脱敏。
func SaveAppliedCache ¶
func SaveAppliedCache(path string, cache AppliedCache) error
SaveAppliedCache 用同目录临时文件 + fsync + 原子 rename 落盘(§3.2 要求)。 空路径表示不持久化(进程重启后从 revision 0 开始,靠 Server 全量快照收敛)。
Types ¶
type AppliedCache ¶
type AppliedCache struct {
AppliedRevision uint64 `json:"applied_revision"`
SnapshotDigest string `json:"snapshot_digest"`
Results []api.PluginApplyResultData `json:"results,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
}
AppliedCache 是 Edge 本地插件控制面缓存(control-plane-sync.md §3.2): 最近一次成功应用的 revision、Server 下发的规范化摘要、每实例结果。
刻意**不保存 boot_id**:boot_id 必须每次进程启动都换新(§3.3),从缓存恢复 旧 boot_id 会让 Server 无法识别进程重启、也无法拒绝旧连接的迟到消息。
func LoadAppliedCache ¶
func LoadAppliedCache(path string) (AppliedCache, error)
LoadAppliedCache 读取本地 applied cache。文件缺失 = 从未应用过(零值,非错误)。 文件损坏返回 ErrCacheCorrupt:调用方必须显式记录,然后以零值继续 (声明式全量快照本身就能把 Edge 收敛到 Server 的当前真相,不会因此跑偏)。
type Applier ¶
type Applier interface {
// ApplySnapshot 应用一份完整期望态快照,返回**逐实例**结果(每实例一条)。
// 只要有一条不是 applied,调用方就不推进 revision。
ApplySnapshot(ctx context.Context, tenant string, instances []api.PluginDesiredInstanceData) ([]api.PluginApplyResultData, error)
// Observe 返回本地安装物与实例实际态。实现必须自行脱敏:
// 不含明文 secret、访问令牌、本机绝对路径、插件 stdout/stderr 原文。
Observe(ctx context.Context, tenant string) ([]api.PluginInstallationStatusData, []api.PluginObservedInstanceData, error)
}
Applier 把 Server 下发的**完整**期望态快照应用到本地,并回报本地实际态。
生产实现是 *Host(驱动 pluginhost.Manager + 本地 desired Store);测试注入 fake,任何测试都不启动真实第三方插件进程。
声明式语义:Applier 只收敛「快照描述的最终态」,不回放断线期间的中间操作 (control-plane-sync.md §1/§8)。因此 ApplySnapshot 必须幂等。
type ApplyUpdateOptions ¶
ApplyUpdateOptions selects the instance whose desired version is advanced after a successful install/update.
type Controller ¶
type Controller struct {
// contains filtered or unexported fields
}
Controller persists desired plugin-instance state and validates each write against the installation lockfile and manifest. It does not launch plugin processes; that is the Plugin Host's job.
func NewController ¶
func NewController(opts ControllerOptions) (*Controller, error)
NewController validates required options and applies defaults.
func (*Controller) ApplyUpdateVersion ¶
func (c *Controller) ApplyUpdateVersion(opts ApplyUpdateOptions) (InstanceState, error)
ApplyUpdateVersion advances one instance's desired version after the installer has already validated compatibility/permissions and written the new lockfile/manifest.
func (*Controller) Disable ¶
func (c *Controller) Disable(tenant, instanceID string) (StateResult, error)
Disable persists disabled desired state for one existing instance.
func (*Controller) Enable ¶
func (c *Controller) Enable(opts EnableOptions) (StateResult, error)
Enable persists an enabled instance state after validating that the plugin is installed, its manifest parses, and its version/contract match the lockfile. It never claims the plugin is online.
func (*Controller) Remove ¶
func (c *Controller) Remove(opts RemoveOptions) (RemoveResult, error)
Remove deletes one instance's desired state. Plugin data is preserved by default and only removed when Purge is explicitly true.
func (*Controller) UpdateCheck ¶
func (c *Controller) UpdateCheck(pluginID string, incoming *registry.Manifest, confirm bool) ([]string, error)
UpdateCheck is the pre-install gate for upgrades: it validates the incoming manifest contract and rejects permission expansion unless confirmed. It performs no state or lockfile mutation.
type ControllerOptions ¶
type ControllerOptions struct {
Store *Store
PluginsDir string
LockPath string
SchemaPath string
CoreVersion string
DataDir string
SupportedProtocol int
}
ControllerOptions configures the CLI-facing control plane. All paths are injected so tests can point the controller at a temporary tree and never touch real plugin installs.
type EnableOptions ¶
type EnableOptions struct {
Tenant string
InstanceID string
PluginID string
ConfigPath string
Isolation pluginhost.Isolation
}
EnableOptions is the input for persisting an enabled desired state.
type FileSecrets ¶
type FileSecrets struct {
Root string
MaxBytes int64
// PluginsDir 用于读取插件 manifest 的 permissions.secrets(双校验的另一半)。
PluginsDir string
// Declared 返回插件声明的 secret 名;nil 时用 declaredFromManifest。
// 注入点:测试无需真实插件安装树。
Declared func(pluginID string) ([]string, error)
}
FileSecrets 是文件实现:明文位于 Root/<tenant>/<instance>/<name>, 按 tenant/instance 隔离;权限与路径安全由 internal/secrethandle 强制 (POSIX 要求 0600 等价权限,越界/符号链接/超大文件一律拒绝)。
func NewFileSecrets ¶
func NewFileSecrets(root, pluginsDir string) *FileSecrets
NewFileSecrets 构造文件 secret provider。
func (*FileSecrets) Resolve ¶
func (f *FileSecrets) Resolve(tenant, instanceID, pluginID string, config map[string]string) (map[string][]byte, error)
Resolve 对每个绑定的 handle 做**双校验**后读取明文:
- 插件 manifest 已声明该 secret 名(permissions.secrets);
- 实例配置显式绑定了该 handle(BoundHandles 给出)。
任一条不满足、或 handle 不存在/已吊销(文件被删除即视为吊销)、路径不安全、 文件权限不合规 → 返回 ErrSecretForbidden 并 fail-closed,绝不回落旧明文缓存 (本实现不持有任何缓存,每次都重新读盘)。
type Host ¶
type Host struct {
// contains filtered or unexported fields
}
Host reloads installed plugins and enabled instance states into a Manager and runs until its context is canceled. It is the only component that turns desired state into observed process state.
func NewHost ¶
func NewHost(opts HostOptions) (*Host, error)
NewHost validates options and returns a Host. The manager is not started until Load or Run is called.
func (*Host) ApplySnapshot ¶
func (h *Host) ApplySnapshot(ctx context.Context, tenant string, instances []api.PluginDesiredInstanceData) ([]api.PluginApplyResultData, error)
ApplySnapshot 把一份**完整**期望态快照收敛到本地,返回逐实例结果。
声明式语义(control-plane-sync.md §1/§8):只收敛快照描述的最终态, 不回放断线期间的中间操作;因此本方法幂等,可用同一份快照重复调用。
快照里没有、但本地还留着的实例 = Server 已删除:停用并移除, 默认保留插件数据(purge 是 Server 侧显式高风险选项,Edge 不自作主张)。
func (*Host) InstallationStatus ¶
func (h *Host) InstallationStatus() ([]api.PluginInstallationStatusData, error)
InstallationStatus 从 plugins.lock + 已安装 manifest 组装安装物公开事实。
上报红线:**不含本地路径**(Installation.Path 刻意不出现在这里)、 不含启动参数与环境变量;permissions.secrets 只携带 secret 名称,永不是值。
func (*Host) InstanceObservations ¶
func (h *Host) InstanceObservations(tenant string) []api.PluginObservedInstanceData
InstanceObservations 返回本租户实例的实际态。
desired 与 observed 永远分开:Manager 不可观测时回报 HostOnline=false + STOPPED/UNKNOWN,绝不把「期望启用」渲染成「实际健康」。
func (*Host) Load ¶
func (h *Host) Load(ctx context.Context) (LoadResult, error)
Load registers every lockfile installation and starts every enabled desired instance across all tenants exactly once. Disabled instances are left stopped. It is safe to call Load repeatedly; subsequent calls return the cached result. This is the global mode used by `cloudpath plugin host`.
func (*Host) LoadTenant ¶
LoadTenant registers every lockfile installation but starts only the enabled desired instances of tenant. An empty tenant means "default" and never the global all-tenant set. Used by the edge's in-process driver host so one edge never starts another tenant's instances.
type HostManager ¶
type HostManager interface {
RegisterInstallation(pluginhost.Installation) error
CreateInstance(pluginhost.InstanceSpec) (pluginhost.Instance, error)
Start(tenant, id string) error
// Disable 停用一个实例但保留其定义(期望态 enabled=false 时调用)。
Disable(tenant, id string) error
// Remove 删除一个实例;默认保留插件数据,只有显式 purge 才删。
Remove(tenant, id string, opts ...pluginhost.RemoveOption) (pluginhost.RemoveResult, error)
Close() error
}
HostManager is the seam used by Host to start supervised plugin instances. *pluginhost.Manager satisfies it; tests inject an in-memory fake so no real third-party plugin process is ever launched.
type HostObserver ¶
type HostObserver interface {
ListInstances(tenant string) []pluginhost.InstanceSnapshot
}
HostObserver 由支持实际态读取的 Manager 可选实现(*pluginhost.Manager 满足)。 刻意与 HostManager 分开:观测能力缺席时 Observe 诚实回报「不可观测」, 而不是编造健康态;既有测试替身也不必被迫实现观测方法。
type HostOptions ¶
type HostOptions struct {
Manager HostManager
Store *Store
PluginsDir string
LockPath string
Logger *slog.Logger
// Secrets 是本地 secret provider(§7)。nil 表示本 Edge 不提供本地明文:
// 任何绑定 secret:// handle 的实例都会 fail-closed,绝不静默跳过校验。
Secrets SecretResolver
}
HostOptions configures the long-running plugin host.
type HostReconciler ¶ added in v0.2.14
type HostReconciler interface {
ReconcileInstance(context.Context, pluginhost.InstanceSpec, bool) error
}
HostReconciler replaces an existing runtime binding after checking its candidate session. Kept separate from HostManager for legacy embedders; managers without it may create instances but must not silently accept updates.
type InstanceState ¶
type InstanceState struct {
Tenant string `json:"tenant"`
InstanceID string `json:"instanceId"`
PluginID string `json:"pluginId"`
Version string `json:"version"`
ConfigPath string `json:"configPath,omitempty"`
Enabled bool `json:"enabled"`
Isolation string `json:"isolation"`
// Config 是 Server 期望态里的非敏感配置(control-plane-sync.md §7)。
// 值只允许非敏感标量或 secret://<name> handle:**明文 secret 永不落盘**。
// 保存它是为了 Edge 进程重启且仍离线时,能带着最后一个完整 applied revision
// 的配置续跑(§8「Edge 重启 → 从本地 applied cache 启动」)。
Config map[string]string `json:"config,omitempty"`
}
InstanceState is the durable desired state for one plugin instance. It is stored as one JSON document per tenant/instance id so a tenant can never see or mutate another tenant's state through the shared filesystem.
func (InstanceState) Validate ¶
func (s InstanceState) Validate() error
Validate checks the persisted fields that are required for later reload.
type LoadResult ¶
LoadResult is a factual summary of one host load.
type MetricsReader ¶
type MetricsReader interface {
Metrics(tenant, id string) (pluginhost.Metrics, error)
}
MetricsReader 由 Manager 可选实现:提供 message rate 与 last healthy。
type Observation ¶
type Observation struct {
HostOnline bool
State pluginhost.State
Health pluginhost.Health
}
Observation is the observed runtime state for one instance. The control plane separates desired state (what the CLI was asked to persist) from observed state (what a live Plugin Host actually reports).
func (Observation) String ¶
func (o Observation) String() string
String returns the stable, human-readable observation. A one-shot CLI enable is intentionally reported as STOPPED rather than HEALTHY because the CLI does not run a plugin process.
type RemoveOptions ¶
RemoveOptions controls instance removal. Purge must be set explicitly to delete plugin data; the default preserves it.
type RemoveResult ¶
RemoveResult reports the data-disposition outcome of one Remove.
type SecretResolver ¶
type SecretResolver interface {
// Resolve 返回 config 里显式绑定的每个 handle 的明文(键 = handle 串)。
// 任一 handle 不可用即整体失败:不部分解析、不回落旧明文。
// 返回的错误文本只含 handle 名与稳定原因码,绝不含明文或本机绝对路径。
Resolve(tenant, instanceID, pluginID string, config map[string]string) (map[string][]byte, error)
}
SecretResolver 在 Edge 本地把 secret://<name> handle 解析成明文 (control-plane-sync.md §7)。
边界:Server 只见 handle,永不接收或解析明文;明文只存在于 Edge 本地 provider 与目标插件进程的短期内存。本接口的实现**不得缓存明文**——handle 被吊销后 插件重启必须重新解析并失败,绝不能从任何本地缓存恢复旧明文。
type StateResult ¶
type StateResult struct {
Desired InstanceState
Observed Observation
}
StateResult separates the persisted desired state from the observed runtime state. A one-shot CLI operation has no live host attached, so Observed is STOPPED/UNKNOWN and HostOnline is false.
type Store ¶
type Store struct {
Dir string
}
Store persists instance states under Dir/<tenant>/<instance-id>.json using same-directory atomic renames. Every path component is validated before use so tenant or instance ids can never escape the state root.
func (*Store) ListAll ¶
func (s *Store) ListAll() ([]InstanceState, error)
ListAll returns every tenant's instance states, sorted by tenant then instance id. It is the reload view used by the plugin host.
func (*Store) ListTenant ¶
func (s *Store) ListTenant(tenant string) ([]InstanceState, error)
ListTenant returns every instance state for tenant sorted by instance id.
func (*Store) Load ¶
func (s *Store) Load(tenant, instanceID string) (InstanceState, error)
Load reads one tenant-scoped instance state. A missing file maps to ErrNotFound and never leaks the underlying path.
func (*Store) Save ¶
func (s *Store) Save(state InstanceState) error
Save writes one instance state atomically and fsyncs the temporary file.
type SyncOptions ¶
type SyncOptions struct {
// Tenant 是本 Edge 归属的租户(缺省 default)。期望态/实际态都按它隔离。
Tenant string
// CachePath 是本地 applied cache 路径;空 = 不持久化。
CachePath string
// Applier 必填。
Applier Applier
Logger *slog.Logger
Now func() time.Time
}
SyncOptions 配置 Edge 侧插件控制面收敛器。
type Syncer ¶
type Syncer struct {
// contains filtered or unexported fields
}
Syncer 是 Edge 侧插件控制面收敛器:revision 规则、本地 applied cache、 plugin_status 上报组装(control-plane-sync.md §3.2/§4/§8)。
并发:HandleDesired 全程持锁,因此即使 Server 连发多份快照、或读循环为每条 消息各起协程,应用也严格串行,绝不会出现两份快照交错写本地状态。
func NewSyncer ¶
func NewSyncer(opts SyncOptions) (*Syncer, error)
NewSyncer 校验选项、生成本次进程的 boot_id 并载入本地 applied cache。
func (*Syncer) AppliedRevision ¶
AppliedRevision 返回最近一次成功应用的 revision。
func (*Syncer) HandleDesired ¶
func (s *Syncer) HandleDesired(ctx context.Context, desired api.PluginDesiredData) api.PluginAckData
HandleDesired 按契约规则处理一份 Server 期望态快照并产出 ack:
- new revision > applied → apply,成功才推进 applied_revision
- same revision + same digest → 幂等 ACK,不重复 apply(无副作用)
- same revision + different digest → reject(协议错误,fail-closed,applied 不变)
- old revision → reject(迟到/倒序消息不得让本地状态倒退)
- 空 digest → reject(无法验证同 revision 内容一致性)
- 部分实例失败 → failed,revision **不前进**,逐实例报告