-
Notifications
You must be signed in to change notification settings - Fork 244
OTA-2084: Conditionally deploy console plugin when AgenticRun CRD is present #1425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
|---|---|---|
| @@ -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 |
|---|---|---|
| @@ -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 | ||
| policyTypes: | ||
| - Ingress | ||
| 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 { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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 | ||
| } | ||
There was a problem hiding this comment.
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:
Repository: openshift/cluster-version-operator
Length of output: 3749
🏁 Script executed:
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 anamespaceSelector/podSelectorfor the console’s labels so only the console can fetch the plugin assets.🤖 Prompt for AI Agents