feat(master): add updater-master binary wiring store, hub, auth and UI
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// Package config loads the master's configuration from environment variables.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config holds the master's runtime configuration.
|
||||
type Config struct {
|
||||
DatabaseURL string
|
||||
PublicURL string // where people reach the UI, without trailing slash
|
||||
AgentURL string // what agents dial, shown in the add-agent snippet
|
||||
AgentImage string
|
||||
OIDCIssuer string
|
||||
OIDCClientID string
|
||||
OIDCClientSecret string
|
||||
AllowedGroup string
|
||||
Port string
|
||||
AgentPort string
|
||||
LogLevel string
|
||||
}
|
||||
|
||||
// Load reads and validates the master configuration, failing fast on
|
||||
// anything missing so a half-configured master never starts.
|
||||
func Load() (*Config, error) {
|
||||
cfg := &Config{
|
||||
DatabaseURL: os.Getenv("DATABASE_URL"),
|
||||
PublicURL: strings.TrimSuffix(os.Getenv("PUBLIC_URL"), "/"),
|
||||
AgentURL: os.Getenv("AGENT_URL"),
|
||||
AgentImage: getenvDefault("AGENT_IMAGE", "gitea.shcizo.se/shcizo/package-updater:latest"),
|
||||
OIDCIssuer: os.Getenv("OIDC_ISSUER"),
|
||||
OIDCClientID: os.Getenv("OIDC_CLIENT_ID"),
|
||||
OIDCClientSecret: os.Getenv("OIDC_CLIENT_SECRET"),
|
||||
AllowedGroup: os.Getenv("ALLOWED_GROUP"),
|
||||
Port: getenvDefault("PORT", "8080"),
|
||||
AgentPort: getenvDefault("AGENT_PORT", "8090"),
|
||||
LogLevel: getenvDefault("LOG_LEVEL", "info"),
|
||||
}
|
||||
for _, r := range []struct{ name, value string }{
|
||||
{"DATABASE_URL", cfg.DatabaseURL},
|
||||
{"PUBLIC_URL", cfg.PublicURL},
|
||||
{"AGENT_URL", cfg.AgentURL},
|
||||
{"OIDC_ISSUER", cfg.OIDCIssuer},
|
||||
{"OIDC_CLIENT_ID", cfg.OIDCClientID},
|
||||
{"OIDC_CLIENT_SECRET", cfg.OIDCClientSecret},
|
||||
{"ALLOWED_GROUP", cfg.AllowedGroup},
|
||||
} {
|
||||
if r.value == "" {
|
||||
return nil, fmt.Errorf("%s is required", r.name)
|
||||
}
|
||||
}
|
||||
if !strings.HasPrefix(cfg.PublicURL, "http://") && !strings.HasPrefix(cfg.PublicURL, "https://") {
|
||||
return nil, fmt.Errorf("PUBLIC_URL must start with http:// or https://")
|
||||
}
|
||||
if !strings.HasPrefix(cfg.AgentURL, "wss://") {
|
||||
return nil, fmt.Errorf("AGENT_URL must start with wss://")
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func getenvDefault(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var required = map[string]string{
|
||||
"DATABASE_URL": "postgres://u:p@db/pu",
|
||||
"PUBLIC_URL": "https://updater.example.com",
|
||||
"AGENT_URL": "wss://10.0.0.5:8090/agent",
|
||||
"OIDC_ISSUER": "https://auth.example.com/application/o/pu/",
|
||||
"OIDC_CLIENT_ID": "id",
|
||||
"OIDC_CLIENT_SECRET": "secret",
|
||||
"ALLOWED_GROUP": "updater-admins",
|
||||
}
|
||||
|
||||
func setAll(t *testing.T) {
|
||||
for k, v := range required {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
for _, k := range []string{"PORT", "AGENT_PORT", "AGENT_IMAGE", "LOG_LEVEL"} {
|
||||
t.Setenv(k, "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_Defaults(t *testing.T) {
|
||||
setAll(t)
|
||||
cfg, err := Load()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "8080", cfg.Port)
|
||||
assert.Equal(t, "8090", cfg.AgentPort)
|
||||
assert.Equal(t, "gitea.shcizo.se/shcizo/package-updater:latest", cfg.AgentImage)
|
||||
assert.Equal(t, "info", cfg.LogLevel)
|
||||
assert.Equal(t, "updater-admins", cfg.AllowedGroup)
|
||||
}
|
||||
|
||||
func TestLoad_EachRequiredVariable(t *testing.T) {
|
||||
for key := range required {
|
||||
t.Run(key, func(t *testing.T) {
|
||||
setAll(t)
|
||||
t.Setenv(key, "")
|
||||
_, err := Load()
|
||||
assert.ErrorContains(t, err, key+" is required")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_URLFormats(t *testing.T) {
|
||||
cases := []struct{ key, value, wantErr string }{
|
||||
{"PUBLIC_URL", "updater.example.com", "PUBLIC_URL must start with http:// or https://"},
|
||||
{"AGENT_URL", "ws://10.0.0.5:8090/agent", "AGENT_URL must start with wss://"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.key, func(t *testing.T) {
|
||||
setAll(t)
|
||||
t.Setenv(c.key, c.value)
|
||||
_, err := Load()
|
||||
assert.ErrorContains(t, err, c.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user