Compare commits

...

2 Commits

4 changed files with 124 additions and 13 deletions
+32 -7
View File
@@ -2,6 +2,7 @@ package main
import "context" import "context"
import "flag" import "flag"
import "fmt"
import "os" import "os"
import "os/signal" import "os/signal"
import "syscall" import "syscall"
@@ -48,26 +49,34 @@ func run() int {
var cfg codit.Config var cfg codit.Config
var logger codit.Logger var logger codit.Logger
var server *codit.Server var server *codit.Server
var logFile string
var err error var err error
flag.Var(&configPaths, "config", "path or wildcard pattern to YAML configuration file; repeatable") flag.Var(&configPaths, "config", "path or wildcard pattern to YAML configuration file; repeatable")
flag.Var(&configPaths, "config-file", "path to YAML configuration file; repeatable") flag.Var(&configPaths, "config-file", "path to YAML configuration file; repeatable")
flag.Var(&configPaths, "config-file-pattern", "wildcard pattern for YAML configuration files; repeatable") flag.Var(&configPaths, "config-file-pattern", "wildcard pattern for YAML configuration files; repeatable")
flag.StringVar(&logFile, "log-file", "", "path to log file")
flag.Parse() flag.Parse()
logger = codit.NewAsyncLogger(PROGRAM_NAME, os.Stderr, codit.LOG_ALL) cfg, err = codit.LoadConfigFilesWithEnvPrefix(configPaths, codit.EnvPrefixFromServerIdentifier(PROGRAM_NAME))
if err != nil {
fmt.Fprintf(os.Stderr, "load config: %v\n", err)
return 1
}
if logFile != "" {
cfg.LogFile = logFile
}
logger, err = newLoggerFromConfig(PROGRAM_NAME, cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "init logger: %v\n", err)
return 1
}
defer logger.Close() defer logger.Close()
ctx, stop = signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) ctx, stop = signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt)
defer stop() defer stop()
logger.Write("", codit.LOG_INFO, "starting %s %s", PROGRAM_NAME, PROGRAM_VERSION) logger.Write("", codit.LOG_INFO, "starting %s %s", PROGRAM_NAME, PROGRAM_VERSION)
defer logger.Write("", codit.LOG_INFO, "stopped %s %s", PROGRAM_NAME, PROGRAM_VERSION) defer logger.Write("", codit.LOG_INFO, "stopped %s %s", PROGRAM_NAME, PROGRAM_VERSION)
cfg, err = codit.LoadConfigFilesWithEnvPrefix(configPaths, codit.EnvPrefixFromServerIdentifier(PROGRAM_NAME))
if err != nil {
logger.Write("", codit.LOG_ERROR, "load config: %v", err)
return 1
}
server, err = codit.NewServerWithIdentifier(&cfg, logger, PROGRAM_NAME) server, err = codit.NewServerWithIdentifier(&cfg, logger, PROGRAM_NAME)
if err != nil { if err != nil {
logger.Write("", codit.LOG_ERROR, "init app: %v", err) logger.Write("", codit.LOG_ERROR, "init app: %v", err)
@@ -83,3 +92,19 @@ func run() int {
logger.Write("", codit.LOG_INFO, "shutdown complete - %s %s", PROGRAM_NAME, PROGRAM_VERSION) logger.Write("", codit.LOG_INFO, "shutdown complete - %s %s", PROGRAM_NAME, PROGRAM_VERSION)
return 0 return 0
} }
func newLoggerFromConfig(id string, cfg codit.Config) (codit.Logger, error) {
var mask codit.LogMask
var logger *codit.AsyncLogger
var err error
mask = codit.LogStrsToMask(cfg.LogMask)
if cfg.LogFile == "" {
return codit.NewAsyncLogger(id, os.Stderr, mask), nil
}
logger, err = codit.NewAsyncLoggerToFile(id, cfg.LogFile, cfg.LogMaxSize, cfg.LogRotate, mask)
if err != nil {
return nil, err
}
return logger, nil
}
+45
View File
@@ -47,6 +47,18 @@ type Config struct {
DockerHTTPPrefix string `yaml:"docker_http_prefix"` DockerHTTPPrefix string `yaml:"docker_http_prefix"`
GitHTTPPrefix string `yaml:"git_http_prefix"` GitHTTPPrefix string `yaml:"git_http_prefix"`
RPMHTTPPrefix string `yaml:"rpm_http_prefix"` RPMHTTPPrefix string `yaml:"rpm_http_prefix"`
App LogConfig `yaml:"app"`
LogMask []string `yaml:"log-mask"`
LogFile string `yaml:"log-file"`
LogMaxSize int64 `yaml:"log-max-size"`
LogRotate int `yaml:"log-rotate"`
}
type LogConfig struct {
LogMask []string `yaml:"log-mask"`
LogFile string `yaml:"log-file"`
LogMaxSize int64 `yaml:"log-max-size"`
LogRotate int `yaml:"log-rotate"`
} }
const DefaultEnvPrefix string = "CODIT_" const DefaultEnvPrefix string = "CODIT_"
@@ -89,11 +101,13 @@ func LoadFilesWithEnvPrefix(paths []string, envPrefix string) (Config, error) {
DockerHTTPPrefix: "/v2", DockerHTTPPrefix: "/v2",
GitHTTPPrefix: "/git", GitHTTPPrefix: "/git",
RPMHTTPPrefix: "/rpm", RPMHTTPPrefix: "/rpm",
LogMask: []string{},
} }
err = loadConfigFiles(&cfg, paths) err = loadConfigFiles(&cfg, paths)
if err != nil { if err != nil {
return cfg, err return cfg, err
} }
applyAppLogConfig(&cfg)
override(&cfg, envPrefix) override(&cfg, envPrefix)
cfg.AuthMode = strings.ToLower(strings.TrimSpace(cfg.AuthMode)) cfg.AuthMode = strings.ToLower(strings.TrimSpace(cfg.AuthMode))
cfg.TLSServerCertSource = strings.ToLower(strings.TrimSpace(cfg.TLSServerCertSource)) cfg.TLSServerCertSource = strings.ToLower(strings.TrimSpace(cfg.TLSServerCertSource))
@@ -112,6 +126,21 @@ func LoadFilesWithEnvPrefix(paths []string, envPrefix string) (Config, error) {
return cfg, nil return cfg, nil
} }
func applyAppLogConfig(cfg *Config) {
if len(cfg.App.LogMask) > 0 {
cfg.LogMask = cfg.App.LogMask
}
if cfg.App.LogFile != "" {
cfg.LogFile = cfg.App.LogFile
}
if cfg.App.LogMaxSize != 0 {
cfg.LogMaxSize = cfg.App.LogMaxSize
}
if cfg.App.LogRotate != 0 {
cfg.LogRotate = cfg.App.LogRotate
}
}
func loadConfigFiles(cfg *Config, paths []string) error { func loadConfigFiles(cfg *Config, paths []string) error {
var expanded []string var expanded []string
@@ -321,6 +350,22 @@ func override(cfg *Config, envPrefix string) {
if v != "" { if v != "" {
cfg.RPMHTTPPrefix = v cfg.RPMHTTPPrefix = v
} }
v = getEnv(envPrefix, "LOG_MASK")
if v != "" {
cfg.LogMask = splitCSV(v)
}
v = getEnv(envPrefix, "LOG_FILE")
if v != "" {
cfg.LogFile = v
}
v = getEnv(envPrefix, "LOG_MAX_SIZE")
if v != "" {
cfg.LogMaxSize, _ = strconv.ParseInt(v, 10, 64)
}
v = getEnv(envPrefix, "LOG_ROTATE")
if v != "" {
cfg.LogRotate, _ = strconv.Atoi(v)
}
} }
func getEnv(prefix string, name string) string { func getEnv(prefix string, name string) string {
+7 -1
View File
@@ -58,7 +58,7 @@ func TestLoadFromYAMLAndEnvOverride(t *testing.T) {
var cfg Config var cfg Config
dir = t.TempDir() dir = t.TempDir()
path = filepath.Join(dir, "config.yaml") path = filepath.Join(dir, "config.yaml")
data = "db_driver: sqlite\ndb_dsn: file:test.db\nauth_mode: HyBrId\ngit_http_prefix: /g\ndocker_http_prefix: registry\n" data = "db_driver: sqlite\ndb_dsn: file:test.db\nauth_mode: HyBrId\ngit_http_prefix: /g\ndocker_http_prefix: registry\napp:\n log-mask: [error, warn]\n log-file: /tmp/codit.log\n log-max-size: 1234\n log-rotate: 3\n"
err = os.WriteFile(path, []byte(data), 0o644) err = os.WriteFile(path, []byte(data), 0o644)
if err != nil { if err != nil {
t.Fatalf("write config file: %v", err) t.Fatalf("write config file: %v", err)
@@ -81,6 +81,12 @@ func TestLoadFromYAMLAndEnvOverride(t *testing.T) {
if cfg.DockerHTTPPrefix != "/registry" { if cfg.DockerHTTPPrefix != "/registry" {
t.Fatalf("docker_http_prefix normalization failed: %s", cfg.DockerHTTPPrefix) t.Fatalf("docker_http_prefix normalization failed: %s", cfg.DockerHTTPPrefix)
} }
if len(cfg.LogMask) != 2 || cfg.LogMask[0] != "error" || cfg.LogMask[1] != "warn" {
t.Fatalf("log-mask load failed: %#v", cfg.LogMask)
}
if cfg.LogFile != "/tmp/codit.log" || cfg.LogMaxSize != 1234 || cfg.LogRotate != 3 {
t.Fatalf("log config load failed: file=%q max=%d rotate=%d", cfg.LogFile, cfg.LogMaxSize, cfg.LogRotate)
}
} }
func TestLoadFilesLaterWinsAndGlob(t *testing.T) { func TestLoadFilesLaterWinsAndGlob(t *testing.T) {
+40 -5
View File
@@ -14,6 +14,7 @@ import "io"
import "log" import "log"
import "net" import "net"
import "net/http" import "net/http"
import "net/netip"
import "os" import "os"
import "path/filepath" import "path/filepath"
import "sync" import "sync"
@@ -1221,14 +1222,22 @@ func (m *additionalListenerManager) startEndpoint(endpoint listenerEndpoint) run
m.wg.Add(1) m.wg.Add(1)
go func(ep listenerEndpoint, srv *http.Server) { go func(ep listenerEndpoint, srv *http.Server) {
var err error var err error
var listener net.Listener
var network string
defer m.wg.Done() defer m.wg.Done()
network = tcpAddrStrClass(ep.Addr)
listener, err = net.Listen(network, ep.Addr)
if err != nil {
m.Logger.Write(ep.Name, LOG_ERROR, "additional listener failed network=%s addr=%s err=%v", network, ep.Addr, err)
return
}
if ep.IsHTTPS { if ep.IsHTTPS {
err = srv.ListenAndServeTLS("", "") err = srv.ServeTLS(listener, "", "")
} else { } else {
err = srv.ListenAndServe() err = srv.Serve(listener)
} }
if err != nil && !errors.Is(err, http.ErrServerClosed) { if err != nil && !errors.Is(err, http.ErrServerClosed) {
m.Logger.Write(ep.Name, LOG_ERROR, "additional listener failed addr=%s err=%v", ep.Addr, err) m.Logger.Write(ep.Name, LOG_ERROR, "additional listener failed network=%s addr=%s err=%v", network, ep.Addr, err)
} }
}(endpoint, server) }(endpoint, server)
return running return running
@@ -1935,6 +1944,24 @@ func TitleFromServerIdentifier(value string) string {
return codit_config.TitleFromServerIdentifier(value) return codit_config.TitleFromServerIdentifier(value)
} }
func tcpAddrStrClass(addr string) string {
var ap netip.AddrPort
var err error
if len(addr) > 0 {
ap, err = netip.ParseAddrPort(addr)
if err == nil {
if ap.Addr().Is6() {
return "tcp6"
}
if ap.Addr().Is4() || ap.Addr().Is4In6() {
return "tcp4"
}
}
}
return "tcp"
}
func serveListeners(ctx context.Context, endpoints []listenerEndpoint, handler http.Handler, logger Logger, serverIdentifier string) error { func serveListeners(ctx context.Context, endpoints []listenerEndpoint, handler http.Handler, logger Logger, serverIdentifier string) error {
var listenCtx context.Context var listenCtx context.Context
var cancel context.CancelFunc var cancel context.CancelFunc
@@ -1980,9 +2007,12 @@ func serveListeners(ctx context.Context, endpoints []listenerEndpoint, handler h
var err error var err error
var server *http.Server var server *http.Server
var l *log.Logger var l *log.Logger
var listener net.Listener
var network string
defer wg.Done() defer wg.Done()
l = log.New(&server_http_log_writer{l: logger, id: "", depth: +2}, "", 0) l = log.New(&server_http_log_writer{l: logger, id: "", depth: +2}, "", 0)
network = tcpAddrStrClass(endpoint.Addr)
if endpoint.IsHTTPS { if endpoint.IsHTTPS {
server = &http.Server{ server = &http.Server{
Addr: endpoint.Addr, Addr: endpoint.Addr,
@@ -2002,10 +2032,15 @@ func serveListeners(ctx context.Context, endpoints []listenerEndpoint, handler h
serversMu.Lock() serversMu.Lock()
servers = append(servers, server) servers = append(servers, server)
serversMu.Unlock() serversMu.Unlock()
listener, err = net.Listen(network, endpoint.Addr)
if err != nil {
errs <- fmt.Errorf("listen %s %s: %w", network, endpoint.Addr, err)
return
}
if endpoint.IsHTTPS { if endpoint.IsHTTPS {
err = server.ListenAndServeTLS("", "") err = server.ServeTLS(listener, "", "")
} else { } else {
err = server.ListenAndServe() err = server.Serve(listener)
} }
errs <- err errs <- err
}(ep) }(ep)