Skip to content

Commit cdb07ef

Browse files
committed
feat(plugin-distribution): Introduce builtin plugin distribution management
- Added `distribution` field to plugin manifest to distinguish between `marketplace` and `builtin` plugins. - Implemented server-side governance to restrict management actions on `builtin` plugins, ensuring they are automatically installed, enabled, and upgraded during startup. - Updated plugin management UI to hide `builtin` plugins from ordinary management views, allowing only read-only access through controlled diagnostic queries. - Enhanced plugin lifecycle management to ensure `builtin` plugins adhere to specific governance rules, including dependency checks and upgrade protocols. - Refactored related specifications and tasks to align with the new distribution model, ensuring comprehensive coverage in API responses and frontend behavior. - Updated documentation and tests to reflect changes in plugin management and governance.
1 parent f5314fd commit cdb07ef

91 files changed

Lines changed: 1968 additions & 94 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/lina-core/api/job/v1/job_create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type JobPayload struct {
2727
MaxConcurrency int `json:"maxConcurrency" d:"1" v:"min:1|max:100" dc:"Maximum number of concurrencies; effective when concurrency=parallel" eg:"1"`
2828
MaxExecutions int `json:"maxExecutions" d:"0" v:"min:0" dc:"Maximum number of executions: 0 = no limit" eg:"0"`
2929
Status Status `json:"status" d:"disabled" v:"required|in:enabled,disabled" dc:"Task status: enabled=enabled disabled=disabled; paused_by_plugin is a system managed state and does not allow writing when creating or editing" eg:"enabled"`
30-
LogRetentionOverride *LogRetentionOption `json:"logRetentionOverride" dc:"Task-level log retention policy; if not passed, follow the system parameter cron.log.retention" eg:"{\"mode\":\"days\",\"value\":60}"`
30+
LogRetentionOverride *LogRetentionOption `json:"logRetentionOverride" dc:"Task-level log retention policy; if not passed, follow the system parameter sys.cron.log.retention" eg:"{\"mode\":\"days\",\"value\":60}"`
3131
}
3232

3333
// CreateReq defines the request for creating one scheduled job.

apps/lina-core/api/plugin/plugin.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/lina-core/api/plugin/v1/plugin.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ const (
99
PluginTypeDynamic PluginType = "dynamic"
1010
)
1111

12+
// PluginDistribution identifies the host governance model for plugin delivery.
13+
type PluginDistribution string
14+
15+
// Supported plugin distribution governance values.
16+
const (
17+
PluginDistributionMarketplace PluginDistribution = "marketplace"
18+
PluginDistributionBuiltin PluginDistribution = "builtin"
19+
)
20+
1221
// RuntimeState identifies whether discovered plugin files match effective state.
1322
type RuntimeState string
1423

apps/lina-core/api/plugin/v1/plugin_list.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type ListReq struct {
1818
Type PluginType `json:"type" dc:"Filter by plugin type: source=source plugin dynamic=dynamic plugin, if not passed, all will be queried; the current dynamic plugin implementation only supports WASM" eg:"dynamic"`
1919
Status *statusflag.Enabled `json:"status" dc:"Filter by enabled status: 1=enabled 0=disabled, if not passed, query all" eg:"1"`
2020
Installed *statusflag.Installation `json:"installed" dc:"Filter by installation status: 1=Installed 0=Not installed, if not uploaded, query all" eg:"1"`
21+
// IncludeBuiltin includes project built-in source plugins for read-only diagnostics. The default false value hides builtin plugins from ordinary plugin management.
22+
IncludeBuiltin bool `json:"includeBuiltin" dc:"Whether to include builtin plugins in this read-only diagnostic list query; default false hides builtin plugins from ordinary plugin management" eg:"false"`
2123
}
2224

2325
// ListRes is the response for querying plugin list.
@@ -38,6 +40,7 @@ type PluginListItem struct {
3840
AbnormalReason RuntimeAbnormalReason `json:"abnormalReason,omitempty" dc:"Stable diagnostic reason when runtimeState is abnormal" eg:"discovered_version_lower_than_effective"`
3941
LastUpgradeFailure *PluginUpgradeFailureItem `json:"lastUpgradeFailure,omitempty" dc:"Latest observable runtime upgrade failure summary for list diagnosis" eg:"{}"`
4042
Type PluginType `json:"type" dc:"Plugin first-level type: source=source plugin dynamic=dynamic plugin" eg:"source"`
43+
Distribution PluginDistribution `json:"distribution" dc:"Plugin distribution governance type: marketplace=ordinary managed plugin builtin=project built-in source plugin" eg:"marketplace"`
4144
Description string `json:"description" dc:"Plugin description" eg:"Source plugin that provides left-side menu pages and public/protected routing examples"`
4245
Installed statusflag.Installation `json:"installed" dc:"Installation status: 1=installed 0=not installed; the source plugin can still be in the uninstalled state by default after being discovered by the host" eg:"1"`
4346
InstalledAt *int64 `json:"installedAt" dc:"Plugin installation time as Unix timestamp in milliseconds, empty if it is not installed" eg:"1767240000000"`
@@ -66,6 +69,7 @@ type PluginItem struct {
6669
AbnormalReason RuntimeAbnormalReason `json:"abnormalReason,omitempty" dc:"Stable diagnostic reason when runtimeState is abnormal" eg:"discovered_version_lower_than_effective"`
6770
LastUpgradeFailure *PluginUpgradeFailureItem `json:"lastUpgradeFailure,omitempty" dc:"Latest observable runtime upgrade failure details" eg:"{}"`
6871
Type PluginType `json:"type" dc:"Plugin first-level type: source=source plugin dynamic=dynamic plugin" eg:"source"`
72+
Distribution PluginDistribution `json:"distribution" dc:"Plugin distribution governance type: marketplace=ordinary managed plugin builtin=project built-in source plugin" eg:"marketplace"`
6973
Description string `json:"description" dc:"Plugin description" eg:"Source plugin that provides left-side menu pages and public/protected routing examples"`
7074
Installed statusflag.Installation `json:"installed" dc:"Installation status: 1=installed 0=not installed; the source plugin can still be in the uninstalled state by default after being discovered by the host" eg:"1"`
7175
InstalledAt *int64 `json:"installedAt" dc:"Plugin installation time as Unix timestamp in milliseconds, empty if it is not installed" eg:"1767240000000"`

apps/lina-core/internal/cmd/internal/httpstartup/httpstartup_runtime.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,14 @@ func startHTTPRuntime(ctx context.Context, runtime *httpRuntime) error {
447447
func startHTTPRuntimeBeforeSourceRoutes(ctx context.Context, runtime *httpRuntime) error {
448448
runtime.clusterSvc.Start(ctx)
449449

450-
// Auto-enable and source-upgrade drift scanning run before plugin routes and
451-
// cron jobs are registered so plugin management can surface runtime state.
450+
// Builtin reconciliation, auto-enable, and source-upgrade drift scanning run
451+
// before plugin routes and cron jobs are registered so plugin management can
452+
// surface runtime state.
453+
if err := startupstats.Observe(ctx, startupstats.PhasePluginBootstrapBuiltin, func() error {
454+
return runtime.pluginSvc.BootstrapBuiltinPlugins(ctx)
455+
}); err != nil {
456+
return err
457+
}
452458
if err := startupstats.Observe(ctx, startupstats.PhasePluginBootstrapAutoEnable, func() error {
453459
return runtime.pluginSvc.BootstrapAutoEnable(ctx)
454460
}); err != nil {

apps/lina-core/internal/cmd/internal/httpstartup/httpstartup_runtime_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func TestLogHTTPStartupSummaryEmitsFieldsWithoutSQL(t *testing.T) {
3232
collector.Add(startupstats.CounterPluginScans, 1)
3333
collector.Add(startupstats.CounterPluginSyncChanged, 2)
3434
collector.Add(startupstats.CounterPluginSyncNoop, 3)
35+
collector.RecordPhase(startupstats.PhasePluginBootstrapBuiltin, 9)
3536
collector.RecordPhase(startupstats.PhasePluginBootstrapAutoEnable, 12)
3637
collector.RecordPhase(startupstats.PhasePluginStartupConsistency, 4)
3738

apps/lina-core/internal/controller/plugin/plugin_v1_list.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ import (
1515
// List scans plugins and returns current synchronized status list.
1616
func (c *ControllerV1) List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) {
1717
out, err := c.pluginSvc.List(ctx, pluginsvc.ListInput{
18-
PageNum: req.PageNum,
19-
PageSize: req.PageSize,
20-
ID: req.Id,
21-
Name: req.Name,
22-
Type: string(req.Type),
23-
Status: enabledPtrToInt(req.Status),
24-
Installed: installationPtrToInt(req.Installed),
18+
PageNum: req.PageNum,
19+
PageSize: req.PageSize,
20+
ID: req.Id,
21+
Name: req.Name,
22+
Type: string(req.Type),
23+
Status: enabledPtrToInt(req.Status),
24+
Installed: installationPtrToInt(req.Installed),
25+
IncludeBuiltin: req.IncludeBuiltin,
2526
})
2627
if err != nil {
2728
return nil, err
@@ -60,6 +61,7 @@ func (c *ControllerV1) buildPluginListItemResponse(
6061
AbnormalReason: v1.RuntimeAbnormalReason(item.AbnormalReason.String()),
6162
LastUpgradeFailure: buildPluginUpgradeFailureItem(item.LastUpgradeFailure),
6263
Type: v1.PluginType(item.Type),
64+
Distribution: v1.PluginDistribution(item.Distribution),
6365
Description: item.Description,
6466
Installed: statusflag.Installation(item.Installed),
6567
InstalledAt: item.InstalledAt,
@@ -98,6 +100,7 @@ func (c *ControllerV1) buildPluginItemResponse(
98100
AbnormalReason: v1.RuntimeAbnormalReason(item.AbnormalReason.String()),
99101
LastUpgradeFailure: buildPluginUpgradeFailureItem(item.LastUpgradeFailure),
100102
Type: v1.PluginType(item.Type),
103+
Distribution: v1.PluginDistribution(item.Distribution),
101104
Description: item.Description,
102105
Installed: statusflag.Installation(item.Installed),
103106
InstalledAt: item.InstalledAt,

apps/lina-core/internal/controller/plugin/plugin_v1_list_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33

44
package plugin
55

6-
import "testing"
6+
import (
7+
"testing"
8+
9+
v1 "lina-core/api/plugin/v1"
10+
pluginsvc "lina-core/internal/service/plugin"
11+
)
712

813
// TestBuildAutoEnableManagedSetNormalizesPluginIDs verifies startup-managed
914
// plugin IDs are trimmed and de-duplicated before list projection uses them.
@@ -28,3 +33,22 @@ func TestBuildAutoEnableManagedSetNormalizesPluginIDs(t *testing.T) {
2833
t.Fatal("expected blank plugin ID to be ignored")
2934
}
3035
}
36+
37+
// TestBuildPluginListItemResponseIncludesDistribution verifies API projection
38+
// exposes plugin distribution governance metadata.
39+
func TestBuildPluginListItemResponseIncludesDistribution(t *testing.T) {
40+
controller := &ControllerV1{}
41+
pluginItem := &pluginsvc.PluginItem{}
42+
pluginItem.Id = "plugin-dev-controller-builtin"
43+
pluginItem.Name = "Controller Builtin"
44+
pluginItem.Type = string(v1.PluginTypeSource)
45+
pluginItem.Distribution = string(v1.PluginDistributionBuiltin)
46+
47+
item := controller.buildPluginListItemResponse(pluginItem, false)
48+
if item == nil {
49+
t.Fatal("expected plugin list item response")
50+
}
51+
if item.Distribution != v1.PluginDistributionBuiltin {
52+
t.Fatalf("expected builtin distribution in API projection, got %q", item.Distribution)
53+
}
54+
}

apps/lina-core/internal/dao/internal/sys_plugin.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/lina-core/internal/model/do/sys_plugin.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)