backend: support external SPARQL and named-graph snapshots

This commit is contained in:
Oxy8
2026-04-06 13:36:08 -03:00
parent 696844f341
commit 44c1d3eaa6
25 changed files with 1695 additions and 243 deletions

View File

@@ -23,15 +23,22 @@ type Config struct {
FreeOSMemoryAfterSnapshot bool
LogSnapshotTimings bool
SparqlSourceMode string
SparqlHost string
SparqlEndpoint string
ExternalSparqlEndpoint string
AccessToken string
KeycloakTokenEndpoint string
KeycloakClientID string
KeycloakUsername string
KeycloakPassword string
KeycloakScope string
SparqlUser string
SparqlPass string
SparqlInsecureTLS bool
SparqlDataFile string
SparqlGraphIRI string
SparqlLoadOnStart bool
SparqlClearOnStart bool
SparqlTimeout time.Duration
SparqlReadyRetries int
@@ -60,20 +67,27 @@ func LoadConfig() (Config, error) {
FreeOSMemoryAfterSnapshot: envBool("FREE_OS_MEMORY_AFTER_SNAPSHOT", false),
LogSnapshotTimings: envBool("LOG_SNAPSHOT_TIMINGS", false),
SparqlSourceMode: envString("SPARQL_SOURCE_MODE", "local"),
SparqlHost: envString("SPARQL_HOST", "http://anzograph:8080"),
SparqlEndpoint: envString("SPARQL_ENDPOINT", ""),
ExternalSparqlEndpoint: envString("EXTERNAL_SPARQL_ENDPOINT", ""),
AccessToken: envString("ACCESS_TOKEN", ""),
KeycloakTokenEndpoint: envString("KEYCLOAK_TOKEN_ENDPOINT", ""),
KeycloakClientID: envString("KEYCLOAK_CLIENT_ID", ""),
KeycloakUsername: envString("KEYCLOAK_USERNAME", ""),
KeycloakPassword: envString("KEYCLOAK_PASSWORD", ""),
KeycloakScope: envString("KEYCLOAK_SCOPE", "openid"),
SparqlUser: envString("SPARQL_USER", ""),
SparqlPass: envString("SPARQL_PASS", ""),
SparqlInsecureTLS: envBool("SPARQL_INSECURE_TLS", false),
SparqlDataFile: envString("SPARQL_DATA_FILE", ""),
SparqlGraphIRI: envString("SPARQL_GRAPH_IRI", ""),
SparqlLoadOnStart: envBool("SPARQL_LOAD_ON_START", false),
SparqlClearOnStart: envBool("SPARQL_CLEAR_ON_START", false),
HierarchyLayoutEngine: envString("HIERARCHY_LAYOUT_ENGINE", "go"),
HierarchyLayoutBridgeBin: envString("HIERARCHY_LAYOUT_BRIDGE_BIN", "/app/radial_sugiyama_go_bridge"),
HierarchyLayoutEngine: envString("HIERARCHY_LAYOUT_ENGINE", "go"),
HierarchyLayoutBridgeBin: envString("HIERARCHY_LAYOUT_BRIDGE_BIN", "/app/radial_sugiyama_go_bridge"),
HierarchyLayoutBridgeWorkdir: envString("HIERARCHY_LAYOUT_BRIDGE_WORKDIR", "/workspace/radial_sugiyama"),
HierarchyLayoutRootIRI: envString("HIERARCHY_LAYOUT_ROOT_IRI", "http://purl.obolibrary.org/obo/BFO_0000001"),
HierarchyLayoutRootIRI: envString("HIERARCHY_LAYOUT_ROOT_IRI", "http://purl.obolibrary.org/obo/BFO_0000001"),
SparqlReadyRetries: envInt("SPARQL_READY_RETRIES", 30),
ListenAddr: envString("LISTEN_ADDR", ":8000"),
@@ -100,6 +114,35 @@ func LoadConfig() (Config, error) {
if cfg.SparqlLoadOnStart && strings.TrimSpace(cfg.SparqlDataFile) == "" {
return Config{}, fmt.Errorf("SPARQL_LOAD_ON_START=true but SPARQL_DATA_FILE is not set")
}
switch strings.ToLower(strings.TrimSpace(cfg.SparqlSourceMode)) {
case "local", "external":
cfg.SparqlSourceMode = strings.ToLower(strings.TrimSpace(cfg.SparqlSourceMode))
default:
return Config{}, fmt.Errorf("SPARQL_SOURCE_MODE must be 'local' or 'external'")
}
if cfg.UsesExternalSparql() {
if strings.TrimSpace(cfg.ExternalSparqlEndpoint) == "" {
return Config{}, fmt.Errorf("EXTERNAL_SPARQL_ENDPOINT must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakTokenEndpoint) == "" {
return Config{}, fmt.Errorf("KEYCLOAK_TOKEN_ENDPOINT must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakClientID) == "" {
return Config{}, fmt.Errorf("KEYCLOAK_CLIENT_ID must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakUsername) == "" {
return Config{}, fmt.Errorf("KEYCLOAK_USERNAME must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakPassword) == "" {
return Config{}, fmt.Errorf("KEYCLOAK_PASSWORD must be set when SPARQL_SOURCE_MODE=external")
}
if strings.TrimSpace(cfg.KeycloakScope) == "" {
cfg.KeycloakScope = "openid"
}
if cfg.SparqlLoadOnStart {
return Config{}, fmt.Errorf("SPARQL_LOAD_ON_START is not supported when SPARQL_SOURCE_MODE=external")
}
}
if cfg.DefaultNodeLimit < 1 {
return Config{}, fmt.Errorf("DEFAULT_NODE_LIMIT must be >= 1")
@@ -148,12 +191,19 @@ func LoadConfig() (Config, error) {
}
func (c Config) EffectiveSparqlEndpoint() string {
if c.UsesExternalSparql() {
return strings.TrimSpace(c.ExternalSparqlEndpoint)
}
if strings.TrimSpace(c.SparqlEndpoint) != "" {
return strings.TrimSpace(c.SparqlEndpoint)
}
return strings.TrimRight(c.SparqlHost, "/") + "/sparql"
}
func (c Config) UsesExternalSparql() bool {
return strings.EqualFold(strings.TrimSpace(c.SparqlSourceMode), "external")
}
func (c Config) corsOriginList() []string {
raw := strings.TrimSpace(c.CorsOrigins)
if raw == "" || raw == "*" {