diff --git a/api/v1beta1/grafana_types.go b/api/v1beta1/grafana_types.go index 77d666e1d..8d4f2406b 100644 --- a/api/v1beta1/grafana_types.go +++ b/api/v1beta1/grafana_types.go @@ -119,6 +119,9 @@ type GrafanaClient struct { // TLS Configuration used to talk with the grafana instance. // +optional TLS *TLSConfig `json:"tls,omitempty"` + // Custom HTTP headers to use when interacting with this Grafana. + // +optional + Headers map[string]string `json:"headers,omitempty"` } // GrafanaPreferences holds Grafana preferences API settings @@ -138,8 +141,8 @@ type GrafanaStatus struct { Version string `json:"version,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // Grafana is the Schema for the grafanas API // +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".status.version",description="" @@ -154,7 +157,7 @@ type Grafana struct { Status GrafanaStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // GrafanaList contains a list of Grafana type GrafanaList struct { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index d8c3f044b..e05be5bc0 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -553,6 +553,13 @@ func (in *GrafanaClient) DeepCopyInto(out *GrafanaClient) { *out = new(TLSConfig) (*in).DeepCopyInto(*out) } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaClient. diff --git a/config/crd/bases/grafana.integreatly.org_grafanas.yaml b/config/crd/bases/grafana.integreatly.org_grafanas.yaml index 61afef7a6..b894e3386 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanas.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanas.yaml @@ -58,6 +58,12 @@ spec: description: Client defines how the grafana-operator talks to the grafana instance. properties: + headers: + additionalProperties: + type: string + description: Custom HTTP headers to use when interacting with + this Grafana. + type: object preferIngress: description: If the operator should send it's request through the grafana instances ingress object instead of through the diff --git a/controllers/client/grafana_client.go b/controllers/client/grafana_client.go index a01b941ca..35191b788 100644 --- a/controllers/client/grafana_client.go +++ b/controllers/client/grafana_client.go @@ -141,6 +141,9 @@ func NewGeneratedGrafanaClient(ctx context.Context, c client.Client, grafana *v1 } transport := NewInstrumentedRoundTripper(grafana.Name, metrics.GrafanaApiRequests, grafana.IsExternal(), tlsConfig) + if grafana.Spec.Client != nil && grafana.Spec.Client.Headers != nil { + transport.(*instrumentedRoundTripper).addHeaders(grafana.Spec.Client.Headers) //nolint:errcheck + } client := &http.Client{ Transport: transport, @@ -161,6 +164,7 @@ func NewGeneratedGrafanaClient(ctx context.Context, c client.Client, grafana *v1 if credentials.username != "" { cfg.BasicAuth = url.UserPassword(credentials.username, credentials.password) } + cl := genapi.NewHTTPClientWithConfig(nil, cfg) return cl, nil diff --git a/controllers/client/http_client.go b/controllers/client/http_client.go index eb3c11712..1fda66012 100644 --- a/controllers/client/http_client.go +++ b/controllers/client/http_client.go @@ -26,8 +26,13 @@ func NewHTTPClient(ctx context.Context, c client.Client, grafana *v1beta1.Grafan return nil, err } + transport := NewInstrumentedRoundTripper(grafana.Name, metrics.GrafanaApiRequests, grafana.IsExternal(), tlsConfig) + if grafana.Spec.Client != nil && grafana.Spec.Client.Headers != nil { + transport.(*instrumentedRoundTripper).addHeaders(grafana.Spec.Client.Headers) //nolint:errcheck + } + return &http.Client{ - Transport: NewInstrumentedRoundTripper(grafana.Name, metrics.GrafanaApiRequests, grafana.IsExternal(), tlsConfig), + Transport: transport, Timeout: time.Second * timeout, }, nil } diff --git a/controllers/client/round_tripper.go b/controllers/client/round_tripper.go index 9043cdd5b..4b3d10f72 100644 --- a/controllers/client/round_tripper.go +++ b/controllers/client/round_tripper.go @@ -13,6 +13,7 @@ type instrumentedRoundTripper struct { relatedResource string wrapped http.RoundTripper metric *prometheus.CounterVec + headers map[string]string } func NewInstrumentedRoundTripper(relatedResource string, metric *prometheus.CounterVec, useProxy bool, tlsConfig *tls.Config) http.RoundTripper { @@ -29,15 +30,24 @@ func NewInstrumentedRoundTripper(relatedResource string, metric *prometheus.Coun transport.Proxy = nil } + headers := make(map[string]string) + headers["user-agent"] = "grafana-operator/" + embeds.Version + return &instrumentedRoundTripper{ relatedResource: relatedResource, wrapped: transport, metric: metric, + headers: headers, } } func (in *instrumentedRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { - r.Header.Add("user-agent", "grafana-operator/"+embeds.Version) + if in.headers != nil { + for k, v := range in.headers { + r.Header.Add(k, v) + } + } + resp, err := in.wrapped.RoundTrip(r) if resp != nil { in.metric.WithLabelValues( @@ -48,3 +58,17 @@ func (in *instrumentedRoundTripper) RoundTrip(r *http.Request) (*http.Response, } return resp, err } + +func (in *instrumentedRoundTripper) addHeaders(headers map[string]string) { + if headers == nil { + return + } + + if in.headers == nil { + in.headers = make(map[string]string) + } + + for k, v := range headers { + in.headers[k] = v + } +} diff --git a/controllers/grafana_controller.go b/controllers/grafana_controller.go index e54c6b360..124064ab9 100644 --- a/controllers/grafana_controller.go +++ b/controllers/grafana_controller.go @@ -56,14 +56,14 @@ type GrafanaReconciler struct { IsOpenShift bool } -//+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanas,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanas/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanas/finalizers,verbs=update -//+kubebuilder:rbac:groups=route.openshift.io,resources=routes;routes/custom-host,verbs=get;list;create;update;delete;watch -//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch;create;patch -//+kubebuilder:rbac:groups="",resources=configmaps;secrets;serviceaccounts;services;persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanas,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanas/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanas/finalizers,verbs=update +// +kubebuilder:rbac:groups=route.openshift.io,resources=routes;routes/custom-host,verbs=get;list;create;update;delete;watch +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch;create;patch +// +kubebuilder:rbac:groups="",resources=configmaps;secrets;serviceaccounts;services;persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete func (r *GrafanaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { controllerLog := log.FromContext(ctx).WithName("GrafanaReconciler") diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanas.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanas.yaml index 61afef7a6..b894e3386 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanas.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanas.yaml @@ -58,6 +58,12 @@ spec: description: Client defines how the grafana-operator talks to the grafana instance. properties: + headers: + additionalProperties: + type: string + description: Custom HTTP headers to use when interacting with + this Grafana. + type: object preferIngress: description: If the operator should send it's request through the grafana instances ingress object instead of through the diff --git a/deploy/kustomize/base/crds.yaml b/deploy/kustomize/base/crds.yaml index 349e9b450..b03bc4b21 100644 --- a/deploy/kustomize/base/crds.yaml +++ b/deploy/kustomize/base/crds.yaml @@ -1882,6 +1882,12 @@ spec: description: Client defines how the grafana-operator talks to the grafana instance. properties: + headers: + additionalProperties: + type: string + description: Custom HTTP headers to use when interacting with + this Grafana. + type: object preferIngress: description: If the operator should send it's request through the grafana instances ingress object instead of through the diff --git a/docs/docs/api.md b/docs/docs/api.md index afecad452..86e7a9dba 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -3785,6 +3785,13 @@ Client defines how the grafana-operator talks to the grafana instance.