mirror of
https://github.com/adnanh/webhook.git
synced 2026-07-20 16:53:47 +00:00
feat: 添加config 能力
命令行参数优先级高于配置文件(配置文件先加载,flag.Parse() 后执行会覆盖)
This commit is contained in:
parent
0645b1404f
commit
cc338cd3ac
3 changed files with 287 additions and 0 deletions
118
config.go
Normal file
118
config.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// configFile holds the path to the YAML configuration file.
|
||||
// It is extracted from os.Args before flag.Parse() runs.
|
||||
var configFile string
|
||||
|
||||
// loadConfigFile reads a YAML configuration file and sets the corresponding
|
||||
// flags. Flags set via the command line take precedence over the config file
|
||||
// because this function is called before flag.Parse().
|
||||
//
|
||||
// Supported YAML structure:
|
||||
//
|
||||
// ip: 0.0.0.0
|
||||
// port: 9000
|
||||
// nopanic: true
|
||||
// hooks:
|
||||
// - /etc/webhook/hooks.yaml
|
||||
// header:
|
||||
// - "X-Custom=value"
|
||||
func loadConfigFile(path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read config file %q: %w", path, err)
|
||||
}
|
||||
|
||||
// Use a generic map so we can handle both scalar and list values.
|
||||
var cfg map[string]interface{}
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return fmt.Errorf("failed to parse config file %q: %w", path, err)
|
||||
}
|
||||
|
||||
for key, val := range cfg {
|
||||
f := flag.CommandLine.Lookup(key)
|
||||
if f == nil {
|
||||
return fmt.Errorf("unknown option %q in config file %q", key, path)
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case []interface{}:
|
||||
// List values: call flag.Set for each element (supports -hooks, -header, etc.)
|
||||
for _, item := range v {
|
||||
s := fmt.Sprintf("%v", item)
|
||||
if err := flag.CommandLine.Set(key, s); err != nil {
|
||||
return fmt.Errorf("error setting flag %q to %q: %w", key, s, err)
|
||||
}
|
||||
}
|
||||
case bool:
|
||||
s := "false"
|
||||
if v {
|
||||
s = "true"
|
||||
}
|
||||
if err := flag.CommandLine.Set(key, s); err != nil {
|
||||
return fmt.Errorf("error setting flag %q to %q: %w", key, s, err)
|
||||
}
|
||||
default:
|
||||
s := fmt.Sprintf("%v", v)
|
||||
if err := flag.CommandLine.Set(key, s); err != nil {
|
||||
return fmt.Errorf("error setting flag %q to %q: %w", key, s, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractConfigFlag scans os.Args for -config / -c / --config and returns the
|
||||
// path value. It also removes those arguments from os.Args so that flag.Parse()
|
||||
// does not see them (since -config is not a registered flag).
|
||||
func extractConfigFlag() string {
|
||||
var path string
|
||||
var newArgs []string
|
||||
|
||||
args := os.Args[1:] // skip program name
|
||||
newArgs = append(newArgs, os.Args[0])
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
|
||||
var matched bool
|
||||
var value string
|
||||
|
||||
switch {
|
||||
case arg == "-c" || arg == "-config" || arg == "--config":
|
||||
matched = true
|
||||
if i+1 < len(args) {
|
||||
i++
|
||||
value = args[i]
|
||||
}
|
||||
case strings.HasPrefix(arg, "-c="):
|
||||
matched = true
|
||||
value = strings.TrimPrefix(arg, "-c=")
|
||||
case strings.HasPrefix(arg, "-config="):
|
||||
matched = true
|
||||
value = strings.TrimPrefix(arg, "-config=")
|
||||
case strings.HasPrefix(arg, "--config="):
|
||||
matched = true
|
||||
value = strings.TrimPrefix(arg, "--config=")
|
||||
}
|
||||
|
||||
if matched {
|
||||
path = value
|
||||
} else {
|
||||
newArgs = append(newArgs, arg)
|
||||
}
|
||||
}
|
||||
|
||||
os.Args = newArgs
|
||||
return path
|
||||
}
|
||||
|
|
@ -99,6 +99,15 @@ func main() {
|
|||
// register platform-specific flags
|
||||
platformFlags()
|
||||
|
||||
// Extract -config / -c from os.Args before flag.Parse() sees it.
|
||||
configFile = extractConfigFlag()
|
||||
if configFile != "" {
|
||||
if err := loadConfigFile(configFile); err != nil {
|
||||
fmt.Println("error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if err := initExecutionSettings(); err != nil {
|
||||
|
|
|
|||
160
webhook.yaml.example
Normal file
160
webhook.yaml.example
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# Webhook 配置文件
|
||||
# 用法: webhook -c /etc/webhook/webhook.yaml
|
||||
#
|
||||
# 支持所有命令行参数。
|
||||
# 列表类参数(hooks、header)使用 YAML 数组语法。
|
||||
# 布尔类参数使用 true/false。
|
||||
# 命令行参数优先级高于本配置文件。
|
||||
|
||||
# ============================================================
|
||||
# 网络设置
|
||||
# ============================================================
|
||||
|
||||
# 监听地址,默认 0.0.0.0(监听所有网卡)
|
||||
ip: 0.0.0.0
|
||||
|
||||
# 监听端口,默认 9000
|
||||
port: 1987
|
||||
|
||||
# URL 前缀,最终 hook 地址为 http://host:port/PREFIX/hook-id
|
||||
# 默认值为 "hooks"
|
||||
urlprefix: wh
|
||||
|
||||
# 限制允许的 HTTP 方法,多个方法用逗号分隔(如 "POST,PUT")
|
||||
# 留空表示允许所有方法
|
||||
# http-methods: "POST"
|
||||
|
||||
# ============================================================
|
||||
# Hook 定义文件
|
||||
# ============================================================
|
||||
|
||||
# hook 定义文件路径,支持 JSON 和 YAML 格式
|
||||
# 可指定多个文件,每个文件中的 hook id 不能重复
|
||||
hooks:
|
||||
- /etc/webhook/admin.yaml
|
||||
- /etc/webhook/hooks.yaml
|
||||
|
||||
# ============================================================
|
||||
# 运行行为
|
||||
# ============================================================
|
||||
|
||||
# 当无法加载任何 hook 时不要 panic 退出(需配合非 verbose 模式)
|
||||
# 默认 false
|
||||
nopanic: true
|
||||
|
||||
# 监视 hook 文件变化,自动热重载
|
||||
# 默认 false
|
||||
hotreload: true
|
||||
|
||||
# 将 hook 文件作为 Go template 解析
|
||||
# 默认 false
|
||||
# template: false
|
||||
|
||||
# multipart 表单解析时的最大内存(字节),超出部分写入磁盘临时文件
|
||||
# 默认 1048576 (1MB)
|
||||
# max-multipart-mem: 1048576
|
||||
|
||||
# ============================================================
|
||||
# 日志设置
|
||||
# ============================================================
|
||||
|
||||
# 启用详细日志输出
|
||||
# 默认 false
|
||||
# verbose: true
|
||||
|
||||
# 启用调试输出(会打印完整的请求/响应内容,隐含 verbose=true)
|
||||
# 默认 false
|
||||
# debug: false
|
||||
|
||||
# 日志输出到文件(设置后隐含 verbose=true)
|
||||
# 留空表示输出到 stdout
|
||||
# logfile: /var/log/webhook.log
|
||||
|
||||
# ============================================================
|
||||
# 自定义响应头
|
||||
# ============================================================
|
||||
|
||||
# 所有 hook 响应都会附带这些 header,格式为 "Name=Value"
|
||||
# header:
|
||||
# - "X-Custom-Header=value"
|
||||
# - "Access-Control-Allow-Origin=*"
|
||||
|
||||
# ============================================================
|
||||
# Request ID
|
||||
# ============================================================
|
||||
|
||||
# 使用客户端传入的 X-Request-Id 头作为请求 ID(用于日志追踪)
|
||||
# 默认 false
|
||||
# x-request-id: false
|
||||
|
||||
# 截断 X-Request-Id 的最大长度,0 表示不限制
|
||||
# x-request-id-limit: 0
|
||||
|
||||
# ============================================================
|
||||
# TLS / HTTPS 设置
|
||||
# ============================================================
|
||||
|
||||
# 启用 HTTPS 模式
|
||||
# 默认 false
|
||||
# secure: false
|
||||
|
||||
# TLS 证书文件路径
|
||||
# cert: /etc/webhook/cert.pem
|
||||
|
||||
# TLS 私钥文件路径
|
||||
# key: /etc/webhook/key.pem
|
||||
|
||||
# TLS 最低版本,可选 "1.0", "1.1", "1.2", "1.3"
|
||||
# 默认 "1.2"
|
||||
# tls-min-version: "1.2"
|
||||
|
||||
# TLS 加密套件,逗号分隔。留空使用 Go 默认值
|
||||
# 可通过 webhook --list-cipher-suites 查看可用列表
|
||||
# cipher-suites: ""
|
||||
|
||||
# ============================================================
|
||||
# Admin 管理界面
|
||||
# ============================================================
|
||||
|
||||
# 启用 Admin UI 和 API,用于在线管理 hooks
|
||||
# 默认 false
|
||||
# admin: false
|
||||
|
||||
# Admin 界面的 URL 前缀,最终地址为 http://host:port/PREFIX/
|
||||
# 默认 "admin"
|
||||
# admin-path: admin
|
||||
|
||||
# Admin 登录使用的 TOTP 密钥(Base32 编码)
|
||||
# 可用 Google Authenticator 等 APP 扫码登录
|
||||
# admin-totp-secret: ""
|
||||
|
||||
# 用于签发 Admin JWT 会话令牌的密钥
|
||||
# admin-jwt-secret: ""
|
||||
|
||||
# Admin JWT 会话有效期,支持 Go duration 格式(如 12h、30m、720h)
|
||||
# 默认 "12h"
|
||||
# admin-session-ttl: "12h"
|
||||
|
||||
# ============================================================
|
||||
# Unix Socket(仅 Linux/macOS)
|
||||
# ============================================================
|
||||
|
||||
# 使用 Unix socket 代替 IP:Port 监听
|
||||
# 设置后 ip 和 port 选项将被忽略
|
||||
# socket: /tmp/webhook.sock
|
||||
|
||||
# ============================================================
|
||||
# 权限降级(仅 Linux/macOS)
|
||||
# ============================================================
|
||||
|
||||
# 打开监听端口后切换到指定的用户/组 ID 运行
|
||||
# setuid 和 setgid 必须同时设置,不能与 socket 同时使用
|
||||
# setuid: 1000
|
||||
# setgid: 1000
|
||||
|
||||
# ============================================================
|
||||
# PID 文件
|
||||
# ============================================================
|
||||
|
||||
# 创建 PID 文件,用于进程管理
|
||||
# pidfile: /var/run/webhook.pid
|
||||
Loading…
Add table
Add a link
Reference in a new issue