diff --git a/api/v1alpha1/output_types.go b/api/v1alpha1/output_types.go index 2ee7957..e884501 100644 --- a/api/v1alpha1/output_types.go +++ b/api/v1alpha1/output_types.go @@ -36,12 +36,12 @@ type OutputSpec struct { // +optional Service *OutputServiceSpec `json:"service,omitempty"` // ServiceRef references a Kubernetes Service to use as the output address. - // Supported for: nats, jetstream, kafka outputs. + // Supported for: nats, jetstream, kafka, remote_write, influxdb outputs. // The service address will be resolved and injected into the output config. // +optional ServiceRef *ServiceReference `json:"serviceRef,omitempty"` // ServiceSelector selects Kubernetes Services by labels to use as output addresses. - // Supported for: nats, jetstream, kafka outputs. + // Supported for: nats, jetstream, kafka, remote_write, influxdb outputs. // All matching service addresses will be resolved and injected into the output config. // +optional ServiceSelector *ServiceSelector `json:"serviceSelector,omitempty"` @@ -58,6 +58,13 @@ type ServiceReference struct { // If not specified, the first port of the service is used. // +optional Port string `json:"port,omitempty"` + // URL is an optional path suffix appended after the resolved service address + // (scheme, host, and port). A leading slash on the value is optional; the + // operator joins the base and suffix with a single '/'. Use this for HTTP(S) + // outputs such as prometheus_write (for example api/v1/write or api/v1/push) + // or influxdb instead of putting the full URL in config. + // +optional + URL string `json:"url,omitempty"` } // ServiceSelector selects Services by labels @@ -71,6 +78,10 @@ type ServiceSelector struct { // If not specified, the first port of the service is used. // +optional Port string `json:"port,omitempty"` + // URL is an optional path suffix appended after each resolved service address. + // Same semantics as ServiceReference.url (see ServiceReference). + // +optional + URL string `json:"url,omitempty"` } // OutputServiceSpec defines the service configuration for outputs that expose an endpoint diff --git a/config/crd/bases/operator.gnmic.dev_outputs.yaml b/config/crd/bases/operator.gnmic.dev_outputs.yaml index 99a5d36..7a7b55d 100644 --- a/config/crd/bases/operator.gnmic.dev_outputs.yaml +++ b/config/crd/bases/operator.gnmic.dev_outputs.yaml @@ -74,7 +74,7 @@ spec: serviceRef: description: |- ServiceRef references a Kubernetes Service to use as the output address. - Supported for: nats, jetstream, kafka outputs. + Supported for: nats, jetstream, kafka, remote_write, influxdb outputs. The service address will be resolved and injected into the output config. properties: name: @@ -89,13 +89,21 @@ spec: Port is the name or number of the port to use. If not specified, the first port of the service is used. type: string + url: + description: |- + URL is an optional path suffix appended after the resolved service address + (scheme, host, and port). A leading slash on the value is optional; the + operator joins the base and suffix with a single '/'. Use this for HTTP(S) + outputs such as prometheus_write (for example api/v1/write or api/v1/push) + or influxdb instead of putting the full URL in config. + type: string required: - name type: object serviceSelector: description: |- ServiceSelector selects Kubernetes Services by labels to use as output addresses. - Supported for: nats, jetstream, kafka outputs. + Supported for: nats, jetstream, kafka, remote_write, influxdb outputs. All matching service addresses will be resolved and injected into the output config. properties: matchLabels: @@ -113,6 +121,11 @@ spec: Port is the name or number of the port to use. If not specified, the first port of the service is used. type: string + url: + description: |- + URL is an optional path suffix appended after each resolved service address. + Same semantics as ServiceReference.url (see ServiceReference). + type: string required: - matchLabels type: object diff --git a/docs/content/docs/user-guide/output.md b/docs/content/docs/user-guide/output.md index 6dbc651..cf7e125 100644 --- a/docs/content/docs/user-guide/output.md +++ b/docs/content/docs/user-guide/output.md @@ -51,6 +51,7 @@ Defines the output address or URL as a service reference. | `name` | string | Yes | Name of the Kubernetes Service | | `namespace` | string | No | Namespace of the Service (defaults to Output's namespace) | | `port` | string | No | Port name or number (defaults to first port) | +| `url` | string | No | Path suffix appended after the resolved `scheme://host:port` (optional leading slash). Use for HTTP(S) outputs such as `prometheus_write` or `influxdb` (for example `api/v1/write` or `api/v1/push`) so you do not need the full URL in `config`. | ### ServiceSelector @@ -61,6 +62,7 @@ Defines the output address or URL as a service selector. | `matchLabels` | map[string]string | Yes | Labels to match services | | `namespace` | string | No | Namespace to search (defaults to Output's namespace) | | `port` | string | No | Port name or number (defaults to first port) | +| `url` | string | No | Path suffix appended after each resolved address; same meaning as `serviceRef.url`. | - It is recommended to label outputs for flexible selection when building Pipelines. @@ -208,6 +210,19 @@ spec: The operator resolves the service and constructs the URL as `http://prometheus-server.{namespace}.svc.cluster.local:9090`. +To set the HTTP path without duplicating the host in `config`, use `serviceRef.url` (for example Grafana Mimir at `/api/v1/push`): + +```yaml +spec: + type: prometheus_write + serviceRef: + name: mimir-distributor + port: http + url: api/v1/push + config: + timeout: 10s +``` + ### Using Service Selector Discover multiple Kafka brokers using labels: @@ -483,6 +498,10 @@ For outputs that connect to external systems (NATS, Kafka, InfluxDB, Prometheus | **Result** | Single address | Multiple addresses (comma-separated) | | **Cross-namespace** | Yes (specify namespace) | Yes (specify namespace) | +### Path suffix (`url`) + +For `serviceRef` and `serviceSelector`, optional field `url` is appended after the resolved address (scheme, DNS name, and port). It is most useful for `prometheus_write` and `influxdb`, where gNMIc expects a full URL: you can rely on service discovery for the host and port and set only the path here (for example `api/v1/write` for Prometheus or `api/v1/push` for Mimir). Leading slashes are optional. + 1. The operator watches for Output resources 2. During reconciliation, it resolves the referenced Service(s) 3. Addresses are formatted with the appropriate scheme (`nats://`) diff --git a/internal/controller/cluster_controller.go b/internal/controller/cluster_controller.go index 24d7a1d..8641b32 100644 --- a/internal/controller/cluster_controller.go +++ b/internal/controller/cluster_controller.go @@ -2334,6 +2334,11 @@ func (r *ClusterReconciler) resolveOutputServiceAddresses(ctx context.Context, o // use cluster DNS name for the service host := fmt.Sprintf("%s.%s.svc.cluster.local", svc.Name, svc.Namespace) addr := gnmic.FormatServiceAddress(spec, host, port) + if spec.ServiceRef.URL != "" { + url := strings.TrimPrefix(spec.ServiceRef.URL, "/") + addr = strings.TrimSuffix(addr, "/") + addr = fmt.Sprintf("%s/%s", addr, url) + } resolved = append(resolved, addr) } @@ -2369,6 +2374,11 @@ func (r *ClusterReconciler) resolveOutputServiceAddresses(ctx context.Context, o // use cluster DNS name for the service host := fmt.Sprintf("%s.%s.svc.cluster.local", svc.Name, svc.Namespace) addr := gnmic.FormatServiceAddress(spec, host, port) + if spec.ServiceSelector.URL != "" { + url := strings.TrimPrefix(spec.ServiceSelector.URL, "/") + addr = strings.TrimSuffix(addr, "/") + addr = fmt.Sprintf("%s/%s", addr, url) + } resolved = append(resolved, addr) } } diff --git a/internal/gnmic/output.go b/internal/gnmic/output.go index c4723b2..029fb1c 100644 --- a/internal/gnmic/output.go +++ b/internal/gnmic/output.go @@ -47,6 +47,8 @@ type outputConfigOptions struct { ResolvedAddresses []string // TLS options to inject into the output config TLS *TLSOptions + // URL to append to discovered service addresses + URL string } type TLSOptions struct { @@ -89,6 +91,7 @@ func buildOutputConfig(spec *gnmicv1alpha1.OutputSpec, options *outputConfigOpti config["address"] = strings.Join(options.ResolvedAddresses, ",") case PrometheusWriteOutputType: config["url"] = strings.Join(options.ResolvedAddresses, ",") + case InfluxDBOutputType: config["url"] = strings.Join(options.ResolvedAddresses, ",") } diff --git a/internal/gnmic/plan.go b/internal/gnmic/plan.go index c4db779..863191e 100644 --- a/internal/gnmic/plan.go +++ b/internal/gnmic/plan.go @@ -60,7 +60,6 @@ type resourceRelationship struct { // NewPlanBuilder creates a new PlanBuilder func NewPlanBuilder(clusterName string, credsFetcher CredentialsFetcher) *PlanBuilder { - return &PlanBuilder{ clusterName: clusterName, pipelines: make(map[string]*PipelineData),