Files
silo-server/internal/plugins/catalog_settings.go

186 lines
6.3 KiB
Go

package plugins
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type CatalogSettings struct {
IncludeApprovedCommunityPlugins bool
ApprovedCommunityPluginCount int
InstalledCommunityPluginCount int
MigratedPluginCount int
CommunityUpdatesPaused bool
}
type ManagedRepositoryReconcileResult struct {
RepositoriesCreated int
}
func (s *RepositoryStore) ReconcileManaged(ctx context.Context) (ManagedRepositoryReconcileResult, error) {
tx, err := s.pool.Begin(ctx)
if err != nil {
return ManagedRepositoryReconcileResult{}, fmt.Errorf("begin managed plugin repository reconciliation: %w", err)
}
defer tx.Rollback(ctx)
if _, err := tx.Exec(ctx, `
INSERT INTO server_settings (key, value)
VALUES ($1, 'false')
ON CONFLICT (key) DO NOTHING
`, IncludeApprovedCommunityPluginsSetting); err != nil {
return ManagedRepositoryReconcileResult{}, fmt.Errorf("seed approved community plugin setting: %w", err)
}
includeCommunity, err := readIncludeApprovedCommunityForUpdate(ctx, tx)
if err != nil {
return ManagedRepositoryReconcileResult{}, err
}
created, err := reconcileManagedRepositories(ctx, tx, includeCommunity)
if err != nil {
return ManagedRepositoryReconcileResult{}, err
}
if err := tx.Commit(ctx); err != nil {
return ManagedRepositoryReconcileResult{}, fmt.Errorf("commit managed plugin repository reconciliation: %w", err)
}
return ManagedRepositoryReconcileResult{RepositoriesCreated: created}, nil
}
func (s *RepositoryStore) GetCatalogSettings(ctx context.Context) (CatalogSettings, error) {
includeCommunity, err := readIncludeApprovedCommunity(ctx, s.pool)
if err != nil {
return CatalogSettings{}, err
}
var installedCommunityCount int
if err := s.pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM plugin_installations AS installation
JOIN plugin_repositories AS repository ON repository.id = installation.repository_id
WHERE repository.source_kind = $1
`, RepositorySourceApprovedCommunity).Scan(&installedCommunityCount); err != nil {
return CatalogSettings{}, fmt.Errorf("count installed approved community plugins: %w", err)
}
migratedPluginCount, err := readIntegerSetting(ctx, s.pool, MigratedApprovedCommunityCountSetting)
if err != nil {
return CatalogSettings{}, err
}
return CatalogSettings{
IncludeApprovedCommunityPlugins: includeCommunity,
ApprovedCommunityPluginCount: len(approvedCommunityPluginIDs),
InstalledCommunityPluginCount: installedCommunityCount,
MigratedPluginCount: migratedPluginCount,
CommunityUpdatesPaused: !includeCommunity && installedCommunityCount > 0,
}, nil
}
func (s *RepositoryStore) SetIncludeApprovedCommunity(ctx context.Context, include bool) (CatalogSettings, error) {
tx, err := s.pool.Begin(ctx)
if err != nil {
return CatalogSettings{}, fmt.Errorf("begin approved community plugin setting update: %w", err)
}
defer tx.Rollback(ctx)
if _, err := tx.Exec(ctx, `
INSERT INTO server_settings (key, value)
VALUES ($1, $2)
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value
`, IncludeApprovedCommunityPluginsSetting, strconv.FormatBool(include)); err != nil {
return CatalogSettings{}, fmt.Errorf("update approved community plugin setting: %w", err)
}
if _, err := reconcileManagedRepositories(ctx, tx, include); err != nil {
return CatalogSettings{}, err
}
if err := tx.Commit(ctx); err != nil {
return CatalogSettings{}, fmt.Errorf("commit approved community plugin setting update: %w", err)
}
return s.GetCatalogSettings(ctx)
}
type catalogSettingsQuerier interface {
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}
type catalogSettingsExecutor interface {
catalogSettingsQuerier
Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error)
}
func readIncludeApprovedCommunity(ctx context.Context, querier catalogSettingsQuerier) (bool, error) {
return readIncludeApprovedCommunityQuery(ctx, querier, `SELECT value FROM server_settings WHERE key = $1`)
}
func readIncludeApprovedCommunityForUpdate(ctx context.Context, querier catalogSettingsQuerier) (bool, error) {
return readIncludeApprovedCommunityQuery(ctx, querier, `SELECT value FROM server_settings WHERE key = $1 FOR UPDATE`)
}
func readIncludeApprovedCommunityQuery(ctx context.Context, querier catalogSettingsQuerier, query string) (bool, error) {
var value string
err := querier.QueryRow(ctx, query, IncludeApprovedCommunityPluginsSetting).Scan(&value)
if err != nil {
if err == pgx.ErrNoRows {
return false, nil
}
return false, fmt.Errorf("read approved community plugin setting: %w", err)
}
parsed, err := strconv.ParseBool(strings.TrimSpace(value))
if err != nil {
return false, nil
}
return parsed, nil
}
func readIntegerSetting(ctx context.Context, querier catalogSettingsQuerier, key string) (int, error) {
var value string
err := querier.QueryRow(ctx, `SELECT value FROM server_settings WHERE key = $1`, key).Scan(&value)
if err != nil {
if err == pgx.ErrNoRows {
return 0, nil
}
return 0, fmt.Errorf("read plugin setting %q: %w", key, err)
}
parsed, err := strconv.Atoi(strings.TrimSpace(value))
if err != nil || parsed < 0 {
return 0, nil
}
return parsed, nil
}
func reconcileManagedRepositories(ctx context.Context, executor catalogSettingsExecutor, includeCommunity bool) (int, error) {
created := 0
for _, definition := range managedRepositoryDefinitions {
var exists bool
if err := executor.QueryRow(ctx, `SELECT EXISTS (SELECT 1 FROM plugin_repositories WHERE url = $1)`, definition.URL).Scan(&exists); err != nil {
return 0, fmt.Errorf("check managed plugin repository %q: %w", definition.Key, err)
}
enabled := definition.Key == OfficialRepositoryManagedKey || includeCommunity
if _, err := executor.Exec(ctx, `
INSERT INTO plugin_repositories (url, display_name, enabled, managed_key, source_kind)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (url) DO UPDATE SET
display_name = EXCLUDED.display_name,
enabled = EXCLUDED.enabled,
managed_key = EXCLUDED.managed_key,
source_kind = EXCLUDED.source_kind,
updated_at = NOW()
`, definition.URL, definition.DisplayName, enabled, definition.Key, definition.SourceKind); err != nil {
return 0, fmt.Errorf("reconcile managed plugin repository %q: %w", definition.Key, err)
}
if !exists {
created++
}
}
return created, nil
}