goaegis-core provides comprehensive addon lifecycle hooks for extensibility:
Core Behavior:
- Default: Loads configs from local filesystem (files or directories)
- Remote sources: Via addons (GitHub, S3, Google Drive, HTTP, etc.)
Addon Capabilities:
- Remote config loading - Addons provide ConfigSource for S3, GitHub, etc.
- Hot reload - Watch for changes and trigger automatic reloads
- Config transformation - Modify/validate configs before use
- Custom authorization - Override authorization decisions
1. Init(core) → Called when addon registered
2. OnBeforeConfigLoad(path) → Addon can provide ConfigSource (or nil for filesystem)
3. [Config Loading] → Core loads from addon's ConfigSource OR filesystem
4. OnConfigValidate(cfg) → Transform/validate before storage
5. OnConfigLoad(cfg) → React to loaded config
6. [Runtime: OnAuthorize(ctx)] → Called on each authorization check
7. Shutdown() → Called on application shutdown
1. ConfigSource.Watch() → Returns channel
2. [Signal received] → Config changed
3. Core calls Load() → Fetch new config
4. OnConfigValidate() → Transform new config
5. OnConfigLoad() → Notify addons of reload
6. [Authorization uses new config]
When: Addon is registered via authz.Use(addon)
Purpose:
- Start servers (HTTP, gRPC)
- Initialize resources
- Setup connections
- Store core reference for later use
Example:
func (a *MyAddon) Init(core interface{}) error {
a.core = core.(*aegis.Aegis)
a.server = startHTTPServer()
return nil
}When: Before config loading starts (initial load or reload)
Purpose:
- Provide alternative config source (GitHub, S3, HTTP)
- Setup hot reload watchers
- Return nil to use default filesystem loader
Use Cases:
- Load from GitHub repositories
- Load from S3 buckets
- Load from HTTP endpoints
- Load from databases
Example:
func (a *GitHubAddon) OnBeforeConfigLoad(path string) (addons.ConfigSource, error) {
// Return ourselves as the config source
go a.startWatcher()
return a, nil // Implements ConfigSource interface
}When: After config is parsed but before validation
Purpose:
- Transform config
- Add computed resources/roles
- Add environment-specific configs
- Custom validation logic
- Return modified config
Use Cases:
- Add wildcard resources
- Add dev-only roles
- Inject tenant-specific configs
- Validate business rules
Example:
func (a *TransformAddon) OnConfigValidate(cfg *config.Config) (*config.Config, error) {
// Add environment-specific admin role
if os.Getenv("ENV") == "development" {
cfg.Roles["dev-admin"] = config.Role{
Name: "dev-admin",
Permissions: []config.Permission{
{Resource: "*", Actions: []string{"*"}, Effect: "allow"},
},
}
}
return cfg, nil
}When: After config is loaded, validated, and stored
Purpose:
- React to config changes
- Update internal state
- Log config events
- Notify other systems
Use Cases:
- Audit logging
- Metrics updates
- Cache invalidation
- Webhook notifications
Example:
func (a *LoggingAddon) OnConfigLoad(cfg *config.Config) error {
log.Printf("Config reloaded: %d roles, %d subjects",
len(cfg.Roles), len(cfg.Subjects))
a.metrics.ConfigReloadCounter.Inc()
return nil
}When: Every authorization check
Purpose:
- Override authorization decisions
- Add custom authorization logic
- Implement special cases
Decisions:
Allow- Grant access immediately (skip core evaluation)Deny- Block access immediately (skip core evaluation)Abstain- Defer to core engine or next addon
Example:
func (a *SuperAdminAddon) OnAuthorize(ctx *addons.Context) (addons.Decision, error) {
if ctx.Subject == "user:super-admin" {
return addons.Allow, nil // Super admin bypasses all rules
}
return addons.Abstain, nil // Let core decide
}When: Application shutdown
Purpose:
- Stop servers
- Close connections
- Cleanup resources
- Flush logs/metrics
Example:
func (a *ServerAddon) Shutdown() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return a.server.Shutdown(ctx)
}For remote config loading, implement:
type ConfigSource interface {
// LoadFiles returns a map of filename -> content for all config files.
// For single file sources, return map with one entry (e.g., {"config.yaml": data}).
// For multi-file sources (e.g., GitHub repo directory, S3 folder), return all YAML files.
// Keys can be any identifiers (filenames, paths, etc.) - used only for error messages.
// This allows remote sources to support nested structures like filesystem directories.
LoadFiles() (map[string][]byte, error)
// Watch returns channel for hot reload signals
// Return nil if hot reload not supported
Watch() <-chan struct{}
}Key Features:
- Single File: Return map with one entry:
map[string][]byte{"config.yaml": data} - Multiple Files: Return all YAML files from S3 folder/GitHub directory
- Nested Structure: Supports arbitrary directory nesting like filesystem
- Merging: Core automatically merges all files (resources, roles, subjects, policies)
Problem: Need different roles for dev/staging/prod
Solution: Use OnConfigValidate to inject environment-specific configs
func (a *EnvAddon) OnConfigValidate(cfg *config.Config) (*config.Config, error) {
env := os.Getenv("ENV")
if env == "development" {
// Add debug roles for developers
cfg.Roles["debug"] = devDebugRole
}
if env == "production" {
// Remove test subjects
delete(cfg.Subjects, "test:user")
}
return cfg, nil
}Problem: Need to track all config changes
Solution: Use OnConfigLoad to log events
func (a *AuditAddon) OnConfigLoad(cfg *config.Config) error {
event := AuditEvent{
Timestamp: time.Now(),
Action: "config_reload",
Resources: len(cfg.Resources),
Roles: len(cfg.Roles),
Subjects: len(cfg.Subjects),
}
return a.logger.Log(event)
}Problem: Want manual reload trigger via API
Solution: Expose ReloadConfig() in server addon
type ServerAddon struct {
core *aegis.Aegis
}
func (s *ServerAddon) handleReload(w http.ResponseWriter, r *http.Request) {
if err := s.core.ReloadConfig(); err != nil {
http.Error(w, err.Error(), 500)
return
}
json.NewEncoder(w).Encode(map[string]string{"status": "reloaded"})
}- Error Handling: Return errors from hooks to abort config loading
- Idempotency: OnConfigLoad is called on every reload, ensure idempotent
- Performance: OnAuthorize is called frequently, keep it fast
- Graceful Shutdown: Always implement Shutdown() to cleanup resources
- Channel Buffering: Buffer Watch() channel (size 1) to prevent blocking
- Validation: Use OnConfigValidate for business rule validation
- Testing: Test each hook independently with mocks
- ConfigSource: Return nil from OnBeforeConfigLoad to use filesystem
See examples/addons/ for working examples!