From db0b62f8be16d4ff549abd789c3f26218342590e Mon Sep 17 00:00:00 2001
From: Peter Yurkovich <47438010+PeterYurkovich@users.noreply.github.com>
Date: Wed, 5 Jun 2024 08:26:35 -0400
Subject: [PATCH] feat: add distributed tracing and troubleshooting panel
uiplugins (#480)
---
Makefile.tools | 3 +-
...bility-operator.clusterserviceversion.yaml | 6 +
.../observability.openshift.io_uiplugins.yaml | 41 ++++++
cmd/operator/main.go | 10 +-
.../observability.openshift.io_uiplugins.yaml | 41 ++++++
.../observability-operator-cluster-role.yaml | 6 +
docs/api.md | 119 ++++++++++++++++-
docs/user-guides/observability-ui-plugins.md | 32 +++++
pkg/apis/uiplugin/v1alpha1/types.go | 61 ++++++++-
.../v1alpha1/zz_generated.deepcopy.go | 58 ++++++++-
.../uiplugin/compatibility_matrix.go | 16 +++
.../uiplugin/compatibility_matrix_test.go | 38 ++++++
pkg/controllers/uiplugin/components.go | 97 ++++++++++----
pkg/controllers/uiplugin/controller.go | 6 +-
.../uiplugin/distributed_tracing.go | 120 ++++++++++++++++++
.../uiplugin/plugin_info_builder.go | 31 +++--
.../uiplugin/troubleshooting_panel.go | 101 +++++++++++++++
pkg/operator/operator.go | 3 +
18 files changed, 747 insertions(+), 42 deletions(-)
create mode 100644 pkg/controllers/uiplugin/distributed_tracing.go
create mode 100644 pkg/controllers/uiplugin/troubleshooting_panel.go
diff --git a/Makefile.tools b/Makefile.tools
index 0ebc720f..21f7887d 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -188,7 +188,8 @@ $(SHELLCHECK) shellcheck: $(TOOLS_DIR)
set -ex ;\
[[ -f $(SHELLCHECK) ]] && exit 0 ;\
cd $$(mktemp -d) ;\
- curl -sSLo shellcheck-stable.tar.xz "https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.x86_64.tar.xz";\
+ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
+ curl -sSLo shellcheck-stable.tar.xz "https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.$$(OS).$$(ARCH).tar.xz";\
tar -xJf shellcheck-stable.tar.xz ;\
cp shellcheck-stable/shellcheck $(SHELLCHECK) ;\
version=$(SHELLCHECK_VERSION) ;\
diff --git a/bundle/manifests/observability-operator.clusterserviceversion.yaml b/bundle/manifests/observability-operator.clusterserviceversion.yaml
index 2da82571..a9419484 100644
--- a/bundle/manifests/observability-operator.clusterserviceversion.yaml
+++ b/bundle/manifests/observability-operator.clusterserviceversion.yaml
@@ -512,6 +512,12 @@ spec:
- securitycontextconstraints
verbs:
- use
+ - apiGroups:
+ - tempo.grafana.com
+ resources:
+ - tempostacks
+ verbs:
+ - list
serviceAccountName: observability-operator-sa
deployments:
- label:
diff --git a/bundle/manifests/observability.openshift.io_uiplugins.yaml b/bundle/manifests/observability.openshift.io_uiplugins.yaml
index f95be930..473edf0b 100644
--- a/bundle/manifests/observability.openshift.io_uiplugins.yaml
+++ b/bundle/manifests/observability.openshift.io_uiplugins.yaml
@@ -39,10 +39,51 @@ spec:
spec:
description: UIPluginSpec is the specification for desired state of UIPlugin.
properties:
+ distributedTracing:
+ description: DistributedTracing contains configuration for the distributed
+ tracing console plugin.
+ properties:
+ timeout:
+ description: |-
+ Timeout is the maximum duration before a query timeout.
+
+
+ The value is expected to be a sequence of digits followed by a unit suffix, which can be 's' (seconds)
+ or 'm' (minutes).
+ pattern: ^([0-9]+)([sm]{1})$
+ type: string
+ type: object
+ troubleshootingPanel:
+ description: TroubleshootingPanel contains configuration for the troubleshooting
+ console plugin.
+ properties:
+ korrel8r:
+ description: korrel8r defines the Korrel8r instance that the troubleshooting
+ panel plugin will connect to
+ properties:
+ name:
+ description: Name of the korrel8r instance
+ type: string
+ namespace:
+ description: Namespace of the korrel8r instance
+ type: string
+ type: object
+ timeout:
+ description: |-
+ Timeout is the maximum duration before a query timeout.
+
+
+ The value is expected to be a sequence of digits followed by a unit suffix, which can be 's' (seconds)
+ or 'm' (minutes).
+ pattern: ^([0-9]+)([sm]{1})$
+ type: string
+ type: object
type:
description: Type defines the UI plugin.
enum:
- Dashboards
+ - TroubleshootingPanel
+ - DistributedTracing
type: string
required:
- type
diff --git a/cmd/operator/main.go b/cmd/operator/main.go
index 60d21c06..5af285dc 100644
--- a/cmd/operator/main.go
+++ b/cmd/operator/main.go
@@ -36,10 +36,12 @@ import (
// prometheus-operator. For thanos we use the default version from
// prometheus-operator.
var defaultImages = map[string]string{
- "prometheus": "",
- "alertmanager": "",
- "thanos": obopo.DefaultThanosImage,
- "ui-dashboards": "quay.io/openshift-observability-ui/console-dashboards-plugin:v0.1.0",
+ "prometheus": "",
+ "alertmanager": "",
+ "thanos": obopo.DefaultThanosImage,
+ "ui-dashboards": "quay.io/openshift-observability-ui/console-dashboards-plugin:v0.1.0",
+ "ui-troubleshooting-panel": "quay.io/openshift-observability-ui/troubleshooting-panel-console-plugin:v0.1.0",
+ "ui-distributed-tracing": "quay.io/openshift-observability-ui/distributed-tracing-console-plugin:v0.1.0",
}
func imagesUsed() []string {
diff --git a/deploy/crds/common/observability.openshift.io_uiplugins.yaml b/deploy/crds/common/observability.openshift.io_uiplugins.yaml
index c091aed3..5f437472 100644
--- a/deploy/crds/common/observability.openshift.io_uiplugins.yaml
+++ b/deploy/crds/common/observability.openshift.io_uiplugins.yaml
@@ -39,10 +39,51 @@ spec:
spec:
description: UIPluginSpec is the specification for desired state of UIPlugin.
properties:
+ distributedTracing:
+ description: DistributedTracing contains configuration for the distributed
+ tracing console plugin.
+ properties:
+ timeout:
+ description: |-
+ Timeout is the maximum duration before a query timeout.
+
+
+ The value is expected to be a sequence of digits followed by a unit suffix, which can be 's' (seconds)
+ or 'm' (minutes).
+ pattern: ^([0-9]+)([sm]{1})$
+ type: string
+ type: object
+ troubleshootingPanel:
+ description: TroubleshootingPanel contains configuration for the troubleshooting
+ console plugin.
+ properties:
+ korrel8r:
+ description: korrel8r defines the Korrel8r instance that the troubleshooting
+ panel plugin will connect to
+ properties:
+ name:
+ description: Name of the korrel8r instance
+ type: string
+ namespace:
+ description: Namespace of the korrel8r instance
+ type: string
+ type: object
+ timeout:
+ description: |-
+ Timeout is the maximum duration before a query timeout.
+
+
+ The value is expected to be a sequence of digits followed by a unit suffix, which can be 's' (seconds)
+ or 'm' (minutes).
+ pattern: ^([0-9]+)([sm]{1})$
+ type: string
+ type: object
type:
description: Type defines the UI plugin.
enum:
- Dashboards
+ - TroubleshootingPanel
+ - DistributedTracing
type: string
required:
- type
diff --git a/deploy/operator/observability-operator-cluster-role.yaml b/deploy/operator/observability-operator-cluster-role.yaml
index f4366229..4588c133 100644
--- a/deploy/operator/observability-operator-cluster-role.yaml
+++ b/deploy/operator/observability-operator-cluster-role.yaml
@@ -249,3 +249,9 @@ rules:
- securitycontextconstraints
verbs:
- use
+- apiGroups:
+ - tempo.grafana.com
+ resources:
+ - tempostacks
+ verbs:
+ - list
diff --git a/docs/api.md b/docs/api.md
index ee74f864..ecdd1455 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -2854,9 +2854,126 @@ UIPluginSpec is the specification for desired state of UIPlugin.
Type defines the UI plugin.
- Enum: Dashboards
+ Enum: Dashboards, TroubleshootingPanel, DistributedTracing
|
true |
+
+ distributedTracing |
+ object |
+
+ DistributedTracing contains configuration for the distributed tracing console plugin.
+ |
+ false |
+
+ troubleshootingPanel |
+ object |
+
+ TroubleshootingPanel contains configuration for the troubleshooting console plugin.
+ |
+ false |
+
+
+
+
+### UIPlugin.spec.distributedTracing
+[↩ Parent](#uipluginspec)
+
+
+
+DistributedTracing contains configuration for the distributed tracing console plugin.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ timeout |
+ string |
+
+ Timeout is the maximum duration before a query timeout.
+
+
+The value is expected to be a sequence of digits followed by a unit suffix, which can be 's' (seconds)
+or 'm' (minutes).
+ |
+ false |
+
+
+
+
+### UIPlugin.spec.troubleshootingPanel
+[↩ Parent](#uipluginspec)
+
+
+
+TroubleshootingPanel contains configuration for the troubleshooting console plugin.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ korrel8r |
+ object |
+
+ korrel8r defines the Korrel8r instance that the troubleshooting panel plugin will connect to
+ |
+ false |
+
+ timeout |
+ string |
+
+ Timeout is the maximum duration before a query timeout.
+
+
+The value is expected to be a sequence of digits followed by a unit suffix, which can be 's' (seconds)
+or 'm' (minutes).
+ |
+ false |
+
+
+
+
+### UIPlugin.spec.troubleshootingPanel.korrel8r
+[↩ Parent](#uipluginspectroubleshootingpanel)
+
+
+
+korrel8r defines the Korrel8r instance that the troubleshooting panel plugin will connect to
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ name |
+ string |
+
+ Name of the korrel8r instance
+ |
+ false |
+
+ namespace |
+ string |
+
+ Namespace of the korrel8r instance
+ |
+ false |
diff --git a/docs/user-guides/observability-ui-plugins.md b/docs/user-guides/observability-ui-plugins.md
index 7ca2dc15..02719fc4 100644
--- a/docs/user-guides/observability-ui-plugins.md
+++ b/docs/user-guides/observability-ui-plugins.md
@@ -5,6 +5,8 @@ Using the Observability UI, you can install and manage plugins that extend the o
## Plugins
- [dashboards](#dashboards): Add enhanced dashboards to the OpenShift web console. This plugin allows you to add other Prometheus datasources present in the cluster, apart from the in-cluster one, to the default dashboards.
+- [troubleshooting-panel](#troubleshooting-panel): Add the troubleshooting panel to the OpenShift web console. This plugin adds a troubleshooting panel to the console dashboard, which queries and displays results from [Korrel8r](https://github.com/korrel8r/korrel8r) to help troubleshoot issues.
+- [distributed-tracing](#distributed-tracing): Add the Observability > Traces page to the Openshift web console. This plugin allows a user to select a [TempoStack](https://docs.openshift.com/container-platform/4.15/observability/distr_tracing/distr_tracing_rn/distr-tracing-rn-3-1-1.html) instance and view trace data from it.
### Dashboards
@@ -20,3 +22,33 @@ metadata:
spec:
type: Dashboards
```
+
+### Troubleshooting Panel
+
+The plugin will connect to a Korrel8r instance named `korrel8r` in the `korrel8r` namespace. A "Troubleshooting Panel" button is added to the alerts page, which will convert the current alert into a Korrel8r query, then retrieve related neighbors and display them in a topology view.
+
+To enable the troubleshooting panel console plugin, create a `UIPlugin` CR. The following example shows how to create a CR to enable the troubleshooting panel console plugin:
+
+```yaml
+apiVersion: observability.openshift.io/v1alpha1
+kind: UIPlugin
+metadata:
+ name: troubleshooting-panel-console-plugin
+spec:
+ type: TroubleshootingPanel
+```
+
+### Distributed Tracing
+
+The plugin allows a user to select a TempoStack instance and query traces from it to display them as a table and a scatter plot.
+
+To enable to distributed tracing console plugin, create a `UIPlugin` CR. The following example shows how to create a CR to enable the distributed tracing console plugin:
+
+```yaml
+apiVersion: observability.openshift.io/v1alpha1
+kind: UIPlugin
+metadata:
+ name: distributed-tracing-console-plugin
+spec:
+ type: DistributedTracing
+```
diff --git a/pkg/apis/uiplugin/v1alpha1/types.go b/pkg/apis/uiplugin/v1alpha1/types.go
index 8fa16127..9aae6410 100644
--- a/pkg/apis/uiplugin/v1alpha1/types.go
+++ b/pkg/apis/uiplugin/v1alpha1/types.go
@@ -31,20 +31,79 @@ type UIPluginList struct {
Items []UIPlugin `json:"items"`
}
-// +kubebuilder:validation:Enum=Dashboards
+// +kubebuilder:validation:Enum=Dashboards;TroubleshootingPanel;DistributedTracing
type UIPluginType string
const (
// TypeDashboards deploys the Dashboards Dynamic Plugin for OpenShift Console.
TypeDashboards UIPluginType = "Dashboards"
+ // DistributedTracing deploys the Distributed Tracing Dynamic Plugin for the OpenShift Console
+ TypeDistributedTracing UIPluginType = "DistributedTracing"
+ // TroubleshootingPanel deploys the Troubleshooting Panel Dynamic Plugin for the OpenShift Console
+ TypeTroubleshootingPanel UIPluginType = "TroubleshootingPanel"
)
+// TroubleshootingPanelConfig contains options for configuring the Troubleshooting Panel plugin
+type TroubleshootingPanelConfig struct {
+ // Timeout is the maximum duration before a query timeout.
+ //
+ // The value is expected to be a sequence of digits followed by a unit suffix, which can be 's' (seconds)
+ // or 'm' (minutes).
+ //
+ // +optional
+ // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="OCP Console Query Timeout",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:ocpConsoleTimeout"}
+ // +kubebuilder:validation:Pattern:="^([0-9]+)([sm]{1})$"
+ Timeout string `json:"timeout,omitempty"`
+ // korrel8r defines the Korrel8r instance that the troubleshooting panel plugin will connect to
+ //
+ // +optional
+ // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Korrel8r Instance"
+ Korrel8r TroubleshootingPanelKorrel8rConfig `json:"korrel8r,omitempty"`
+}
+
+type TroubleshootingPanelKorrel8rConfig struct {
+ // Name of the korrel8r instance
+ //
+ // +optional
+ // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Korrel8r Instance Name"
+ Name string `json:"name,omitempty"`
+
+ // Namespace of the korrel8r instance
+ //
+ // +optional
+ // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Korrel8r Instance Namespace"
+ Namespace string `json:"namespace,omitempty"`
+}
+
+// DistributedTracingConfig contains options for configuring the Distributed Tracing plugin
+type DistributedTracingConfig struct {
+ // Timeout is the maximum duration before a query timeout.
+ //
+ // The value is expected to be a sequence of digits followed by a unit suffix, which can be 's' (seconds)
+ // or 'm' (minutes).
+ //
+ // +optional
+ // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="OCP Console Query Timeout",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:ocpConsoleTimeout"}
+ // +kubebuilder:validation:Pattern:="^([0-9]+)([sm]{1})$"
+ Timeout string `json:"timeout,omitempty"`
+}
+
// UIPluginSpec is the specification for desired state of UIPlugin.
type UIPluginSpec struct {
// Type defines the UI plugin.
// +required
// +kubebuilder:validation:Required
Type UIPluginType `json:"type"`
+
+ // TroubleshootingPanel contains configuration for the troubleshooting console plugin.
+ //
+ // +kubebuilder:validation:Optional
+ TroubleshootingPanel *TroubleshootingPanelConfig `json:"troubleshootingPanel,omitempty"`
+
+ // DistributedTracing contains configuration for the distributed tracing console plugin.
+ //
+ // +kubebuilder:validation:Optional
+ DistributedTracing *DistributedTracingConfig `json:"distributedTracing,omitempty"`
}
// UIPluginStatus defines the observed state of UIPlugin.
diff --git a/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go
index 292e70e7..33854060 100644
--- a/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go
@@ -40,12 +40,58 @@ func (in *Condition) DeepCopy() *Condition {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DistributedTracingConfig) DeepCopyInto(out *DistributedTracingConfig) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DistributedTracingConfig.
+func (in *DistributedTracingConfig) DeepCopy() *DistributedTracingConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(DistributedTracingConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TroubleshootingPanelConfig) DeepCopyInto(out *TroubleshootingPanelConfig) {
+ *out = *in
+ out.Korrel8r = in.Korrel8r
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TroubleshootingPanelConfig.
+func (in *TroubleshootingPanelConfig) DeepCopy() *TroubleshootingPanelConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(TroubleshootingPanelConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TroubleshootingPanelKorrel8rConfig) DeepCopyInto(out *TroubleshootingPanelKorrel8rConfig) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TroubleshootingPanelKorrel8rConfig.
+func (in *TroubleshootingPanelKorrel8rConfig) DeepCopy() *TroubleshootingPanelKorrel8rConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(TroubleshootingPanelKorrel8rConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UIPlugin) DeepCopyInto(out *UIPlugin) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
- out.Spec = in.Spec
+ in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
@@ -102,6 +148,16 @@ func (in *UIPluginList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UIPluginSpec) DeepCopyInto(out *UIPluginSpec) {
*out = *in
+ if in.TroubleshootingPanel != nil {
+ in, out := &in.TroubleshootingPanel, &out.TroubleshootingPanel
+ *out = new(TroubleshootingPanelConfig)
+ **out = **in
+ }
+ if in.DistributedTracing != nil {
+ in, out := &in.DistributedTracing, &out.DistributedTracing
+ *out = new(DistributedTracingConfig)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UIPluginSpec.
diff --git a/pkg/controllers/uiplugin/compatibility_matrix.go b/pkg/controllers/uiplugin/compatibility_matrix.go
index b4cf96f0..169b532c 100644
--- a/pkg/controllers/uiplugin/compatibility_matrix.go
+++ b/pkg/controllers/uiplugin/compatibility_matrix.go
@@ -25,6 +25,22 @@ var compatibilityMatrix = []CompatibilityEntry{
ImageKey: "ui-dashboards",
Features: []string{},
},
+ {
+ PluginType: uiv1alpha1.TypeTroubleshootingPanel,
+ // This plugin requires changes made in the monitoring-plugin for OpenShift 4.16
+ // to render the "Troubleshooting Panel" button on the alert details page.
+ MinClusterVersion: "v4.16",
+ MaxClusterVersion: "",
+ ImageKey: "ui-troubleshooting-panel",
+ Features: []string{},
+ },
+ {
+ PluginType: uiv1alpha1.TypeDistributedTracing,
+ MinClusterVersion: "v4.11",
+ MaxClusterVersion: "",
+ ImageKey: "ui-distributed-tracing",
+ Features: []string{},
+ },
}
func getImageKeyForPluginType(pluginType uiv1alpha1.UIPluginType, clusterVersion string) (string, error) {
diff --git a/pkg/controllers/uiplugin/compatibility_matrix_test.go b/pkg/controllers/uiplugin/compatibility_matrix_test.go
index 24548c45..20e90523 100644
--- a/pkg/controllers/uiplugin/compatibility_matrix_test.go
+++ b/pkg/controllers/uiplugin/compatibility_matrix_test.go
@@ -34,6 +34,44 @@ func TestCompatibilityMatrixSpec(t *testing.T) {
expectedKey: "ui-dashboards",
expectedErr: nil,
},
+ {
+ pluginType: uiv1alpha1.TypeTroubleshootingPanel,
+ // This plugin requires changes made in the monitoring-plugin for Openshift 4.16
+ // to render the "Troubleshooting Panel" button on the alert details page.
+ clusterVersion: "4.15",
+ expectedKey: "",
+ expectedErr: fmt.Errorf("no compatible image found for plugin type %s and cluster version %s", uiv1alpha1.TypeTroubleshootingPanel, "4.15"),
+ },
+ {
+ pluginType: uiv1alpha1.TypeTroubleshootingPanel,
+ clusterVersion: "4.16",
+ expectedKey: "ui-troubleshooting-panel",
+ expectedErr: nil,
+ },
+ {
+ pluginType: uiv1alpha1.TypeTroubleshootingPanel,
+ clusterVersion: "4.24.0-0.nightly-2024-03-11-200348",
+ expectedKey: "ui-troubleshooting-panel",
+ expectedErr: nil,
+ },
+ {
+ pluginType: uiv1alpha1.TypeDistributedTracing,
+ clusterVersion: "4.10",
+ expectedKey: "",
+ expectedErr: fmt.Errorf("dynamic plugins not supported before 4.11"),
+ },
+ {
+ pluginType: uiv1alpha1.TypeDistributedTracing,
+ clusterVersion: "4.11",
+ expectedKey: "ui-troubleshooting-panel",
+ expectedErr: nil,
+ },
+ {
+ pluginType: uiv1alpha1.TypeDistributedTracing,
+ clusterVersion: "4.24.0-0.nightly-2024-03-11-200348",
+ expectedKey: "ui-distributed-tracing",
+ expectedErr: nil,
+ },
{
pluginType: "non-existent-plugin",
clusterVersion: "4.24.0-0.nightly-2024-03-11-200348",
diff --git a/pkg/controllers/uiplugin/components.go b/pkg/controllers/uiplugin/components.go
index fde955d3..7f0f0894 100644
--- a/pkg/controllers/uiplugin/components.go
+++ b/pkg/controllers/uiplugin/components.go
@@ -39,6 +39,22 @@ func pluginComponentReconcilers(plugin *uiv1alpha1.UIPlugin, pluginInfo UIPlugin
components = append(components, reconciler.NewUpdater(newRoleBinding(pluginInfo), plugin))
}
+ if pluginInfo.ConfigMap != nil {
+ components = append(components, reconciler.NewUpdater(pluginInfo.ConfigMap, plugin))
+ }
+
+ for _, role := range pluginInfo.ClusterRoles {
+ if role != nil {
+ components = append(components, reconciler.NewUpdater(role, plugin))
+ }
+ }
+
+ for _, roleBinding := range pluginInfo.ClusterRoleBindings {
+ if roleBinding != nil {
+ components = append(components, reconciler.NewUpdater(roleBinding, plugin))
+ }
+ }
+
return components
}
@@ -86,6 +102,53 @@ func newConsolePlugin(info UIPluginInfo, namespace string) *osv1alpha1.ConsolePl
}
func newDeployment(info UIPluginInfo, namespace string) *appsv1.Deployment {
+ pluginArgs := []string{
+ fmt.Sprintf("-port=%d", port),
+ "-cert=/var/serving-cert/tls.crt",
+ "-key=/var/serving-cert/tls.key",
+ }
+
+ if len(info.ExtraArgs) > 0 {
+ pluginArgs = append(pluginArgs, info.ExtraArgs...)
+ }
+
+ volumes := []corev1.Volume{
+ {
+ Name: servingCertVolumeName,
+ VolumeSource: corev1.VolumeSource{
+ Secret: &corev1.SecretVolumeSource{
+ SecretName: info.Name,
+ DefaultMode: ptr.To(int32(420)),
+ },
+ },
+ },
+ }
+ volumeMounts := []corev1.VolumeMount{
+ {
+ Name: servingCertVolumeName,
+ ReadOnly: true,
+ MountPath: "/var/serving-cert",
+ },
+ }
+
+ if info.ConfigMap != nil {
+ volumes = append(volumes, corev1.Volume{
+ Name: "plugin-config",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: info.Name,
+ },
+ },
+ },
+ })
+ volumeMounts = append(volumeMounts, corev1.VolumeMount{
+ Name: "plugin-config",
+ ReadOnly: true,
+ MountPath: "/etc/plugin/config",
+ })
+ }
+
plugin := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: appsv1.SchemeGroupVersion.String(),
@@ -129,34 +192,22 @@ func newDeployment(info UIPluginInfo, namespace string) *appsv1.Deployment {
},
},
},
- VolumeMounts: []corev1.VolumeMount{
- {
- Name: servingCertVolumeName,
- ReadOnly: true,
- MountPath: "/var/serving-cert",
- },
- },
- Args: []string{
- fmt.Sprintf("-port=%d", port),
- "-cert=/var/serving-cert/tls.crt",
- "-key=/var/serving-cert/tls.key",
- },
- },
- },
- Volumes: []corev1.Volume{
- {
- Name: servingCertVolumeName,
- VolumeSource: corev1.VolumeSource{
- Secret: &corev1.SecretVolumeSource{
- SecretName: info.Name,
- DefaultMode: ptr.To(int32(420)),
- },
- },
+ VolumeMounts: volumeMounts,
+ Args: pluginArgs,
},
},
+ Volumes: volumes,
NodeSelector: map[string]string{
"kubernetes.io/os": "linux",
},
+ RestartPolicy: "Always",
+ DNSPolicy: "ClusterFirst",
+ SecurityContext: &corev1.PodSecurityContext{
+ RunAsNonRoot: ptr.To(true),
+ SeccompProfile: &corev1.SeccompProfile{
+ Type: corev1.SeccompProfileTypeRuntimeDefault,
+ },
+ },
},
},
ProgressDeadlineSeconds: ptr.To(int32(300)),
diff --git a/pkg/controllers/uiplugin/controller.go b/pkg/controllers/uiplugin/controller.go
index 91cb9a1f..c5c3824a 100644
--- a/pkg/controllers/uiplugin/controller.go
+++ b/pkg/controllers/uiplugin/controller.go
@@ -69,6 +69,9 @@ const (
// RBAC for reading cluster version
// +kubebuilder:rbac:groups=config.openshift.io,resources=clusterversions,verbs=get;list;watch
+// RBAC for distributed tracing
+// +kubebuilder:rbac:groups=tempo.grafana.com,resources=tempostacks,verbs=list
+
func RegisterWithManager(mgr ctrl.Manager, opts Options) error {
logger := ctrl.Log.WithName("observability-ui")
@@ -251,7 +254,8 @@ func (rm resourceManager) registerPluginWithConsole(ctx context.Context, pluginI
OperatorSpec: operatorv1.OperatorSpec{
ManagementState: operatorv1.Managed,
},
- Plugins: clusterPlugins,
+ Plugins: clusterPlugins,
+ Customization: *cluster.Spec.Customization.DeepCopy(),
},
}
diff --git a/pkg/controllers/uiplugin/distributed_tracing.go b/pkg/controllers/uiplugin/distributed_tracing.go
new file mode 100644
index 00000000..8dc47900
--- /dev/null
+++ b/pkg/controllers/uiplugin/distributed_tracing.go
@@ -0,0 +1,120 @@
+package uiplugin
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+
+ "gopkg.in/yaml.v3"
+ corev1 "k8s.io/api/core/v1"
+ rbacv1 "k8s.io/api/rbac/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ uiv1alpha1 "github.com/rhobs/observability-operator/pkg/apis/uiplugin/v1alpha1"
+)
+
+func createDistributedTracingPluginInfo(plugin *uiv1alpha1.UIPlugin, namespace, name, image string, features []string) (*UIPluginInfo, error) {
+ distributedTracingConfig := plugin.Spec.DistributedTracing
+
+ configYaml, err := marshalDistributedTracingPluginConfig(distributedTracingConfig)
+ if err != nil {
+ return nil, fmt.Errorf("error creating plugin configuration file: %w", err)
+ }
+
+ extraArgs := []string{
+ "-plugin-config-path=/etc/plugin/config/config.yaml",
+ }
+
+ if len(features) > 0 {
+ extraArgs = append(extraArgs, fmt.Sprintf("-features=%s", strings.Join(features, ",")))
+ }
+
+ pluginInfo := &UIPluginInfo{
+ Image: image,
+ Name: plugin.Name,
+ ConsoleName: "distributed-tracing-console-plugin",
+ DisplayName: "Distributed Tracing Console Plugin",
+ ResourceNamespace: namespace,
+ ExtraArgs: extraArgs,
+ ConfigMap: &corev1.ConfigMap{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: corev1.SchemeGroupVersion.String(),
+ Kind: "ConfigMap",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ },
+ Data: map[string]string{
+ "config.yaml": configYaml,
+ },
+ },
+ ClusterRoles: []*rbacv1.ClusterRole{
+ {
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: rbacv1.SchemeGroupVersion.String(),
+ Kind: "ClusterRole",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: plugin.Name + "-cr",
+ Namespace: namespace,
+ },
+ Rules: []rbacv1.PolicyRule{
+ {
+ APIGroups: []string{"tempo.grafana.com"},
+ Resources: []string{"tempostacks"},
+ Verbs: []string{"list"},
+ },
+ },
+ },
+ },
+ ClusterRoleBindings: []*rbacv1.ClusterRoleBinding{
+ {
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: rbacv1.SchemeGroupVersion.String(),
+ Kind: "ClusterRoleBinding",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: plugin.Name + "-crb",
+ Namespace: namespace,
+ },
+ Subjects: []rbacv1.Subject{{
+ APIGroup: corev1.SchemeGroupVersion.Group,
+ Kind: "ServiceAccount",
+ Name: plugin.Name + "-sa",
+ Namespace: namespace,
+ }},
+ RoleRef: rbacv1.RoleRef{
+ APIGroup: rbacv1.SchemeGroupVersion.Group,
+ Kind: "ClusterRole",
+ Name: plugin.Name + "-cr",
+ },
+ },
+ },
+ }
+
+ return pluginInfo, nil
+}
+
+func marshalDistributedTracingPluginConfig(cfg *uiv1alpha1.DistributedTracingConfig) (string, error) {
+ if cfg == nil {
+ return "", nil
+ }
+
+ if cfg.Timeout == "" {
+ return "", nil
+ }
+
+ pluginCfg := struct {
+ Timeout string `yaml:"timeout"`
+ }{
+ Timeout: cfg.Timeout,
+ }
+
+ buf := &bytes.Buffer{}
+ if err := yaml.NewEncoder(buf).Encode(pluginCfg); err != nil {
+ return "", err
+ }
+
+ return buf.String(), nil
+}
diff --git a/pkg/controllers/uiplugin/plugin_info_builder.go b/pkg/controllers/uiplugin/plugin_info_builder.go
index 470c76d9..70f8d06d 100644
--- a/pkg/controllers/uiplugin/plugin_info_builder.go
+++ b/pkg/controllers/uiplugin/plugin_info_builder.go
@@ -12,14 +12,18 @@ import (
)
type UIPluginInfo struct {
- Image string
- Name string
- ConsoleName string
- DisplayName string
- Proxies []osv1alpha1.ConsolePluginProxy
- Role *rbacv1.Role
- RoleBinding *rbacv1.RoleBinding
- ResourceNamespace string
+ Image string
+ Name string
+ ConsoleName string
+ DisplayName string
+ ExtraArgs []string
+ Proxies []osv1alpha1.ConsolePluginProxy
+ Role *rbacv1.Role
+ RoleBinding *rbacv1.RoleBinding
+ ClusterRoles []*rbacv1.ClusterRole
+ ClusterRoleBindings []*rbacv1.ClusterRoleBinding
+ ConfigMap *corev1.ConfigMap
+ ResourceNamespace string
}
func PluginInfoBuilder(plugin *uiv1alpha1.UIPlugin, pluginConf UIPluginsConfiguration, clusterVersion string) (*UIPluginInfo, error) {
@@ -33,12 +37,11 @@ func PluginInfoBuilder(plugin *uiv1alpha1.UIPlugin, pluginConf UIPluginsConfigur
return nil, fmt.Errorf("no image provided for plugin type %s with key %s", plugin.Spec.Type, imageKey)
}
- name := "observability-ui-" + plugin.Name
namespace := pluginConf.ResourcesNamespace
-
switch plugin.Spec.Type {
case uiv1alpha1.TypeDashboards:
{
+ name := "observability-ui-" + plugin.Name
readerRoleName := plugin.Name + "-datasource-reader"
datasourcesNamespace := "openshift-config-managed"
@@ -104,6 +107,14 @@ func PluginInfoBuilder(plugin *uiv1alpha1.UIPlugin, pluginConf UIPluginsConfigur
return pluginInfo, nil
}
+ case uiv1alpha1.TypeTroubleshootingPanel:
+ {
+ return createTroubleshootingPanelPluginInfo(plugin, namespace, plugin.Name, image, []string{})
+ }
+ case uiv1alpha1.TypeDistributedTracing:
+ {
+ return createDistributedTracingPluginInfo(plugin, namespace, plugin.Name, image, []string{})
+ }
}
return nil, fmt.Errorf("plugin type not supported: %s", plugin.Spec.Type)
diff --git a/pkg/controllers/uiplugin/troubleshooting_panel.go b/pkg/controllers/uiplugin/troubleshooting_panel.go
new file mode 100644
index 00000000..5a27e550
--- /dev/null
+++ b/pkg/controllers/uiplugin/troubleshooting_panel.go
@@ -0,0 +1,101 @@
+package uiplugin
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+
+ osv1alpha1 "github.com/openshift/api/console/v1alpha1"
+ "gopkg.in/yaml.v3"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ uiv1alpha1 "github.com/rhobs/observability-operator/pkg/apis/uiplugin/v1alpha1"
+)
+
+func createTroubleshootingPanelPluginInfo(plugin *uiv1alpha1.UIPlugin, namespace, name, image string, features []string) (*UIPluginInfo, error) {
+ troubleshootingPanelConfig := plugin.Spec.TroubleshootingPanel
+
+ configYaml, err := marshalTroubleshootingPanelPluginConfig(troubleshootingPanelConfig)
+ if err != nil {
+ return nil, fmt.Errorf("error creating plugin configuration file: %w", err)
+ }
+
+ extraArgs := []string{
+ "-plugin-config-path=/etc/plugin/config/config.yaml",
+ }
+
+ if len(features) > 0 {
+ extraArgs = append(extraArgs, fmt.Sprintf("-features=%s", strings.Join(features, ",")))
+ }
+
+ proxyName, proxyNamespace := "korrel8r", "korrel8r"
+
+ if plugin.Spec.TroubleshootingPanel != nil {
+ if plugin.Spec.TroubleshootingPanel.Korrel8r.Name != "" {
+ proxyName = plugin.Spec.TroubleshootingPanel.Korrel8r.Name
+ }
+ if plugin.Spec.TroubleshootingPanel.Korrel8r.Namespace != "" {
+ proxyNamespace = plugin.Spec.TroubleshootingPanel.Korrel8r.Namespace
+ }
+ }
+
+ pluginInfo := &UIPluginInfo{
+ Image: image,
+ Name: plugin.Name,
+ ConsoleName: "troubleshooting-panel-console-plugin",
+ DisplayName: "Troubleshooting Panel Console Plugin",
+ ResourceNamespace: namespace,
+ ExtraArgs: extraArgs,
+ Proxies: []osv1alpha1.ConsolePluginProxy{
+ {
+ Type: osv1alpha1.ProxyTypeService,
+ Alias: "korrel8r",
+ Authorize: false,
+ Service: osv1alpha1.ConsolePluginProxyServiceConfig{
+ Name: proxyName,
+ Namespace: proxyNamespace,
+ Port: 8443,
+ },
+ },
+ },
+ ConfigMap: &corev1.ConfigMap{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: corev1.SchemeGroupVersion.String(),
+ Kind: "ConfigMap",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ },
+ Data: map[string]string{
+ "config.yaml": configYaml,
+ },
+ },
+ }
+
+ return pluginInfo, nil
+}
+
+func marshalTroubleshootingPanelPluginConfig(cfg *uiv1alpha1.TroubleshootingPanelConfig) (string, error) {
+ if cfg == nil {
+ return "", nil
+ }
+
+ if cfg.Timeout == "" {
+ return "", nil
+ }
+
+ pluginCfg := struct {
+ Timeout string `yaml:"timeout"`
+ }{
+ Timeout: cfg.Timeout,
+ }
+
+ buf := &bytes.Buffer{}
+ if err := yaml.NewEncoder(buf).Encode(pluginCfg); err != nil {
+ return "", err
+ }
+
+ return buf.String(), nil
+}
diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go
index 093ed025..5b2e5151 100644
--- a/pkg/operator/operator.go
+++ b/pkg/operator/operator.go
@@ -131,6 +131,9 @@ func New(cfg *OperatorConfiguration) (*Operator, error) {
if err := uictrl.RegisterWithManager(mgr, uictrl.Options{PluginsConf: cfg.UIPlugins}); err != nil {
return nil, fmt.Errorf("unable to register observability-ui-plugin controller: %w", err)
}
+ } else {
+ setupLog := ctrl.Log.WithName("setup")
+ setupLog.Info("OpenShift feature gate is disabled, UIPlugins are not enabled")
}
if err := mgr.AddHealthzCheck("health probe", healthz.Ping); err != nil {