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. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
timeoutstring + 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. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
korrel8robject + korrel8r defines the Korrel8r instance that the troubleshooting panel plugin will connect to
+
false
timeoutstring + 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 + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
namestring + Name of the korrel8r instance
+
false
namespacestring + 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 {