Skip to content

Commit 22bcb02

Browse files
committed
feat: OpenAPI Spec support #123
1 parent 02df65c commit 22bcb02

22 files changed

Lines changed: 603 additions & 112 deletions

README-zhCN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
-**高性能**:可配置并发、速率限制(含基于 Redis 的分布式限流)与优化的请求处理
1414
- 🎯 **灵活配置**:支持 JSON 和 YAML 配置文件,支持 Go 模板
1515
- 🔐 **高级认证**:多种触发规则类型,包括 HMAC 签名验证、IP 白名单和自定义规则
16-
- 📊 **可观测性**:内置 Prometheus 指标、健康检查端点、OpenTelemetry 追踪、审计日志与全面日志
16+
- 📊 **可观测性**:内置 Prometheus 指标、健康检查端点、可选 OpenAPI 规范(便于 Swagger/客户端生成)、OpenTelemetry 追踪、审计日志与全面日志
1717
- 🐳 **容器就绪**:官方 Docker 镜像,提供多个变体
1818
- 🌍 **国际化**:完整的中英文文档支持
1919
- 🔄 **热重载**:无需重启服务器即可更新钩子配置

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
-**High Performance**: Configurable concurrency, rate limiting (including Redis-backed distributed), and optimized request handling
1414
- 🎯 **Flexible Configuration**: Support for JSON and YAML configuration files with Go template support
1515
- 🔐 **Advanced Authentication**: Multiple trigger rule types including HMAC signature validation, IP whitelisting, and custom rules
16-
- 📊 **Observability**: Built-in Prometheus metrics, health check endpoint, OpenTelemetry tracing, audit logging, and comprehensive logging
16+
- 📊 **Observability**: Built-in Prometheus metrics, health check endpoint, optional OpenAPI spec (for Swagger/client generation), OpenTelemetry tracing, audit logging, and comprehensive logging
1717
- 🐳 **Container Ready**: Official Docker images with multiple variants
1818
- 🌍 **Internationalization**: Full support for English and Chinese documentation
1919
- 🔄 **Hot Reload**: Update hook configurations without restarting the server

cmd/config-ui/main.go

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,41 +34,41 @@ const (
3434
)
3535

3636
type pageData struct {
37-
I18N template.JS
38-
Title string
39-
Lang string
40-
ConfigSections []configSection
37+
I18N template.JS
38+
Title string
39+
Lang string
40+
ConfigSections []configSection
4141
}
4242

4343
type configSection struct {
44-
TitleKey string `yaml:"titleKey"`
45-
Options []configOption `yaml:"options"`
46-
Collapsible bool `yaml:"collapsible"`
44+
TitleKey string `yaml:"titleKey"`
45+
Options []configOption `yaml:"options"`
46+
Collapsible bool `yaml:"collapsible"`
4747
}
4848

4949
type configOption struct {
50-
Type string `yaml:"type"`
51-
ID string `yaml:"id"`
52-
Name string `yaml:"name"`
53-
LabelKey string `yaml:"labelKey"`
54-
DescKey string `yaml:"descKey"`
50+
Type string `yaml:"type"`
51+
ID string `yaml:"id"`
52+
Name string `yaml:"name"`
53+
LabelKey string `yaml:"labelKey"`
54+
DescKey string `yaml:"descKey"`
5555
Placeholder string `yaml:"placeholder"`
56-
Default string `yaml:"default"`
56+
Default string `yaml:"default"`
5757
}
5858

5959
type pageYAML struct {
6060
I18N map[string]map[string]string `yaml:"i18n"`
61-
ConfigSections []configSection `yaml:"configSections"`
61+
ConfigSections []configSection `yaml:"configSections"`
6262
}
6363

6464
type generateRequest struct {
65-
ID string `json:"id"`
66-
ExecuteCommand string `json:"execute-command"`
65+
ID string `json:"id"`
66+
ExecuteCommand string `json:"execute-command"`
6767
CommandWorkingDirectory string `json:"command-working-directory"`
6868
ResponseMessage string `json:"response-message"`
6969
HTTPMethods string `json:"http-methods"` // comma-separated or single
7070
SuccessHTTPResponseCode int `json:"success-http-response-code"`
71-
IncludeCommandOutputInResponse bool `json:"include-command-output-in-response"`
71+
IncludeCommandOutputInResponse bool `json:"include-command-output-in-response"`
7272
WebhookBaseURL string `json:"webhook_base_url"` // e.g. http://localhost:9000
7373
ResponseHeadersJSON string `json:"response-headers"`
7474
PassArgumentsToCommandJSON string `json:"pass-arguments-to-command"`
@@ -181,14 +181,14 @@ func requestToHook(req *generateRequest) *hook.Hook {
181181
return nil
182182
}
183183
h := &hook.Hook{
184-
ID: strings.TrimSpace(req.ID),
185-
ExecuteCommand: strings.TrimSpace(req.ExecuteCommand),
186-
CommandWorkingDirectory: strings.TrimSpace(req.CommandWorkingDirectory),
187-
ResponseMessage: strings.TrimSpace(req.ResponseMessage),
188-
HTTPMethods: parseHTTPMethods(req.HTTPMethods),
189-
SuccessHttpResponseCode: successCode(req.SuccessHTTPResponseCode),
190-
CaptureCommandOutput: req.IncludeCommandOutputInResponse,
191-
IncomingPayloadContentType: strings.TrimSpace(req.IncomingPayloadContentType),
184+
ID: strings.TrimSpace(req.ID),
185+
ExecuteCommand: strings.TrimSpace(req.ExecuteCommand),
186+
CommandWorkingDirectory: strings.TrimSpace(req.CommandWorkingDirectory),
187+
ResponseMessage: strings.TrimSpace(req.ResponseMessage),
188+
HTTPMethods: parseHTTPMethods(req.HTTPMethods),
189+
SuccessHttpResponseCode: successCode(req.SuccessHTTPResponseCode),
190+
CaptureCommandOutput: req.IncludeCommandOutputInResponse,
191+
IncomingPayloadContentType: strings.TrimSpace(req.IncomingPayloadContentType),
192192
}
193193
if req.ResponseHeadersJSON != "" {
194194
var headers []hook.Header

cmd/config-ui/main_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ func TestValidateOptionalJSON(t *testing.T) {
8686

8787
func TestRequestToHook(t *testing.T) {
8888
req := &generateRequest{
89-
ID: "test-hook",
90-
ExecuteCommand: "/bin/true",
91-
ResponseMessage: "OK",
92-
HTTPMethods: "POST",
89+
ID: "test-hook",
90+
ExecuteCommand: "/bin/true",
91+
ResponseMessage: "OK",
92+
HTTPMethods: "POST",
9393
SuccessHTTPResponseCode: 200,
9494
}
9595
h := requestToHook(req)

docs/en-US/API-Reference.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,33 @@ curl http://localhost:9000/metrics
8181

8282
---
8383

84-
### 4. Hook Execution Endpoint
84+
### 4. OpenAPI Specification Endpoint (Optional)
85+
86+
**Endpoint:** `GET /openapi` (or custom path via `-openapi-path`)
87+
88+
**Availability:** Only when the server is started with the `-openapi` flag (or `OPENAPI_ENABLED=true`). Not exposed by default. Recommend use only for debugging or intranet.
89+
90+
**Description:** Returns the OpenAPI 3.0.x specification (JSON) for the webhook HTTP API. Use with Swagger UI, Swagger Editor, or client code generators.
91+
92+
**Response:**
93+
- **Status Code:** `200 OK`
94+
- **Content-Type:** `application/json; charset=utf-8`
95+
- **Body:** OpenAPI 3.0.3 document describing `/`, `/health`, `/livez`, `/readyz`, `/version`, `/metrics`, and `/hooks/{id}` (or custom hook prefix).
96+
97+
**Example:**
98+
```bash
99+
# Start server with OpenAPI enabled
100+
./webhook -hooks hooks.json -openapi
101+
102+
# Fetch the spec
103+
curl http://localhost:9000/openapi
104+
```
105+
106+
You can also print the spec to stdout at startup with `-openapi-print` (e.g. `./webhook -openapi -openapi-print > openapi.json`).
107+
108+
---
109+
110+
### 5. Hook Execution Endpoint
85111

86112
**Endpoint:** `POST|GET|PUT|DELETE /hooks/{hook-id}`
87113

docs/en-US/Webhook-Parameters.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ This document describes all available command-line parameters and environment va
130130
| `-audit-workers int` | Number of audit async write workers | `2` |
131131
| `-audit-mask-ip` | Mask IP addresses in audit logs | `true` |
132132

133+
### OpenAPI
134+
135+
| Flag | Description | Default |
136+
|------|-------------|---------|
137+
| `-openapi` | Enable OpenAPI spec: serve at openapi-path and/or print to stdout; recommend only for debugging or intranet | `false` |
138+
| `-openapi-path string` | HTTP path for OpenAPI spec when openapi is enabled | `/openapi` |
139+
| `-openapi-print` | Print OpenAPI spec to stdout at startup when openapi is enabled | `false` |
140+
141+
*Note:* When `-openapi` is set, the spec is available via GET at `-openapi-path` (e.g. `/openapi`) and can be used with Swagger UI or client code generation. Use only in debugging or intranet environments. Do not set `-openapi-path` to a reserved path (e.g. `/`, `/health`, `/version`, `/metrics`, or the hooks prefix like `/hooks`); if you do, the OpenAPI route will not be registered and a warning will be logged.
142+
133143
### Other
134144

135145
| Flag | Description | Default |
@@ -256,6 +266,14 @@ All command-line parameters can also be set via environment variables:
256266
| `AUDIT_WORKERS` | `-audit-workers` | Audit async workers | `2` |
257267
| `AUDIT_MASK_IP` | `-audit-mask-ip` | Mask IP in audit logs | `true` |
258268

269+
### OpenAPI
270+
271+
| Environment Variable | CLI Flag | Description | Default |
272+
|---------------------|----------|-------------|---------|
273+
| `OPENAPI_ENABLED` | `-openapi` | Enable OpenAPI spec | `false` |
274+
| `OPENAPI_PATH` | `-openapi-path` | HTTP path for OpenAPI spec | `/openapi` |
275+
| `OPENAPI_PRINT` | `-openapi-print` | Print OpenAPI spec to stdout at startup | `false` |
276+
259277
## Security Best Practices
260278

261279
### Command Path Whitelisting
@@ -322,6 +340,10 @@ Alternatively, use `-hotreload` (or `HOT_RELOAD=true`) for automatic hot reloadi
322340
-write-timeout-seconds=60 \
323341
-max-concurrent-hooks=20
324342

343+
# With OpenAPI spec (for Swagger UI or client generation; recommend debugging/intranet only)
344+
./webhook -hooks hooks.json -openapi
345+
# Or print spec to stdout: ./webhook -hooks hooks.json -openapi -openapi-print > openapi.json
346+
325347
# Using environment variables
326348
export PORT=8080
327349
export HOOKS=/path/to/hooks.json

docs/zh-CN/API-Reference.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,33 @@ curl http://localhost:9000/metrics
8181

8282
---
8383

84-
### 4. Hook 执行端点
84+
### 4. OpenAPI 规范端点(可选)
85+
86+
**端点:** `GET /openapi`(或通过 `-openapi-path` 自定义路径)
87+
88+
**可用性:** 仅当使用 `-openapi` 参数(或 `OPENAPI_ENABLED=true`)启动服务时可用。默认不暴露,建议仅在调试或内网使用。
89+
90+
**描述:** 返回 Webhook HTTP API 的 OpenAPI 3.0.x 规范(JSON),可用于 Swagger UI、Swagger Editor 或客户端代码生成。
91+
92+
**响应:**
93+
- **状态码:** `200 OK`
94+
- **Content-Type:** `application/json; charset=utf-8`
95+
- **响应体:** 描述 `/``/health``/livez``/readyz``/version``/metrics``/hooks/{id}`(或自定义 hook 前缀)的 OpenAPI 3.0.3 文档。
96+
97+
**示例:**
98+
```bash
99+
# 启用 OpenAPI 后启动服务
100+
./webhook -hooks hooks.json -openapi
101+
102+
# 获取规范
103+
curl http://localhost:9000/openapi
104+
```
105+
106+
也可使用 `-openapi-print` 在启动时将规范打印到 stdout(例如 `./webhook -openapi -openapi-print > openapi.json`)。
107+
108+
---
109+
110+
### 5. Hook 执行端点
85111

86112
**端点:** `POST|GET|PUT|DELETE /hooks/{hook-id}`
87113

docs/zh-CN/CLI-ENV.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,21 @@
243243
- `-audit-mask-ip`
244244
在审计日志中脱敏 IP 地址(默认值:`true`
245245

246+
### OpenAPI
247+
248+
以下参数用于可选地暴露 OpenAPI 规范(便于客户端集成或 Swagger 调试);**建议仅在调试或内网环境使用**
249+
250+
- `-openapi`
251+
启用 OpenAPI 规范:在 openapi-path 提供 GET 和/或启动时打印到 stdout(默认值:`false`
252+
253+
- `-openapi-path string`
254+
启用 openapi 时,提供 OpenAPI 规范的 HTTP 路径(默认值:`/openapi`
255+
256+
请勿设置为与现有端点冲突的路径(如 `/``/health``/version``/metrics` 等)。
257+
258+
- `-openapi-print`
259+
启用 openapi 时,在启动时将规范打印到标准输出(默认值:`false`
260+
246261
### 其他
247262

248263
- `-version`
@@ -372,6 +387,14 @@
372387
| `AUDIT_WORKERS` | `-audit-workers` | 异步写入工作协程数 | `2` |
373388
| `AUDIT_MASK_IP` | `-audit-mask-ip` | 审计日志中脱敏 IP | `true` |
374389

390+
### OpenAPI
391+
392+
| 环境变量 | 命令行参数 | 说明 | 默认值 |
393+
|---------|-----------|------|--------|
394+
| `OPENAPI_ENABLED` | `-openapi` | 启用 OpenAPI 规范 | `false` |
395+
| `OPENAPI_PATH` | `-openapi-path` | OpenAPI 规范 HTTP 路径 | `/openapi` |
396+
| `OPENAPI_PRINT` | `-openapi-print` | 启动时打印规范到 stdout | `false` |
397+
375398
### 环境变量使用示例
376399

377400
```bash
@@ -402,6 +425,10 @@ export ALLOWED_COMMAND_PATHS="/usr/bin,/opt/scripts"
402425
export MAX_ARG_LENGTH=1048576
403426
export STRICT_MODE=true
404427

428+
# 可选:启用 OpenAPI 规范(仅建议在调试或内网使用)
429+
# export OPENAPI_ENABLED=true
430+
# export OPENAPI_PRINT=true # 启动时打印到 stdout
431+
405432
# 运行 webhook
406433
./webhook
407434
```

internal/flags/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ func ParseConfig() AppFlags {
8787
fs.Int("audit-workers", DEFAULT_AUDIT_WORKERS, "number of audit async write workers (default 2)")
8888
fs.Bool("audit-mask-ip", DEFAULT_AUDIT_MASK_IP, "mask IP addresses in audit logs (default true)")
8989

90+
// OpenAPI flags (recommend only for debugging or intranet)
91+
fs.Bool("openapi", DEFAULT_OPENAPI_ENABLED, "enable OpenAPI spec: serve at openapi-path and/or print to stdout; recommend only for debugging or intranet (default false)")
92+
fs.String("openapi-path", DEFAULT_OPENAPI_PATH, "HTTP path for OpenAPI spec when openapi is enabled (default /openapi)")
93+
fs.Bool("openapi-print", DEFAULT_OPENAPI_PRINT, "print OpenAPI spec to stdout at startup when openapi is enabled (default false)")
94+
9095
showVersion := fs.Bool("version", false, "display webhook version and quit")
9196
validateConfig := fs.Bool("validate-config", false, "validate configuration and exit")
9297

@@ -181,6 +186,11 @@ func ParseConfig() AppFlags {
181186
flags.AuditWorkers = configutil.ResolveInt(fs, "audit-workers", ENV_KEY_AUDIT_WORKERS, DEFAULT_AUDIT_WORKERS, false)
182187
flags.AuditMaskIP = configutil.ResolveBool(fs, "audit-mask-ip", ENV_KEY_AUDIT_MASK_IP, DEFAULT_AUDIT_MASK_IP)
183188

189+
// OpenAPI settings
190+
flags.OpenAPIEnabled = configutil.ResolveBool(fs, "openapi", ENV_KEY_OPENAPI_ENABLED, DEFAULT_OPENAPI_ENABLED)
191+
flags.OpenAPIPath = configutil.ResolveString(fs, "openapi-path", ENV_KEY_OPENAPI_PATH, DEFAULT_OPENAPI_PATH, true)
192+
flags.OpenAPIPrint = configutil.ResolveBool(fs, "openapi-print", ENV_KEY_OPENAPI_PRINT, DEFAULT_OPENAPI_PRINT)
193+
184194
// Special flags
185195
flags.ShowVersion = *showVersion
186196
flags.ValidateConfig = *validateConfig

internal/flags/define.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ const (
7575
DEFAULT_AUDIT_QUEUE_SIZE = 1000
7676
DEFAULT_AUDIT_WORKERS = 2
7777
DEFAULT_AUDIT_MASK_IP = true
78+
79+
// OpenAPI defaults
80+
DEFAULT_OPENAPI_ENABLED = false
81+
DEFAULT_OPENAPI_PATH = "/openapi"
82+
DEFAULT_OPENAPI_PRINT = false
7883
)
7984

8085
const (
@@ -149,6 +154,11 @@ const (
149154
ENV_KEY_AUDIT_QUEUE_SIZE = "AUDIT_QUEUE_SIZE"
150155
ENV_KEY_AUDIT_WORKERS = "AUDIT_WORKERS"
151156
ENV_KEY_AUDIT_MASK_IP = "AUDIT_MASK_IP"
157+
158+
// OpenAPI environment keys
159+
ENV_KEY_OPENAPI_ENABLED = "OPENAPI_ENABLED"
160+
ENV_KEY_OPENAPI_PATH = "OPENAPI_PATH"
161+
ENV_KEY_OPENAPI_PRINT = "OPENAPI_PRINT"
152162
)
153163

154164
type AppFlags struct {
@@ -225,4 +235,9 @@ type AppFlags struct {
225235
AuditQueueSize int // 异步写入队列大小
226236
AuditWorkers int // 异步写入工作协程数
227237
AuditMaskIP bool // 是否对 IP 地址进行脱敏
238+
239+
// OpenAPI settings
240+
OpenAPIEnabled bool // 是否启用 OpenAPI 规范(GET 路径或打印)
241+
OpenAPIPath string // OpenAPI 规范 HTTP 路径(默认 /openapi)
242+
OpenAPIPrint bool // 是否在启动时将规范打印到 stdout
228243
}

0 commit comments

Comments
 (0)