Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
21d23c8
feat(controller): emit event when Service reconciliation fails (#192)
ibm-adarsh Jun 2, 2026
e88f903
build(deps): bump actions/checkout from 6.0.2 to 6.0.3 (#221)
dependabot[bot] Jun 3, 2026
b7521b0
build(deps): bump golang from 1.26.3 to 1.26.4 (#220)
dependabot[bot] Jun 3, 2026
13d8021
merge upstream/main into main
Jun 3, 2026
05855f4
Prep of downstreaming
matzew Mar 20, 2026
b7ea51a
Adding ocp owners
matzew Apr 8, 2026
20efb4f
Adding deps
matzew Apr 13, 2026
372f8e1
adding ignore for vendored files
matzew Apr 13, 2026
27c8e19
Add downstream Dockerfile
Kaustubh-pande Apr 14, 2026
823a8e0
Fix build by including vendor and Makefiles in dockerignore
Kaustubh-pande Apr 17, 2026
87f3050
Red Hat Konflux kflux-prd-rh02 update mcp-lifecycle-operator-main
Apr 17, 2026
efcac02
Enable multi-arch and hermetic builds; add Snyk report
Kaustubh-pande Apr 17, 2026
b7e4fa1
use always use :latest for go builder image
Kaustubh-pande Apr 17, 2026
d0b8927
add renovate config file to auto update konflux deps
Kaustubh-pande Apr 22, 2026
a628854
Update Konflux references main
red-hat-konflux-kflux-prd-rh02[bot] Apr 25, 2026
9450ab3
Update Konflux references main
red-hat-konflux-kflux-prd-rh02[bot] May 2, 2026
bd95a50
Update Konflux references main
red-hat-konflux-kflux-prd-rh02[bot] May 9, 2026
804f5d8
Update Konflux references main
red-hat-konflux-kflux-prd-rh02[bot] May 16, 2026
7eb5458
Fixing .ci-operator.yaml with proper image
matzew May 28, 2026
a81391a
chore: ignore sync PRs in coderabbit
Cali0707 May 27, 2026
cfe4733
chore(deps): update konflux references main
red-hat-konflux-kflux-prd-rh02[bot] May 30, 2026
a78d934
chore(build): Go 1.26.3 bump - since we have matching UBI now
matzew Jun 2, 2026
bb87e6e
chore: add component metadata for ODH/RHOAI integration
matzew Jun 3, 2026
8f6177b
UPSTREAM: <drop>: Updating and vendoring go modules after an upstream…
Jun 3, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/govulncheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Clone the code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# https://github.com/kubernetes-sigs/kubebuilder/blob/v4.11.1/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go

# Build the manager binary
FROM golang:1.26.3 AS builder
FROM golang:1.26.4 AS builder
ARG TARGETOS
ARG TARGETARCH

Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.ci
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG BUILDER_IMAGE=registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-5.0
ARG BASE_IMAGE=registry.ci.openshift.org/ocp/5.0:base-rhel9
ARG BUILDER_IMAGE=registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.23

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.

we must have 1.26

ARG BASE_IMAGE=registry.ci.openshift.org/ocp/4.23:base-rhel9

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.

and I would like to keep the 5.0 bits?


FROM ${BUILDER_IMAGE} AS builder
ARG TARGETOS
Expand Down
14 changes: 14 additions & 0 deletions internal/controller/mcpserver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ const (
eventActionMCPHandshakeRetriesExhausted = "MCPHandshakeRetriesExhausted"
// eventActionDeploymentReconcileFailed is the reporting action when Deployment reconciliation fails.
eventActionDeploymentReconcileFailed = "DeploymentReconcileFailed"
// eventActionServiceReconcileFailed is the reporting action when Service reconciliation fails.
eventActionServiceReconcileFailed = "ServiceReconcileFailed"

// requeueDelayMCPHandshake is the initial delay before requeuing when an MCP handshake fails.
requeueDelayMCPHandshake = 10 * time.Second
Expand Down Expand Up @@ -295,6 +297,10 @@ func (r *MCPServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
recordCondition(mcpServer.Name, mcpServer.Namespace,
readyCondition.Type, string(readyCondition.Status), readyCondition.Reason)

if !duplicateServiceUnavailable(mcpServer.Status.Conditions, readyCondition.Message) {
r.emitServiceReconcileFailed(mcpServer, readyCondition.Message)
}

status := acv1alpha1.MCPServerStatus().
WithObservedGeneration(mcpServer.Generation).
WithDeploymentName(existingDeployment.Name).
Expand Down Expand Up @@ -518,6 +524,14 @@ func (r *MCPServerReconciler) emitDeploymentReconcileFailed(mcpServer *mcpv1alph
"MCPServer %s: %s", mcpServer.Name, message)
}

func (r *MCPServerReconciler) emitServiceReconcileFailed(mcpServer *mcpv1alpha1.MCPServer, message string) {
if r.Recorder == nil {
return
}
r.Recorder.Eventf(mcpServer, nil, corev1.EventTypeWarning, ReasonServiceUnavailable, eventActionServiceReconcileFailed,
"MCPServer %s: %s", mcpServer.Name, message)
}

func (r *MCPServerReconciler) emitMCPHandshakeFailed(mcpServer *mcpv1alpha1.MCPServer, message string) {
if r.Recorder == nil {
return
Expand Down
6 changes: 6 additions & 0 deletions internal/controller/mcpserver_controller_conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,9 @@ func duplicateDeploymentUnavailable(conditions []metav1.Condition, message strin
return prevReady != nil && prevReady.Status == metav1.ConditionFalse &&
prevReady.Reason == ReasonDeploymentUnavailable && prevReady.Message == message
}

func duplicateServiceUnavailable(conditions []metav1.Condition, message string) bool {
prevReady := meta.FindStatusCondition(conditions, ConditionTypeReady)
return prevReady != nil && prevReady.Status == metav1.ConditionFalse &&
prevReady.Reason == ReasonServiceUnavailable && prevReady.Message == message
}
14 changes: 14 additions & 0 deletions internal/controller/mcpserver_controller_conditions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,18 @@ var _ = Describe("status condition helpers", func() {
{Type: ConditionTypeReady, Status: metav1.ConditionFalse, Reason: ReasonDeploymentUnavailable, Message: msg},
}, msg)).To(BeTrue())
})

It("duplicateServiceUnavailable returns true only for matching Ready=False ServiceUnavailable message", func() {
msg := "Failed to reconcile Service: simulated failure"
Expect(duplicateServiceUnavailable(nil, msg)).To(BeFalse())
Expect(duplicateServiceUnavailable([]metav1.Condition{
{Type: ConditionTypeReady, Status: metav1.ConditionFalse, Reason: ReasonDeploymentUnavailable, Message: msg},
}, msg)).To(BeFalse())
Expect(duplicateServiceUnavailable([]metav1.Condition{
{Type: ConditionTypeReady, Status: metav1.ConditionFalse, Reason: ReasonServiceUnavailable, Message: "other"},
}, msg)).To(BeFalse())
Expect(duplicateServiceUnavailable([]metav1.Condition{
{Type: ConditionTypeReady, Status: metav1.ConditionFalse, Reason: ReasonServiceUnavailable, Message: msg},
}, msg)).To(BeTrue())
})
})
96 changes: 96 additions & 0 deletions internal/controller/mcpserver_controller_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package controller
import (
"context"
"fmt"
"strings"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -581,6 +583,100 @@ var _ = Describe("MCPServer Controller - Service Reconciliation Failures", func(
})
})

var _ = Describe("MCPServer Controller - Service Reconcile Events", func() {
const resourceName = "test-service-events"

ctx := context.Background()

typeNamespacedName := types.NamespacedName{
Name: resourceName,
Namespace: "default",
}

BeforeEach(func() {
resource := newTestMCPServer(resourceName)
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
})

AfterEach(func() {
resource := &mcpv1alpha1.MCPServer{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
if err == nil {
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
}
})

It("should emit a Warning ServiceReconcileFailed event only when service error message changes", func() {
failMsg := "simulated service creation failure"
reconciler, fr := newReconcilerForTestWithFakeEvents(k8sClient, k8sClient.Scheme())

wrappedClient, err := client.NewWithWatch(cfg, client.Options{Scheme: k8sClient.Scheme()})
Expect(err).NotTo(HaveOccurred())

interceptedClient := interceptor.NewClient(wrappedClient, interceptor.Funcs{
Create: func(ctx context.Context, c client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
if _, ok := obj.(*corev1.Service); ok {
return fmt.Errorf("%s", failMsg)
}
return c.Create(ctx, obj, opts...)
},
})
reconciler.Client = interceptedClient

By("First service reconcile failure — Warning event emitted once")
_, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
Expect(err).To(HaveOccurred())

var serviceFailedEvent string
Eventually(func(g Gomega) {
for _, ev := range drainEvents(fr.Events) {
if strings.Contains(ev, corev1.EventTypeWarning) && strings.Contains(ev, ReasonServiceUnavailable) {
serviceFailedEvent = ev
break
}
}
g.Expect(serviceFailedEvent).NotTo(BeEmpty())
g.Expect(serviceFailedEvent).To(ContainSubstring(resourceName))
g.Expect(serviceFailedEvent).To(ContainSubstring("Failed to reconcile Service"))
g.Expect(serviceFailedEvent).To(ContainSubstring(failMsg))
}).Should(Succeed())

By("Second reconcile with same error — no duplicate service failed event")
_, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
Expect(err).To(HaveOccurred())
Consistently(fr.Events, 300*time.Millisecond, 20*time.Millisecond).ShouldNot(Receive())

By("Change error message — second Warning event emitted")
failMsg = "simulated service ownership failure"
interceptedClient = interceptor.NewClient(wrappedClient, interceptor.Funcs{
Create: func(ctx context.Context, c client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
if _, ok := obj.(*corev1.Service); ok {
return fmt.Errorf("%s", failMsg)
}
return c.Create(ctx, obj, opts...)
},
})
reconciler.Client = interceptedClient

_, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
Expect(err).To(HaveOccurred())

var secondServiceFailedEvent string
Eventually(func(g Gomega) {
for _, ev := range drainEvents(fr.Events) {
if strings.Contains(ev, corev1.EventTypeWarning) && strings.Contains(ev, ReasonServiceUnavailable) {
secondServiceFailedEvent = ev
break
}
}
g.Expect(secondServiceFailedEvent).NotTo(BeEmpty())
g.Expect(secondServiceFailedEvent).To(ContainSubstring(resourceName))
g.Expect(secondServiceFailedEvent).To(ContainSubstring(failMsg))
g.Expect(secondServiceFailedEvent).NotTo(Equal(serviceFailedEvent))
}).Should(Succeed())
})
})

var _ = Describe("MCPServer Controller - Server-Side Apply for Status", func() {
const resourceName = "test-ssa-status"
const subResourceStatus = "status"
Expand Down