feat(14-01): record vendor calls with summer parity:upstream and replay them offline
- WriteUpstream masks vars, hashes long base64 JSON strings and refuses unmasked Authorization/X-Api-Key - multipart requests recorded as ordered parts; the fake compares parts and hashed payloads - loopback CONNECT recording proxy with a local ECDSA parity CA, script and forward modes - parity:upstream command, README and parity docs
This commit is contained in:
@@ -89,6 +89,67 @@ func parityBroadcastsCommand() bonfire.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func parityUpstreamCommand() bonfire.Command {
|
||||
return bonfire.Command{
|
||||
Name: "parity:upstream",
|
||||
Description: "Record the vendor HTTPS calls a reference backend sends through a loopback recording proxy into an upstream sidecar",
|
||||
Flags: []bonfire.Flag{
|
||||
{Name: "listen", Description: "Loopback address of the recording proxy", Default: tide.DefaultUpstreamProxyListen},
|
||||
{Name: "ca-dir", Description: "Directory of the local parity CA, outside the fixtures tree (key kept mode 0600)"},
|
||||
{Name: "out", Description: "Destination sidecar path (<fixture>.upstream.yaml)"},
|
||||
{Name: "mode", Description: "script (answer from --script) or forward (send once to the real vendor)", Default: "script"},
|
||||
{Name: "script", Description: "YAML file of scripted vendor responses (script mode)"},
|
||||
{Name: "vars", Description: "Private mode-0600 variable store outside fixtures; its values are masked in the sidecar"},
|
||||
},
|
||||
Run: runParityUpstream,
|
||||
}
|
||||
}
|
||||
|
||||
func runParityUpstream(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
||||
caDir, err := requireFlag(in, "ca-dir", "parity:upstream")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outPath, err := requireFlag(in, "out", "parity:upstream")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
varsPath, err := requireFlag(in, "vars", "parity:upstream")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listen := flagValue(in, "listen")
|
||||
if listen == "" {
|
||||
listen = tide.DefaultUpstreamProxyListen
|
||||
}
|
||||
proxy, err := tide.NewUpstreamProxy(tide.UpstreamProxyConfig{
|
||||
Listen: listen,
|
||||
CADir: caDir,
|
||||
Out: outPath,
|
||||
Mode: flagValue(in, "mode"),
|
||||
Script: flagValue(in, "script"),
|
||||
VarsPath: varsPath,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certPath := filepath.Join(caDir, "parity-ca.pem")
|
||||
if abs, err := filepath.Abs(certPath); err == nil {
|
||||
certPath = abs
|
||||
}
|
||||
out.Info(fmt.Sprintf("upstream proxy listening on %s", listen))
|
||||
out.Info(fmt.Sprintf("parity CA certificate: %s", certPath))
|
||||
out.Info(fmt.Sprintf("point the reference backend at it: HTTPS_PROXY=http://%s, curl.cainfo and openssl.cafile=%s", listen, certPath))
|
||||
if err := proxy.ListenAndServe(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := proxy.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Success(fmt.Sprintf("wrote %s", outPath))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runParityBroadcasts(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
||||
flowPath, err := requireFlag(in, "flow", "parity:broadcasts")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user