This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/sandbox/lib_module.go
Jeffrey Smith f0dd43144e rebrand: Switchboard Core → Armature
- Rename Go module switchboard-core → armature (155+ files)
- Rename Docker image → gobha/armature
- Rename K8s resources, secrets, deployments
- Rename Prometheus metrics switchboard_* → armature_*
- Rename env vars SWITCHBOARD_ADMIN_* → ARMATURE_ADMIN_*
- Rename DB names switchboard_core* → armature*
- Update all frontend branding, notification templates, docs
- Update CI scripts, e2e tests, Keycloak realm, nginx conf
- Rename scripts/switchboard-ca.sh → scripts/armature-ca.sh
- Rename k8s/switchboard.yaml → k8s/armature.yaml
- Rename chart alerting/dashboard files
- Fix: DockerHub push uses env: binding for secret injection
- Helm chart updated (name, labels, template functions, dashboard, alerting)
- Replace favicon/icon assets with Armature brand

No functional changes. Pure mechanical rename + CI fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:39:58 +00:00

155 lines
4.9 KiB
Go

// Package sandbox — lib_module.go
//
// No permission required — any starlark package can use lib.require().
//
// Starlark API:
// gitea = lib.require("gitea-client") → struct with library's exports
//
// The loaded library runs with its own permission context, not the
// consumer's. Results are cached per ExecPackage invocation.
package sandbox
import (
"context"
"fmt"
"log"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"armature/models"
)
// libContext carries per-invocation state for lib.require() calls.
// Created once per ExecPackage and threaded through buildModules.
type libContext struct {
cache map[string]starlark.Value // library ID → frozen exports struct
loading map[string]bool // cycle detection
}
func newLibContext() *libContext {
return &libContext{
cache: make(map[string]starlark.Value),
loading: make(map[string]bool),
}
}
// BuildLibModule creates the "lib" module.
// consumerPkgID is the package calling lib.require() — used to validate
// the dependency record exists.
func BuildLibModule(ctx context.Context, r *Runner, consumerPkgID string, rc *RunContext, lc *libContext) *starlarkstruct.Module {
return MakeModule("lib", starlark.StringDict{
"require": starlark.NewBuiltin("lib.require", func(
thread *starlark.Thread, b *starlark.Builtin,
args starlark.Tuple, kwargs []starlark.Tuple,
) (starlark.Value, error) {
var libraryID string
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &libraryID); err != nil {
return nil, err
}
// 1. Check cache
if cached, ok := lc.cache[libraryID]; ok {
return cached, nil
}
// 2. Cycle detection
if lc.loading[libraryID] {
return nil, fmt.Errorf("lib.require: circular dependency: %q", libraryID)
}
lc.loading[libraryID] = true
defer func() { delete(lc.loading, libraryID) }()
// 3. Verify dependency is declared
if r.stores.Dependencies == nil {
return nil, fmt.Errorf("lib.require: dependency store not available")
}
deps, err := r.stores.Dependencies.ListByConsumer(ctx, consumerPkgID)
if err != nil {
return nil, fmt.Errorf("lib.require: failed to check dependencies: %w", err)
}
declared := false
for _, dep := range deps {
if dep.LibraryID == libraryID {
declared = true
break
}
}
if !declared {
return nil, fmt.Errorf("lib.require: package %q does not declare dependency on %q", consumerPkgID, libraryID)
}
// 4. Load library's PackageRegistration
libPkg, err := r.stores.Packages.Get(ctx, libraryID)
if err != nil {
return nil, fmt.Errorf("lib.require: library %q not found: %w", libraryID, err)
}
if libPkg.Type != "library" {
return nil, fmt.Errorf("lib.require: package %q is type %q, not library", libraryID, libPkg.Type)
}
if libPkg.Status != models.PackageStatusActive {
return nil, fmt.Errorf("lib.require: library %q is %s, not active", libraryID, libPkg.Status)
}
if libPkg.Tier != models.ExtTierStarlark {
return nil, fmt.Errorf("lib.require: library %q is tier %s, not starlark", libraryID, libPkg.Tier)
}
// 5. Build library's module set (library's own permissions)
libModules, err := r.buildModulesWithLibCtx(ctx, libraryID, libPkg.Manifest, rc, lc)
if err != nil {
return nil, fmt.Errorf("lib.require: failed to build modules for library %q: %w", libraryID, err)
}
// 6. Load library script from disk
libScript, err := r.loadScript(libPkg)
if err != nil {
return nil, fmt.Errorf("lib.require: %w", err)
}
// 7. Execute with library-scoped loader
libLoader := r.packageLoader(libraryID, libModules)
result, err := r.sandbox.ExecWithLoader(ctx, libraryID+"/script.star", libScript, libModules, libLoader)
if err != nil {
return nil, fmt.Errorf("lib.require: error executing library %q: %w", libraryID, err)
}
// 8. Extract exports from manifest
exports := extractExports(libPkg.Manifest)
if len(exports) == 0 {
return nil, fmt.Errorf("lib.require: library %q declares no exports", libraryID)
}
members := make(starlark.StringDict, len(exports))
for _, name := range exports {
val, ok := result.Globals[name]
if !ok {
return nil, fmt.Errorf("lib.require: library %q does not export %q", libraryID, name)
}
members[name] = val
}
// 9. Freeze into struct, cache, return
frozen := starlarkstruct.FromStringDict(starlark.String(libraryID), members)
lc.cache[libraryID] = frozen
log.Printf(" 📦 lib.require: loaded %s (%d exports) for %s", libraryID, len(exports), consumerPkgID)
return frozen, nil
}),
})
}
// extractExports reads the "exports" array from a package manifest.
func extractExports(manifest map[string]any) []string {
raw, ok := manifest["exports"].([]any)
if !ok {
return nil
}
exports := make([]string, 0, len(raw))
for _, v := range raw {
if s, ok := v.(string); ok {
exports = append(exports, s)
}
}
return exports
}