WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package storageurl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
filesvc "github.com/Tencent/WeKnora/internal/application/service/file"
|
||||
"github.com/Tencent/WeKnora/internal/logger"
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
)
|
||||
|
||||
// LocalStorageBaseDir is the on-disk root used when a reference resolves to the
|
||||
// local provider.
|
||||
func LocalStorageBaseDir() string {
|
||||
baseDir := strings.TrimSpace(os.Getenv("LOCAL_STORAGE_BASE_DIR"))
|
||||
if baseDir == "" {
|
||||
baseDir = "/data/files"
|
||||
}
|
||||
return baseDir
|
||||
}
|
||||
|
||||
// FileServiceResolver resolves and caches one FileService per storage provider.
|
||||
// The cache is scoped to a single request or outbound message so a long answer
|
||||
// with many images does not re-create an SDK client per reference.
|
||||
//
|
||||
// Not safe for concurrent use; Rewriter drives it from one goroutine at a time.
|
||||
type FileServiceResolver struct {
|
||||
tenant *types.Tenant
|
||||
defaultSvc interfaces.FileService
|
||||
storageResolver interfaces.StorageBackendResolver
|
||||
ctx context.Context
|
||||
cache map[string]interfaces.FileService
|
||||
}
|
||||
|
||||
// NewFileServiceResolver builds a resolver for tenant. defaultSvc is the
|
||||
// process-wide FileService used for `resource://` handles and as the fallback
|
||||
// when tenant storage config is missing.
|
||||
func NewFileServiceResolver(
|
||||
tenant *types.Tenant,
|
||||
defaultSvc interfaces.FileService,
|
||||
storageResolvers ...interfaces.StorageBackendResolver,
|
||||
) *FileServiceResolver {
|
||||
resolver := &FileServiceResolver{
|
||||
tenant: tenant,
|
||||
defaultSvc: defaultSvc,
|
||||
ctx: context.Background(),
|
||||
cache: make(map[string]interfaces.FileService),
|
||||
}
|
||||
if len(storageResolvers) > 0 {
|
||||
resolver.storageResolver = storageResolvers[0]
|
||||
}
|
||||
return resolver
|
||||
}
|
||||
|
||||
// WithContext sets the context used for backend lookups and their logs.
|
||||
func (r *FileServiceResolver) WithContext(ctx context.Context) *FileServiceResolver {
|
||||
if ctx != nil {
|
||||
r.ctx = ctx
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ResolveFileService implements Resolver.
|
||||
func (r *FileServiceResolver) ResolveFileService(filePath string) interfaces.FileService {
|
||||
if _, ok := types.ParseResourcePath(filePath); ok {
|
||||
return r.defaultSvc
|
||||
}
|
||||
backendID, _, _ := types.ParseStorageBackendPath(filePath)
|
||||
provider := types.ParseProviderScheme(filePath)
|
||||
if provider == "" {
|
||||
if r.tenant != nil && r.tenant.StorageEngineConfig != nil {
|
||||
provider = strings.ToLower(strings.TrimSpace(r.tenant.StorageEngineConfig.DefaultProvider))
|
||||
}
|
||||
if provider == "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
cacheKey := backendID + ":" + provider
|
||||
if svc, ok := r.cache[cacheKey]; ok {
|
||||
return svc
|
||||
}
|
||||
if r.storageResolver != nil && r.tenant != nil {
|
||||
svc, _, err := r.storageResolver.ResolveFileService(r.ctx, r.tenant, backendID, provider, LocalStorageBaseDir())
|
||||
if err == nil {
|
||||
r.cache[cacheKey] = svc
|
||||
return svc
|
||||
}
|
||||
logger.Warnf(r.ctx, "resolve storage backend failed: backend_id=%s provider=%s err=%v",
|
||||
backendID, provider, err)
|
||||
}
|
||||
svc := BuildFileServiceForProvider(r.tenant, provider, r.defaultSvc)
|
||||
if svc != nil {
|
||||
r.cache[cacheKey] = svc
|
||||
}
|
||||
return svc
|
||||
}
|
||||
|
||||
// BuildFileServiceForProvider selects the FileService for a storage provider.
|
||||
// The reference's own scheme wins over the tenant DefaultProvider. It falls back
|
||||
// to the process-wide default FileService (STORAGE_TYPE / env) when tenant
|
||||
// config is missing — mirrors ImageMultimodalService.resolveFileServiceForPayload
|
||||
// (issue #1282).
|
||||
func BuildFileServiceForProvider(
|
||||
tenant *types.Tenant,
|
||||
provider string,
|
||||
defaultSvc interfaces.FileService,
|
||||
) interfaces.FileService {
|
||||
baseDir := LocalStorageBaseDir()
|
||||
var sec *types.StorageEngineConfig
|
||||
if tenant != nil {
|
||||
sec = tenant.StorageEngineConfig
|
||||
}
|
||||
|
||||
svc, _, err := filesvc.NewFileServiceFromStorageConfig(provider, sec, baseDir)
|
||||
if err == nil {
|
||||
return svc
|
||||
}
|
||||
if provider == "local" {
|
||||
externalURL := strings.TrimSpace(os.Getenv("APP_EXTERNAL_URL"))
|
||||
return filesvc.NewLocalFileService(baseDir, externalURL)
|
||||
}
|
||||
if defaultSvc != nil {
|
||||
return defaultSvc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Resolver = (*FileServiceResolver)(nil)
|
||||
Reference in New Issue
Block a user