mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
// Command meter-proxy is the ONE binary that commercializes any non-Go Hanzo
|
|
// product. It runs as a sidecar next to the product (vector, search, …),
|
|
// terminates the product's port, gates every request on the caller's prepaid
|
|
// commerce balance (fail-closed) and records per-org usage — then forwards to
|
|
// the product on localhost. The product itself needs zero changes.
|
|
//
|
|
// All configuration is env vars the operator wires (the commerce service token
|
|
// from a KMS-backed secret), so one image serves every product:
|
|
//
|
|
// METER_PROXY_LISTEN address to listen on (default :8080)
|
|
// METER_PROXY_UPSTREAM the product's real address (required, e.g. http://127.0.0.1:6333)
|
|
// METER_PROXY_PROVIDER usage label (required, e.g. vector)
|
|
// METER_PROXY_PRICES price-table spec (see metering/proxy.ParsePriceTable)
|
|
// METER_PROXY_SKIP comma list of path prefixes to bypass (health/metrics)
|
|
//
|
|
// COMMERCE_URL commerce base url (default in-cluster commerce)
|
|
// COMMERCE_SERVICE_TOKEN admin S2S token (KMS-backed) (required to bill)
|
|
// COMMERCE_SERVICE_ORG default org when a request carries none
|
|
//
|
|
// No variable turns the gate off: a request whose balance or spend cap cannot be
|
|
// read is refused with 503, and every served request is recorded.
|
|
package main
|
|
|
|
import (
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hanzoai/commerce/metering"
|
|
"github.com/hanzoai/commerce/metering/proxy"
|
|
)
|
|
|
|
func main() {
|
|
listen := envOr("METER_PROXY_LISTEN", ":8080")
|
|
upstream := strings.TrimSpace(os.Getenv("METER_PROXY_UPSTREAM"))
|
|
provider := strings.TrimSpace(os.Getenv("METER_PROXY_PROVIDER"))
|
|
pricesSpec := os.Getenv("METER_PROXY_PRICES")
|
|
skip := splitList(os.Getenv("METER_PROXY_SKIP"))
|
|
|
|
if upstream == "" {
|
|
log.Fatal("meter-proxy: METER_PROXY_UPSTREAM is required")
|
|
}
|
|
if provider == "" {
|
|
log.Fatal("meter-proxy: METER_PROXY_PROVIDER is required")
|
|
}
|
|
|
|
meter, err := metering.FromEnv()
|
|
if err != nil {
|
|
log.Fatalf("meter-proxy: metering config: %v", err)
|
|
}
|
|
|
|
handler, err := proxy.New(proxy.Config{
|
|
Upstream: upstream,
|
|
Provider: provider,
|
|
Prices: proxy.ParsePriceTable(pricesSpec),
|
|
SkipPaths: skip,
|
|
Meter: meter,
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("meter-proxy: %v", err)
|
|
}
|
|
|
|
srv := &http.Server{
|
|
Addr: listen,
|
|
Handler: handler,
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
}
|
|
|
|
log.Printf("meter-proxy: provider=%s listen=%s upstream=%s", provider, listen, upstream)
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("meter-proxy: serve: %v", err)
|
|
}
|
|
}
|
|
|
|
func envOr(key, def string) string {
|
|
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func splitList(s string) []string {
|
|
var out []string
|
|
for _, p := range strings.Split(s, ",") {
|
|
if p = strings.TrimSpace(p); p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|