Something went wrong. Try again.
sloproxy
Something went wrong. Try again.
25 kB · 825 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826package homeplugins
import ( "context" "errors" "fmt" "net/http" "os" "path/filepath" "runtime" "sort" "strings" "time"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" sdkpluginstore "github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginstore" "gopkg.in/yaml.v3")
type Platform struct { GOOS string `json:"goos"` GOARCH string `json:"goarch"`}
type PluginRuntime interface { PluginBusy(id string) bool UnloadPlugin(id string) bool}
type PluginLoadInspector interface { PluginRegistered(id string) bool}
type contextualPluginUnloader interface { UnloadPluginContext(ctx context.Context, id string) bool}
type SyncReport struct { SchemaVersion int `json:"schema_version"` TaskID uint `json:"task_id,omitempty"` Task string `json:"task"` NodeID string `json:"node_id,omitempty"` Status string `json:"status"` Phase string `json:"phase"` OK bool `json:"ok"` StartedAt time.Time `json:"started_at"` FinishedAt time.Time `json:"finished_at,omitempty"` UpdatedAt time.Time `json:"updated_at"` Platform Platform `json:"platform"` Plugins []PluginInstallStatus `json:"plugins"` Error string `json:"error,omitempty"`}
type PluginInstallStatus struct { ID string `json:"id"` Version string `json:"version,omitempty"` ReleaseTag string `json:"release_tag,omitempty"` Repository string `json:"repository,omitempty"` InstallType string `json:"install_type,omitempty"` InstallStatus string `json:"install_status"` LoadStatus string `json:"load_status,omitempty"` Path string `json:"path,omitempty"` Skipped bool `json:"skipped,omitempty"` Overwritten bool `json:"overwritten,omitempty"` Error string `json:"error,omitempty"`}
const ( pluginTaskName = "plugin-sync" pluginDeleteTaskName = "plugin-delete" pluginTaskStatusOK = "success" pluginTaskStatusError = "failed" pluginTaskPhaseInstall = "install" pluginTaskPhaseLoad = "load" pluginTaskPhaseDelete = "delete"
pluginInstallStatusInstalled = "installed" pluginInstallStatusSkipped = "skipped" pluginInstallStatusFailed = "failed" pluginInstallStatusDeleted = "deleted" pluginInstallStatusMissing = "missing" pluginLoadStatusLoaded = "loaded" pluginLoadStatusFailed = "failed")
// CurrentPlatform reports the platform used by pluginhost discovery.func CurrentPlatform() Platform { return Platform{ GOOS: runtime.GOOS, GOARCH: runtime.GOARCH, }}
func NormalizePlatform(platform Platform) Platform { goos := strings.ToLower(strings.TrimSpace(platform.GOOS)) switch goos { case "mac", "macos", "osx": goos = "darwin" } goarch := strings.ToLower(strings.TrimSpace(platform.GOARCH)) switch goarch { case "x64", "x86_64": goarch = "amd64" case "aarch64": goarch = "arm64" } return Platform{GOOS: goos, GOARCH: goarch}}
func Sync(ctx context.Context, cfg *config.Config, pluginRuntime PluginRuntime) error { _, errSync := SyncPlatformWithReport(ctx, cfg, pluginRuntime, CurrentPlatform()) return errSync}
func SyncPlatform(ctx context.Context, cfg *config.Config, pluginRuntime PluginRuntime, platform Platform) error { _, errSync := SyncPlatformWithReport(ctx, cfg, pluginRuntime, platform) return errSync}
func SyncWithReport(ctx context.Context, cfg *config.Config, pluginRuntime PluginRuntime) (SyncReport, error) { return SyncPlatformWithReport(ctx, cfg, pluginRuntime, CurrentPlatform())}
func SyncPlatformWithReport(ctx context.Context, cfg *config.Config, pluginRuntime PluginRuntime, platform Platform) (SyncReport, error) { if cfg == nil || !cfg.Home.Enabled || !cfg.Plugins.Enabled { return newSyncReport(platform), nil } platform = NormalizePlatform(platform) report := newSyncReport(platform) if platform.GOOS == "" { errPlatform := fmt.Errorf("home plugins: goos is required") finishReport(&report, errPlatform) return report, errPlatform } if platform.GOARCH == "" { errPlatform := fmt.Errorf("home plugins: goarch is required") finishReport(&report, errPlatform) return report, errPlatform } report.Platform = platform root, errResolvePluginsDir := config.ResolvePluginsDir(cfg.Plugins.Dir) if errResolvePluginsDir != nil { errPluginsDir := fmt.Errorf("home plugins: %w", errResolvePluginsDir) finishReport(&report, errPluginsDir) return report, errPluginsDir } client := newPluginStoreClient(cfg) var syncErrors []error ids := make([]string, 0, len(cfg.Plugins.Configs)) for id := range cfg.Plugins.Configs { ids = append(ids, id) } sort.Strings(ids) for _, id := range ids { item := cfg.Plugins.Configs[id] if !pluginConfigEnabled(item) { continue } manifest, okManifest, errManifest := storeManifestFromPluginConfig(id, item) if errManifest != nil { status := PluginInstallStatus{ ID: strings.TrimSpace(id), InstallStatus: pluginInstallStatusFailed, Error: errManifest.Error(), } report.Plugins = append(report.Plugins, status) syncErrors = append(syncErrors, errManifest) continue } if !okManifest { continue } status := pluginStatusFromManifest(manifest) result, errSync := installManifest(ctx, client, manifest, root, platform, pluginRuntime) if errSync != nil { status.InstallStatus = pluginInstallStatusFailed status.Error = errSync.Error() report.Plugins = append(report.Plugins, status) syncErrors = append(syncErrors, errSync) continue } status.Path = strings.TrimSpace(result.Path) status.Skipped = result.Skipped status.Overwritten = result.Overwritten if result.Skipped { status.InstallStatus = pluginInstallStatusSkipped } else { status.InstallStatus = pluginInstallStatusInstalled } report.Plugins = append(report.Plugins, status) } errSync := errors.Join(syncErrors...) finishReport(&report, errSync) return report, errSync}
func SyncResolvedWithReport(ctx context.Context, cfg *config.Config, items []sdkpluginstore.PluginSyncItem, expiresAt time.Time, installedVersions map[string]string, pluginRuntime PluginRuntime) (SyncReport, error) { defer func() { for index := range items { items[index].Clear() } }() platform := NormalizePlatform(CurrentPlatform()) report := newSyncReport(platform) if cfg == nil || !cfg.Home.Enabled || !cfg.Plugins.Enabled { finishReport(&report, nil) return report, nil } root, errResolvePluginsDir := config.ResolvePluginsDir(cfg.Plugins.Dir) if errResolvePluginsDir != nil { errPluginsDir := fmt.Errorf("home plugins: %w", errResolvePluginsDir) finishReport(&report, errPluginsDir) return report, errPluginsDir } addInstalledVersionStatuses(&report, cfg, root, installedVersions) var syncErrors []error for index := range items { if !time.Now().UTC().Before(expiresAt) { errExpired := fmt.Errorf("home plugins: plugin sync response expired") syncErrors = append(syncErrors, errExpired) break } item := &items[index] manifest := item.Manifest status := pluginStatusFromManifest(manifest) result, errInstall := installResolvedManifest(ctx, cfg, manifest, item.Auth, expiresAt, root, platform, pluginRuntime) item.Clear() if errInstall != nil { status.InstallStatus = pluginInstallStatusFailed status.Error = errInstall.Error() upsertPluginInstallStatus(&report, status) syncErrors = append(syncErrors, errInstall) continue } status.Path = strings.TrimSpace(result.Path) status.Skipped = result.Skipped status.Overwritten = result.Overwritten if result.Skipped { status.InstallStatus = pluginInstallStatusSkipped } else { status.InstallStatus = pluginInstallStatusInstalled } upsertPluginInstallStatus(&report, status) } errSync := errors.Join(syncErrors...) finishReport(&report, errSync) return report, errSync}
func addInstalledVersionStatuses(report *SyncReport, cfg *config.Config, root string, installedVersions map[string]string) { if report == nil || cfg == nil || len(installedVersions) == 0 { return } ids := make([]string, 0, len(cfg.Plugins.Configs)) for id := range cfg.Plugins.Configs { ids = append(ids, id) } sort.Strings(ids) for _, id := range ids { item := cfg.Plugins.Configs[id] if !pluginConfigEnabled(item) { continue } id = strings.TrimSpace(id) version, okVersion := installedVersions[id] if !okVersion { continue } status := PluginInstallStatus{ ID: id, Version: strings.TrimSpace(version), InstallStatus: pluginInstallStatusSkipped, Skipped: true, } files, errFiles := pluginFileInfos(root, id) if errFiles == nil { for _, file := range files { if strings.TrimSpace(file.Version) == status.Version { status.Path = strings.TrimSpace(file.Path) break } } } manifest, okManifest, errManifest := storeManifestFromPluginConfig(id, item) if errManifest == nil && okManifest && pluginVersionsEqual(status.Version, manifest.Version) { status.ReleaseTag = strings.TrimSpace(manifest.ReleaseTag) status.Repository = strings.TrimSpace(manifest.Repository) status.InstallType = manifest.InstallType() } report.Plugins = append(report.Plugins, status) }}
func pluginVersionsEqual(left string, right string) bool { left = strings.TrimSpace(left) right = strings.TrimSpace(right) if left == "" || right == "" { return false } return !sdkpluginstore.UpdateAvailable(left, right) && !sdkpluginstore.UpdateAvailable(right, left)}
func upsertPluginInstallStatus(report *SyncReport, status PluginInstallStatus) { if report == nil { return } id := strings.TrimSpace(status.ID) for index := range report.Plugins { if strings.TrimSpace(report.Plugins[index].ID) == id { report.Plugins[index] = status return } } report.Plugins = append(report.Plugins, status)}
func installResolvedManifest(ctx context.Context, cfg *config.Config, manifest sdkpluginstore.Manifest, auth []sdkpluginstore.ResolvedAuthConfig, expiresAt time.Time, root string, platform Platform, pluginRuntime PluginRuntime) (sdkpluginstore.InstallResult, error) { client := newResolvedPluginStoreClient(cfg, auth, expiresAt) defer client.ClearAuth() return installManifest(ctx, client, manifest, root, platform, pluginRuntime)}
func InstalledVersions(cfg *config.Config) (map[string]string, error) { if cfg == nil { return map[string]string{}, nil } root, errResolvePluginsDir := config.ResolvePluginsDir(cfg.Plugins.Dir) if errResolvePluginsDir != nil { return nil, fmt.Errorf("home plugins: %w", errResolvePluginsDir) } versions := make(map[string]string, len(cfg.Plugins.Configs)) for id := range cfg.Plugins.Configs { files, errFiles := pluginFileInfos(root, id) if errFiles != nil { return nil, fmt.Errorf("home plugins: discover installed plugin %s: %w", id, errFiles) } if len(files) == 0 { continue } version := strings.TrimSpace(files[0].Version) if version != "" { versions[strings.TrimSpace(id)] = version } } return versions, nil}
func installManifest(ctx context.Context, client sdkpluginstore.Client, manifest sdkpluginstore.Manifest, root string, platform Platform, pluginRuntime PluginRuntime) (sdkpluginstore.InstallResult, error) { id := strings.TrimSpace(manifest.ID) if id == "" { return sdkpluginstore.InstallResult{}, fmt.Errorf("home plugins: manifest plugin id is empty") } pluginIsBusy := func() bool { return pluginRuntime != nil && pluginRuntime.PluginBusy(id) } result, errInstall := client.InstallManifest(ctx, manifest, sdkpluginstore.InstallOptions{ PluginsDir: root, GOOS: platform.GOOS, GOARCH: platform.GOARCH, PluginLoaded: pluginIsBusy, }) if errInstall != nil { return sdkpluginstore.InstallResult{}, fmt.Errorf("home plugins: install %s: %w", id, errInstall) } return result, nil}
func DeleteWithReport(ctx context.Context, cfg *config.Config, pluginRuntime PluginRuntime, taskID uint, pluginID string) SyncReport { if ctx == nil { ctx = context.Background() } platform := CurrentPlatform() report := newSyncReport(platform) report.TaskID = taskID report.Task = pluginDeleteTaskName report.Phase = pluginTaskPhaseDelete pluginID = strings.TrimSpace(pluginID) status := PluginInstallStatus{ID: pluginID} if errContext := ctx.Err(); errContext != nil { status.InstallStatus = pluginInstallStatusFailed status.Error = errContext.Error() report.Plugins = append(report.Plugins, status) finishReport(&report, errContext) return report } if cfg == nil { status.InstallStatus = pluginInstallStatusFailed status.Error = "home plugins: config is nil" report.Plugins = append(report.Plugins, status) finishReport(&report, errors.New(status.Error)) return report } root, errResolvePluginsDir := config.ResolvePluginsDir(cfg.Plugins.Dir) if errResolvePluginsDir != nil { errPluginsDir := fmt.Errorf("home plugins: %w", errResolvePluginsDir) status.InstallStatus = pluginInstallStatusFailed status.Error = errPluginsDir.Error() report.Plugins = append(report.Plugins, status) finishReport(&report, errPluginsDir) return report } if errContext := ctx.Err(); errContext != nil { status.InstallStatus = pluginInstallStatusFailed status.Error = errContext.Error() report.Plugins = append(report.Plugins, status) finishReport(&report, errContext) return report } path, deleted, errDelete := deletePluginArtifact(ctx, root, pluginID, pluginRuntime) status.Path = strings.TrimSpace(path) switch { case errDelete != nil: status.InstallStatus = pluginInstallStatusFailed status.Error = errDelete.Error() case deleted: status.InstallStatus = pluginInstallStatusDeleted default: status.InstallStatus = pluginInstallStatusMissing } report.Plugins = append(report.Plugins, status) finishReport(&report, errDelete) return report}
func deletePluginArtifact(ctx context.Context, root string, id string, pluginRuntime PluginRuntime) (string, bool, error) { if ctx == nil { ctx = context.Background() } if errContext := ctx.Err(); errContext != nil { return "", false, errContext } id = strings.TrimSpace(id) if !validPluginFileID(id) { return "", false, fmt.Errorf("invalid plugin id %q", id) } paths, errPaths := pluginFilePaths(root, id) if errPaths != nil { return "", false, errPaths } if errContext := ctx.Err(); errContext != nil { return "", false, errContext } if len(paths) == 0 { return "", false, nil } if pluginRuntime != nil && pluginRuntime.PluginBusy(id) { if errContext := ctx.Err(); errContext != nil { return paths[0], false, errContext } unloaded := false if contextual, ok := pluginRuntime.(contextualPluginUnloader); ok { unloaded = contextual.UnloadPluginContext(ctx, id) } else { unloaded = pluginRuntime.UnloadPlugin(id) } if !unloaded && pluginRuntime.PluginBusy(id) { return paths[0], false, sdkpluginstore.ErrLoadedPluginLocked } } deleted := false for _, path := range paths { if errContext := ctx.Err(); errContext != nil { return paths[0], deleted, errContext } if errRemove := os.Remove(path); errRemove != nil { if errors.Is(errRemove, os.ErrNotExist) { continue } return paths[0], deleted, errRemove } deleted = true if errContext := ctx.Err(); errContext != nil { return paths[0], deleted, errContext } } return paths[0], deleted, nil}
func currentPluginFilePath(root string, id string) (string, error) { paths, errPaths := pluginFilePaths(root, id) if errPaths != nil { return "", errPaths } if len(paths) == 0 { return "", nil } return paths[0], nil}
func pluginFilePaths(root string, id string) ([]string, error) { files, errFiles := pluginFileInfos(root, id) if errFiles != nil { return nil, errFiles } out := make([]string, 0, len(files)) for _, file := range files { out = append(out, file.Path) } return out, nil}
func pluginFileInfos(root string, id string) ([]pluginFileInfo, error) { root = strings.TrimSpace(root) if root == "" { root = "plugins" } id = strings.TrimSpace(id) platform := CurrentPlatform() extension := pluginExtension(platform.GOOS) candidates := make([]pluginFileInfo, 0) for _, dir := range pluginCandidateDirs(root, platform.GOOS, platform.GOARCH) { entries, errReadDir := os.ReadDir(dir) if errReadDir != nil { if errors.Is(errReadDir, os.ErrNotExist) { continue } return nil, errReadDir } files := make([]string, 0, len(entries)) for _, entry := range entries { if entry == nil || !entry.Type().IsRegular() { continue } if strings.HasSuffix(strings.ToLower(entry.Name()), extension) { files = append(files, filepath.Join(dir, entry.Name())) } } sort.Strings(files) for _, filePath := range files { file, okFile := pluginFileFromPath(filePath, extension) if !okFile || file.ID != id { continue } candidates = append(candidates, file) } } if len(candidates) <= 1 { return candidates, nil } bestIndex := 0 for index := 1; index < len(candidates); index++ { if pluginFilePreferred(candidates[index], candidates[bestIndex]) { bestIndex = index } } if bestIndex == 0 { return candidates, nil } out := make([]pluginFileInfo, 0, len(candidates)) out = append(out, candidates[bestIndex]) for index, candidate := range candidates { if index == bestIndex { continue } out = append(out, candidate) } return out, nil}
type pluginFileInfo struct { ID string Path string Version string}
func pluginCandidateDirs(root string, goos string, goarch string) []string { dirs := make([]string, 0, 2) dirs = append(dirs, filepath.Join(root, goos, goarch)) dirs = append(dirs, root) return dirs}
func pluginIDFromPath(path string) string { file, ok := pluginFileFromPath(path, "") if ok { return file.ID } base := filepath.Base(path) lowerBase := strings.ToLower(base) for _, extension := range []string{".so", ".dylib", ".dll"} { if strings.HasSuffix(lowerBase, extension) { return base[:len(base)-len(extension)] } } return base}
func pluginFileFromPath(filePath string, requiredExtension string) (pluginFileInfo, bool) { base := filepath.Base(filePath) lowerBase := strings.ToLower(base) extension := strings.TrimSpace(requiredExtension) if extension != "" { if !strings.HasSuffix(lowerBase, strings.ToLower(extension)) { return pluginFileInfo{}, false } } else { for _, candidateExtension := range []string{".so", ".dylib", ".dll"} { if strings.HasSuffix(lowerBase, candidateExtension) { extension = candidateExtension break } } if extension == "" { return pluginFileInfo{}, false } } name := base[:len(base)-len(extension)] id := name version := "" if versionIndex := strings.LastIndex(name, "-v"); versionIndex > 0 { candidateID := name[:versionIndex] candidateVersion := name[versionIndex+2:] if validPluginFileID(candidateID) && validPluginFileVersion(candidateVersion) { id = candidateID version = candidateVersion } } if !validPluginFileID(id) { return pluginFileInfo{}, false } return pluginFileInfo{ID: id, Path: filePath, Version: version}, true}
func pluginFilePreferred(candidate pluginFileInfo, current pluginFileInfo) bool { if strings.TrimSpace(current.Path) == "" { return true } if candidate.Version == "" { return false } if current.Version == "" { return true } return sdkpluginstore.UpdateAvailable(current.Version, candidate.Version)}
func pluginExtension(goos string) string { switch strings.ToLower(strings.TrimSpace(goos)) { case "darwin", "mac", "macos", "osx": return ".dylib" case "windows": return ".dll" default: return ".so" }}
func validPluginFileID(id string) bool { id = strings.TrimSpace(id) if id == "" || id == "." || id == ".." || strings.ContainsAny(id, `/\`) { return false } for _, char := range id { switch { case char >= 'a' && char <= 'z': case char >= 'A' && char <= 'Z': case char >= '0' && char <= '9': case char == '-', char == '_', char == '.': default: return false } } return true}
func validPluginFileVersion(version string) bool { version = strings.TrimSpace(version) if version == "" || strings.HasPrefix(version, "v") { return false } first := version[0] return first >= '0' && first <= '9'}
func MarkLoadResults(report *SyncReport, inspector PluginLoadInspector) error { if report == nil { return nil } report.Phase = pluginTaskPhaseLoad var loadErrors []error preserveSyncError := !report.OK && strings.TrimSpace(report.Error) != "" if preserveSyncError { loadErrors = append(loadErrors, errors.New(report.Error)) } for index := range report.Plugins { status := &report.Plugins[index] if status.InstallStatus == pluginInstallStatusFailed { if status.LoadStatus == "" { status.LoadStatus = pluginInstallStatusSkipped } if !preserveSyncError { if strings.TrimSpace(status.Error) != "" { loadErrors = append(loadErrors, errors.New(status.Error)) } else { loadErrors = append(loadErrors, fmt.Errorf("home plugins: plugin %s install failed", status.ID)) } } continue } if inspector != nil && inspector.PluginRegistered(status.ID) { status.LoadStatus = pluginLoadStatusLoaded continue } status.LoadStatus = pluginLoadStatusFailed errLoad := fmt.Errorf("home plugins: plugin %s installed but not loaded", status.ID) if strings.TrimSpace(status.Error) == "" { status.Error = errLoad.Error() } loadErrors = append(loadErrors, errLoad) } errLoad := errors.Join(loadErrors...) finishReport(report, errLoad) return errLoad}
func newSyncReport(platform Platform) SyncReport { now := time.Now().UTC() return SyncReport{ SchemaVersion: 1, Task: pluginTaskName, Status: pluginTaskStatusOK, Phase: pluginTaskPhaseInstall, OK: true, StartedAt: now, UpdatedAt: now, Platform: NormalizePlatform(platform), Plugins: []PluginInstallStatus{}, }}
// CompletedSyncReport builds a completed report for outcomes before plugin installation starts.func CompletedSyncReport(platform Platform, errSync error) SyncReport { report := newSyncReport(platform) finishReport(&report, errSync) return report}
func finishReport(report *SyncReport, errTask error) { if report == nil { return } now := time.Now().UTC() report.FinishedAt = now report.UpdatedAt = now report.OK = errTask == nil if errTask != nil { report.Status = pluginTaskStatusError report.Error = errTask.Error() return } report.Status = pluginTaskStatusOK report.Error = ""}
func pluginStatusFromManifest(manifest sdkpluginstore.Manifest) PluginInstallStatus { return PluginInstallStatus{ ID: strings.TrimSpace(manifest.ID), Version: strings.TrimSpace(manifest.Version), ReleaseTag: strings.TrimSpace(manifest.ReleaseTag), Repository: strings.TrimSpace(manifest.Repository), InstallType: manifest.InstallType(), InstallStatus: pluginInstallStatusFailed, }}
func storeManifestFromPluginConfig(id string, item config.PluginInstanceConfig) (sdkpluginstore.Manifest, bool, error) { if item.Raw.Kind == 0 { return sdkpluginstore.Manifest{}, false, nil } storeNode := yamlMappingValue(&item.Raw, "store") if storeNode == nil || storeNode.Kind == 0 { return sdkpluginstore.Manifest{}, false, nil } var manifest sdkpluginstore.Manifest if errDecode := storeNode.Decode(&manifest); errDecode != nil { return sdkpluginstore.Manifest{}, false, fmt.Errorf("home plugins: decode store manifest for %s: %w", id, errDecode) } if strings.TrimSpace(manifest.ID) == "" { manifest.ID = strings.TrimSpace(id) } if errValidate := manifest.Validate(); errValidate != nil { return sdkpluginstore.Manifest{}, false, fmt.Errorf("home plugins: invalid store manifest for %s: %w", id, errValidate) } return manifest, true, nil}
func yamlMappingValue(node *yaml.Node, key string) *yaml.Node { if node == nil || node.Kind != yaml.MappingNode { return nil } for i := 0; i+1 < len(node.Content); i += 2 { keyNode := node.Content[i] if keyNode == nil || keyNode.Value != key { continue } return node.Content[i+1] } return nil}
var newPluginStoreClient = func(cfg *config.Config) sdkpluginstore.Client { client := &http.Client{} var storeAuth []sdkpluginstore.AuthConfig if cfg != nil && strings.TrimSpace(cfg.ProxyURL) != "" { util.SetProxy(&sdkconfig.SDKConfig{ProxyURL: strings.TrimSpace(cfg.ProxyURL)}, client) } if cfg != nil { storeAuth = cfg.Plugins.StoreAuth } return sdkpluginstore.NewClientWithAuth(client, "", storeAuth)}
var newResolvedPluginStoreClient = func(cfg *config.Config, auth []sdkpluginstore.ResolvedAuthConfig, expiresAt time.Time) sdkpluginstore.Client { client := &http.Client{} if cfg != nil && strings.TrimSpace(cfg.ProxyURL) != "" { util.SetProxy(&sdkconfig.SDKConfig{ProxyURL: strings.TrimSpace(cfg.ProxyURL)}, client) } return sdkpluginstore.NewClientWithResolvedAuthExpiry(client, "", auth, expiresAt)}
func pluginConfigEnabled(item config.PluginInstanceConfig) bool { return item.Enabled != nil && *item.Enabled}