Skip to content

Commit fe9c247

Browse files
Fix linter issues
- Remove ctx field from Spoa struct (containedctx) - Use context parameter instead of context.Background() (contextcheck) - Add type assertion checks with ok for all type assertions (revive) - Prefix unused ctx parameters with _ (unparam) - Remove unused readKeyFromMessage function (unused)
1 parent c43c867 commit fe9c247

File tree

2 files changed

+285
-50
lines changed

2 files changed

+285
-50
lines changed

MIGRATION_PLAN.md

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# Migration Plan: Switch to github.com/dropmorepackets/haproxy-go
2+
3+
## Overview
4+
This document outlines the plan to migrate from `github.com/negasus/haproxy-spoe-go` to `github.com/dropmorepackets/haproxy-go` for improved performance (less allocations).
5+
6+
## Current Implementation
7+
- **Package**: `github.com/negasus/haproxy-spoe-go`
8+
- **Version**: v1.0.7
9+
- **Key Components Used**:
10+
- `agent.Agent` - SPOE agent/server
11+
- `request.Request` - Incoming request handler
12+
- `message.Message` - Individual message within request
13+
- `action.ScopeTransaction` - Setting variables back to HAProxy
14+
15+
## Target Implementation
16+
- **Package**: `github.com/dropmorepackets/haproxy-go`
17+
- **Version**: v0.0.7 (as used by coraza-spoa)
18+
- **Subpackages**:
19+
- `github.com/dropmorepackets/haproxy-go/spop` - SPOE agent/server
20+
- `github.com/dropmorepackets/haproxy-go/pkg/encoding` - Message encoding/decoding
21+
- **Reference**: Used by Coraza WAF (coraza-spoa repository)
22+
23+
## Key Areas to Migrate
24+
25+
### 1. Package Imports
26+
**Current:**
27+
```go
28+
import (
29+
"github.com/negasus/haproxy-spoe-go/action"
30+
"github.com/negasus/haproxy-spoe-go/agent"
31+
"github.com/negasus/haproxy-spoe-go/message"
32+
"github.com/negasus/haproxy-spoe-go/request"
33+
)
34+
```
35+
36+
**Target:**
37+
```go
38+
import (
39+
"github.com/dropmorepackets/haproxy-go/pkg/encoding"
40+
"github.com/dropmorepackets/haproxy-go/spop"
41+
)
42+
```
43+
44+
### 2. Agent/Server Initialization
45+
**Current:**
46+
```go
47+
s.Server = agent.New(handlerWrapper(s), s.logger)
48+
```
49+
50+
**Target:**
51+
```go
52+
// Spoa struct needs to implement spop.Handler interface
53+
// No separate Server field needed - create agent on Serve()
54+
agent := spop.Agent{
55+
Handler: s, // Spoa implements HandleSPOE method
56+
BaseContext: ctx,
57+
}
58+
return agent.Serve(listener)
59+
```
60+
61+
### 3. Handler Function Signature
62+
**Current:**
63+
```go
64+
func handlerWrapper(s *Spoa) func(req *request.Request) {
65+
return func(req *request.Request) {
66+
mes, err := req.Messages.GetByName(messageName)
67+
// ...
68+
}
69+
}
70+
```
71+
72+
**Target:**
73+
```go
74+
// Spoa must implement spop.Handler interface
75+
func (s *Spoa) HandleSPOE(ctx context.Context, writer *encoding.ActionWriter, message *encoding.Message) {
76+
messageName := string(message.NameBytes())
77+
switch messageName {
78+
case "crowdsec-http":
79+
s.handleHTTPRequest(ctx, writer, message)
80+
case "crowdsec-ip":
81+
s.handleIPRequest(ctx, writer, message)
82+
}
83+
}
84+
```
85+
86+
### 4. Message Reading
87+
**Current:**
88+
```go
89+
func readKeyFromMessage[T string | net.IP | netip.Addr | bool | []byte](msg *message.Message, key string) (*T, error) {
90+
value, ok := msg.KV.Get(key)
91+
// ...
92+
}
93+
```
94+
95+
**Target:**
96+
```go
97+
// Uses zero-allocation iterator pattern with pooling
98+
func readKeyFromMessage[T string | netip.Addr | bool | []byte](msg *encoding.Message, key string) (*T, error) {
99+
k := encoding.AcquireKVEntry()
100+
defer encoding.ReleaseKVEntry(k)
101+
102+
for msg.KV.Next(k) {
103+
if !k.NameEquals(key) {
104+
continue
105+
}
106+
// Extract value based on type T
107+
switch any(*new(T)).(type) {
108+
case string:
109+
val := string(k.ValueBytes())
110+
return &val, nil
111+
case netip.Addr:
112+
val := k.ValueAddr()
113+
return &val, nil
114+
case bool:
115+
val := k.ValueBool()
116+
return &val, nil
117+
case []byte:
118+
// Need to copy bytes as they're borrowed
119+
val := make([]byte, len(k.ValueBytes()))
120+
copy(val, k.ValueBytes())
121+
return &val, nil
122+
}
123+
}
124+
return nil, ErrMessageKeyNotFound
125+
}
126+
```
127+
128+
### 5. Setting Variables (Actions)
129+
**Current:**
130+
```go
131+
req.Actions.SetVar(action.ScopeTransaction, "remediation", rString)
132+
```
133+
134+
**Target:**
135+
```go
136+
// Use ActionWriter methods with VarScopeTransaction
137+
writer.SetString(encoding.VarScopeTransaction, "remediation", rString)
138+
writer.SetInt64(encoding.VarScopeTransaction, "status", int64(status))
139+
writer.SetBool(encoding.VarScopeTransaction, "ssl", true)
140+
```
141+
142+
### 6. Server Lifecycle
143+
**Current:**
144+
```go
145+
func (s *Spoa) Serve(ctx context.Context) error {
146+
err := s.Server.Serve(listener)
147+
// ...
148+
}
149+
150+
func (s *Spoa) Shutdown(ctx context.Context) error {
151+
s.ListenAddr.Close()
152+
s.ListenSocket.Close()
153+
// ...
154+
}
155+
```
156+
157+
**Target:**
158+
```go
159+
func (s *Spoa) Serve(ctx context.Context) error {
160+
// Store context for use in HandleSPOE
161+
s.ctx = ctx
162+
163+
// Create agent for each listener
164+
agent := spop.Agent{
165+
Handler: s,
166+
BaseContext: ctx,
167+
}
168+
169+
if s.ListenAddr != nil {
170+
go agent.Serve(s.ListenAddr)
171+
}
172+
if s.ListenSocket != nil {
173+
go agent.Serve(s.ListenSocket)
174+
}
175+
176+
<-ctx.Done()
177+
return nil
178+
}
179+
180+
func (s *Spoa) Shutdown(ctx context.Context) error {
181+
// Close listeners - agent will handle graceful shutdown
182+
if s.ListenAddr != nil {
183+
s.ListenAddr.Close()
184+
}
185+
if s.ListenSocket != nil {
186+
s.ListenSocket.Close()
187+
}
188+
return nil
189+
}
190+
```
191+
192+
## Migration Steps
193+
194+
1. **Research Phase**
195+
- [ ] Review coraza-spoa repository for implementation examples
196+
- [ ] Check dropmorepackets/haproxy-go documentation/API
197+
- [ ] Identify all API differences
198+
199+
2. **Dependency Update**
200+
- [ ] Update go.mod to add new dependency
201+
- [ ] Remove old dependency
202+
- [ ] Run `go mod tidy`
203+
204+
3. **Code Migration**
205+
- [ ] Update imports
206+
- [ ] Refactor Spoa struct fields
207+
- [ ] Update New() function
208+
- [ ] Update handlerWrapper()
209+
- [ ] Update readKeyFromMessage()
210+
- [ ] Update all SetVar() calls
211+
- [ ] Update Serve() and Shutdown()
212+
213+
4. **Testing**
214+
- [ ] Unit tests
215+
- [ ] Integration tests with HAProxy
216+
- [ ] Performance benchmarking (allocation comparison)
217+
218+
## Key API Differences Summary
219+
220+
### Zero-Allocation Design
221+
- Uses `encoding.AcquireKVEntry()`/`ReleaseKVEntry()` for KV entry pooling
222+
- `k.ValueBytes()` returns borrowed slices - must copy if storing
223+
- Iterator pattern: `for message.KV.Next(k)` instead of map lookup
224+
225+
### Handler Pattern
226+
- Implements `spop.Handler` interface with `HandleSPOE()` method
227+
- Receives `writer` and `message` directly (no `request.Request` wrapper)
228+
- Context passed separately
229+
230+
### Value Extraction
231+
- `k.ValueBytes()` - returns []byte (borrowed)
232+
- `k.ValueInt()` - returns int64
233+
- `k.ValueAddr()` - returns netip.Addr
234+
- `k.ValueBool()` - returns bool
235+
- `k.NameBytes()` - returns []byte (borrowed)
236+
- `k.NameEquals(string)` - compares name efficiently
237+
238+
### Setting Variables
239+
- `writer.SetString(scope, key, value)`
240+
- `writer.SetInt64(scope, key, value)`
241+
- `writer.SetBool(scope, key, value)`
242+
- Scope: `encoding.VarScopeTransaction`, `encoding.VarScopeSession`, etc.
243+
244+
## Notes
245+
- The new package is promoted as having "less allocations" - uses pooling and zero-copy where possible
246+
- Coraza uses this package, so it's production-ready
247+
- Need to maintain backward compatibility with HAProxy configuration
248+
- All existing functionality must be preserved
249+
- KV entry pooling requires careful management - must release entries
250+
- Byte slices from `ValueBytes()` are borrowed - copy if storing long-term
251+
252+
## References
253+
- [dropmorepackets/haproxy-go](https://github.com/dropmorepackets/haproxy-go)
254+
- [coraza-spoa](https://github.com/corazawaf/coraza-spoa) - Example implementation
255+

0 commit comments

Comments
 (0)