Backend Services
Each extension backend is a Go HTTP server that queries the underlying system and exposes a JSON API consumed by the UI via ArgoCD’s proxy.
HTTP handlers
Use Go 1.22+ routing patterns:
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/metrics", s.handleMetrics)
mux.HandleFunc("GET /api/v1/logs", s.handleLogs)
mux.HandleFunc("GET /api/v1/logs/label/{name}/values", s.handleLabelValues) ArgoCD identity headers
Backend services receive identity headers from ArgoCD’s proxy. Use them for authorization and scoping:
func userFromRequest(r *http.Request) (username, userID string, groups []string) {
username = r.Header.Get("Argocd-Username")
userID = r.Header.Get("Argocd-User-Id")
groups = strings.Split(r.Header.Get("Argocd-User-Groups"), ",")
return
}
func clusterFromRequest(r *http.Request) (name, url string) {
name = r.Header.Get("Argocd-Target-Cluster-Name")
url = r.Header.Get("Argocd-Target-Cluster-URL")
return
} Namespace authorization
Every ArgoPlane backend enforces namespace scoping. The Argocd-Application-Name header (format namespace:name, set by ArgoCD’s proxy) identifies the calling Application. Before serving data, the backend fetches that Application via the Kubernetes API and checks that the requested namespace is one the Application manages: the destination namespace plus any namespace in status.resources. Anything else gets a 403.
If you build a new backend, reuse this pattern. It needs a ServiceAccount with a ClusterRole granting get on applications.argoproj.io (the Helm chart wires this up per extension via serviceAccount.create and rbac.rules).
Configuration
Use envconfig struct tags. Load in main(), pass values to constructors:
type Config struct {
Port string `envconfig:"PORT" default:"8080"`
PrometheusURL string `envconfig:"PROMETHEUS_URL" required:"true"`
LogLevel string `envconfig:"LOG_LEVEL" default:"info"`
} Error handling
Always wrap errors with context:
if err != nil {
return nil, fmt.Errorf("failed to fetch flows: %w", err)
} Logging
Use log/slog with structured key-value pairs:
slog.Info("querying prometheus", "url", config.PrometheusURL, "query", query)
slog.Error("failed to fetch flows", "error", err, "namespace", ns) Import organization
Three groups separated by blank lines: stdlib, external, internal:
import (
"context"
"fmt"
"net/http"
"github.com/prometheus/client_golang/api"
"github.com/natrontech/argoplane/extensions/metrics/internal/query"
) Graceful shutdown
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
srv := &http.Server{Addr: ":" + config.Port, Handler: mux}
go func() {
<-ctx.Done()
srv.Shutdown(context.Background())
}()
srv.ListenAndServe() Naming conventions
- Constructors:
New<Type>(...) (*Type, error) - Method receivers: short names (
s *Server,c *Client) - No
Get/Listprefixes:Flows()notGetFlows() - Packages: single lowercase word (
metrics,logs,proxy)