Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions pkg/agenticrun/bindata/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package bindata

import (
"embed"
)

//go:embed assets/*
var f embed.FS

func Asset(name string) ([]byte, error) {
return f.ReadFile(name)
}

func MustAsset(name string) []byte {
data, err := f.ReadFile(name)
if err != nil {
panic(err)
}
return data
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ metadata:
namespace: openshift-cluster-update-console-plugin
annotations:
kubernetes.io/description: Nginx configuration for the cluster-update console plugin.
capability.openshift.io/name: Console
release.openshift.io/feature-set: TechPreviewNoUpgrade
exclude.release.openshift.io/internal-openshift-hosted: "true"
include.release.openshift.io/self-managed-high-availability: "true"
data:
nginx.conf: |
error_log /dev/stderr;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
apiVersion: console.openshift.io/v1
kind: ConsolePlugin
metadata:
name: openshift-cluster-update-console-plugin
name: cluster-update-console-plugin
annotations:
kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates.
capability.openshift.io/name: Console
release.openshift.io/feature-set: TechPreviewNoUpgrade
exclude.release.openshift.io/internal-openshift-hosted: "true"
include.release.openshift.io/self-managed-high-availability: "true"
spec:
displayName: Cluster Updates
i18n:
loadType: Preload
backend:
type: Service
service:
name: openshift-cluster-update-console-plugin
name: cluster-update-console-plugin
namespace: openshift-cluster-update-console-plugin
port: 9001
basePath: /
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ metadata:
namespace: openshift-cluster-update-console-plugin
annotations:
kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates.
capability.openshift.io/name: Console
release.openshift.io/feature-set: TechPreviewNoUpgrade
exclude.release.openshift.io/internal-openshift-hosted: "true"
include.release.openshift.io/self-managed-high-availability: "true"
spec:
selector:
matchLabels:
Expand All @@ -30,7 +26,7 @@ spec:
automountServiceAccountToken: false
containers:
- name: plugin
image: '{{index .Images "cluster-update-console-plugin"}}'
image: ${IMAGE}
imagePullPolicy: IfNotPresent
ports:
- name: https
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ metadata:
name: openshift-cluster-update-console-plugin
annotations:
kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates.
capability.openshift.io/name: Console
release.openshift.io/feature-set: TechPreviewNoUpgrade
exclude.release.openshift.io/internal-openshift-hosted: "true"
include.release.openshift.io/self-managed-high-availability: "true"
workload.openshift.io/allowed: management
labels:
openshift.io/cluster-monitoring: "true"
Expand Down
17 changes: 17 additions & 0 deletions pkg/agenticrun/bindata/assets/networkpolicy-allow-console.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-console-ingress
namespace: openshift-cluster-update-console-plugin
annotations:
kubernetes.io/description: Allow ingress from the console to the cluster-update console plugin on port 9001.
spec:
podSelector:
matchLabels:
app: cluster-update-console-plugin
ingress:
- ports:
- port: 9001
protocol: TCP
Comment on lines +12 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file list ==\n'
git ls-files 'pkg/agenticrun/bindata/assets/*' | sed -n '1,120p'

printf '\n== target file ==\n'
cat -n pkg/agenticrun/bindata/assets/networkpolicy-allow-console.yaml

printf '\n== related networkpolicy assets ==\n'
rg -n --no-heading -A6 -B4 'kind: NetworkPolicy|podSelector:|namespaceSelector:|policyTypes:|ingress:' pkg/agenticrun/bindata/assets -g '*.yaml' -g '*.yml'

Repository: openshift/cluster-version-operator

Length of output: 3749


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== console plugin manifest ==\n'
cat -n pkg/agenticrun/bindata/assets/consoleplugin.yaml

printf '\n== service/deployment excerpts ==\n'
rg -n --no-heading -A8 -B4 'kind: (Service|Deployment)|labels:|selector:|ports:|9001|console' pkg/agenticrun/bindata/assets/{service.yaml,deployment.yaml,consoleplugin.yaml} -g '*.yaml'

Repository: openshift/cluster-version-operator

Length of output: 8737


Restrict ingress to the OpenShift console.
This rule omits from, so any source can reach port 9001 on the plugin pods. Add a namespaceSelector/podSelector for the console’s labels so only the console can fetch the plugin assets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/agenticrun/bindata/assets/networkpolicy-allow-console.yaml` around lines
12 - 15, Update the ingress rule in the network policy to add a from clause
containing namespaceSelector and podSelector selectors matching the OpenShift
console labels, while preserving TCP port 9001. Ensure only console pods can
access the plugin assets.

policyTypes:
- Ingress
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ metadata:
namespace: openshift-cluster-update-console-plugin
annotations:
kubernetes.io/description: This NetworkPolicy is used to deny all ingress and egress traffic by default in this namespace, matching all Pods, and serving as a baseline.
capability.openshift.io/name: Console
release.openshift.io/feature-set: TechPreviewNoUpgrade
exclude.release.openshift.io/internal-openshift-hosted: "true"
include.release.openshift.io/self-managed-high-availability: "true"
spec:
podSelector: {}
policyTypes:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: openshift-cluster-update-console-plugin
name: cluster-update-console-plugin
namespace: openshift-cluster-update-console-plugin
annotations:
kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates.
service.beta.openshift.io/serving-cert-secret-name: cluster-update-console-plugin-cert
capability.openshift.io/name: Console
release.openshift.io/feature-set: TechPreviewNoUpgrade
exclude.release.openshift.io/internal-openshift-hosted: "true"
include.release.openshift.io/self-managed-high-availability: "true"
spec:
type: ClusterIP
selector:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,3 @@ metadata:
namespace: openshift-cluster-update-console-plugin
annotations:
kubernetes.io/description: The OpenShift cluster-update console plugin provides a web-console interface for managing ClusterVersion updates.
capability.openshift.io/name: Console
release.openshift.io/feature-set: TechPreviewNoUpgrade
exclude.release.openshift.io/internal-openshift-hosted: "true"
include.release.openshift.io/self-managed-high-availability: "true"
183 changes: 183 additions & 0 deletions pkg/agenticrun/consoleplugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package agenticrun

import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"

ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"

appsv1 "k8s.io/api/apps/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/klog/v2"

operatorv1 "github.com/openshift/api/operator/v1"

"github.com/openshift/cluster-version-operator/pkg/agenticrun/bindata"
i "github.com/openshift/cluster-version-operator/pkg/internal"
)

var consolePluginAssets = []string{
"assets/namespace.yaml",
"assets/serviceaccount.yaml",
"assets/networkpolicy.yaml",
"assets/networkpolicy-allow-console.yaml",
"assets/configmap.yaml",
"assets/deployment.yaml",
"assets/service.yaml",
"assets/consoleplugin.yaml",
}

func applyConsolePluginManifests(ctx context.Context, client ctrlruntimeclient.Client, image string) error {
for _, asset := range consolePluginAssets {
raw := bindata.MustAsset(asset)

if asset == "assets/deployment.yaml" {
raw = []byte(strings.ReplaceAll(string(raw), "${IMAGE}", image))
}

obj := &unstructured.Unstructured{}
if err := yaml.NewYAMLOrJSONDecoder(strings.NewReader(string(raw)), len(raw)).Decode(obj); err != nil {
return fmt.Errorf("decoding %s: %w", asset, err)
}

existing := &unstructured.Unstructured{}
existing.SetGroupVersionKind(obj.GroupVersionKind())
err := client.Get(ctx, ctrlruntimeclient.ObjectKeyFromObject(obj), existing)
if err != nil {
if ctrlruntimeclient.IgnoreNotFound(err) != nil {
return fmt.Errorf("getting %s %s: %w", obj.GetKind(), obj.GetName(), err)
}
if err := client.Create(ctx, obj); err != nil {
return fmt.Errorf("creating %s %s: %w", obj.GetKind(), obj.GetName(), err)
}
klog.V(i.Normal).Infof("Created console plugin %s %s", obj.GetKind(), obj.GetName())
continue
}

if !needsUpdate(existing, obj) {
klog.V(i.Debug).Infof("Console plugin %s %s is up to date", obj.GetKind(), obj.GetName())
continue
}
obj.SetResourceVersion(existing.GetResourceVersion())
if err := client.Update(ctx, obj); err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it might hot-loop on updates? Without AgenticRun in the CI runs, I guess we don't have Kube API audit logs to know, but I'd have expected us to be using lib/resourceapply client-side existing-vs.-required diff logic to compare the existing we pulled from the cluster with our local-manifest expectations, and to only update when we saw changes we thought needed to be pushed into the cluster.

return fmt.Errorf("updating %s %s: %w", obj.GetKind(), obj.GetName(), err)
}
klog.V(i.Normal).Infof("Updated console plugin %s %s", obj.GetKind(), obj.GetName())
}
return nil
}

func needsUpdate(existing, desired *unstructured.Unstructured) bool {
if !reflect.DeepEqual(existing.Object["spec"], desired.Object["spec"]) {
return true
}
return !reflect.DeepEqual(existing.Object["data"], desired.Object["data"])
}

func cleanupConsolePluginManifests(ctx context.Context, client ctrlruntimeclient.Client) error {
for idx := len(consolePluginAssets) - 1; idx >= 0; idx-- {
raw := bindata.MustAsset(consolePluginAssets[idx])

obj := &unstructured.Unstructured{}
if err := yaml.NewYAMLOrJSONDecoder(strings.NewReader(string(raw)), len(raw)).Decode(obj); err != nil {
return fmt.Errorf("decoding %s: %w", consolePluginAssets[idx], err)
}

existing := &unstructured.Unstructured{}
existing.SetGroupVersionKind(obj.GroupVersionKind())
existing.SetName(obj.GetName())
existing.SetNamespace(obj.GetNamespace())

if err := client.Delete(ctx, existing); err != nil {
if !kerrors.IsNotFound(err) {
return fmt.Errorf("deleting %s %s: %w", obj.GetKind(), obj.GetName(), err)
}
} else {
klog.V(i.Normal).Infof("Deleted console plugin %s %s", obj.GetKind(), obj.GetName())
}
}
return nil
}

const (
consolePluginName = "cluster-update-console-plugin"
consolePluginNamespace = "openshift-cluster-update-console-plugin"
)

func waitForPluginReady(ctx context.Context, client ctrlruntimeclient.Client) error {
deployment := &appsv1.Deployment{}
if err := client.Get(ctx, types.NamespacedName{Name: consolePluginName, Namespace: consolePluginNamespace}, deployment); err != nil {
return fmt.Errorf("getting deployment: %w", err)
}
if deployment.Status.AvailableReplicas < 1 {
return fmt.Errorf("deployment %s has no available replicas", consolePluginName)
}
return nil
}

func enableConsolePlugin(ctx context.Context, client ctrlruntimeclient.Client) error {
console := &operatorv1.Console{}
if err := client.Get(ctx, types.NamespacedName{Name: "cluster"}, console); err != nil {
return fmt.Errorf("getting console operator config: %w", err)
}
for _, p := range console.Spec.Plugins {
if p == consolePluginName {
return nil
}
}
plugins := append(console.Spec.Plugins, consolePluginName)
patch, err := json.Marshal(map[string]interface{}{
"spec": map[string]interface{}{
"plugins": plugins,
},
})
if err != nil {
return fmt.Errorf("marshaling patch: %w", err)
}
if err := client.Patch(ctx, console, ctrlruntimeclient.RawPatch(types.MergePatchType, patch)); err != nil {
return fmt.Errorf("enabling console plugin: %w", err)
}
klog.V(i.Normal).Infof("Enabled %s in console operator config", consolePluginName)
return nil
}

func disableConsolePlugin(ctx context.Context, client ctrlruntimeclient.Client) error {
console := &operatorv1.Console{}
if err := client.Get(ctx, types.NamespacedName{Name: "cluster"}, console); err != nil {
if kerrors.IsNotFound(err) {
return nil
}
return fmt.Errorf("getting console operator config: %w", err)
}
filtered := make([]string, 0, len(console.Spec.Plugins))
found := false
for _, p := range console.Spec.Plugins {
if p == consolePluginName {
found = true
continue
}
filtered = append(filtered, p)
}
if !found {
return nil
}
patch, err := json.Marshal(map[string]interface{}{
"spec": map[string]interface{}{
"plugins": filtered,
},
})
if err != nil {
return fmt.Errorf("marshaling patch: %w", err)
}
if err := client.Patch(ctx, console, ctrlruntimeclient.RawPatch(types.MergePatchType, patch)); err != nil {
return fmt.Errorf("disabling console plugin: %w", err)
}
klog.V(i.Normal).Infof("Disabled %s in console operator config", consolePluginName)
return nil
}
Loading