Skip to content
Open
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
8 changes: 8 additions & 0 deletions deploy/operator/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ rules:
- get
- patch
- update
- apiGroups:
- config.openshift.io
resources:
- dnses
verbs:
- get
- list
- watch
- apiGroups:
- coordination.k8s.io
resources:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ limitations under the License.
package endpoints

import (
"context"
"fmt"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)

Expand Down Expand Up @@ -48,3 +54,48 @@ func discoverAPIResource(config *rest.Config, groupVersion, kind string) bool {

return false
}

// DiscoverBaseDomain attempts to auto-detect the baseDomain from OpenShift DNS cluster config
// It returns the detected baseDomain in the format "namespace.apps.baseDomain" for
// OpenShift clusters, or an error if it cannot be determined.
func DiscoverBaseDomain(ctx context.Context, c client.Client, namespace string) (string, error) {
logger := log.FromContext(ctx)

// Try to fetch the OpenShift DNS cluster configuration
dns := &unstructured.Unstructured{}
dns.SetGroupVersionKind(schema.GroupVersionKind{
Group: "config.openshift.io",
Version: "v1",
Kind: "DNS",
})

err := c.Get(ctx, client.ObjectKey{Name: "cluster"}, dns)
if err != nil {
logger.Error(err, "Failed to get OpenShift DNS cluster config - baseDomain cannot be auto-detected")
return "", fmt.Errorf("failed to auto-detect baseDomain from OpenShift DNS cluster config: %w", err)
}

// Extract spec.baseDomain from the DNS object
spec, found, err := unstructured.NestedMap(dns.Object, "spec")
if err != nil || !found {
logger.Error(err, "Failed to get spec from OpenShift DNS cluster config")
return "", fmt.Errorf("failed to get spec from OpenShift DNS cluster config: spec not found")
}

openShiftBaseDomain, found, err := unstructured.NestedString(spec, "baseDomain")
if err != nil || !found || openShiftBaseDomain == "" {
logger.Error(err, "Failed to get baseDomain from OpenShift DNS cluster config")
return "", fmt.Errorf("failed to get baseDomain from OpenShift DNS cluster config: baseDomain not found or empty")
}

// Format the baseDomain as "namespace.apps.openShiftBaseDomain"
// This matches the Helm template behavior when .noNs is false
detectedBaseDomain := fmt.Sprintf("%s.apps.%s", namespace, openShiftBaseDomain)

Choose a reason for hiding this comment

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

I think using apps.<baseDomain> works for most OpenShift clusters, but since the domain can be customized by admins, it might be safer to read it from the cluster’s ingress configuration instead of using hardcoded value, wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, that would make sense 👍 I need to check where that ingress configuration lives :)


logger.Info("Auto-detected baseDomain from OpenShift DNS cluster config",
"openShiftBaseDomain", openShiftBaseDomain,
"detectedBaseDomain", detectedBaseDomain,
"namespace", namespace)

return detectedBaseDomain, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package endpoints

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)

var _ = Describe("DiscoverBaseDomain", func() {
// Note: These tests require OpenShift CRDs to be available in the test environment.
// They will be skipped if the CRDs are not present, which is expected in non-OpenShift environments.

Context("when OpenShift is available", func() {
BeforeEach(func() {
// Check if OpenShift CRDs are available
dns := &unstructured.Unstructured{}
dns.SetGroupVersionKind(schema.GroupVersionKind{
Group: "config.openshift.io",
Version: "v1",
Kind: "DNS",
})
dns.SetName("cluster")
dns.Object["spec"] = map[string]interface{}{
"baseDomain": "test-check.com",
}

// Try to create a test DNS object to check if the CRD is available
err := k8sClient.Create(ctx, dns)
if err != nil {
Skip("Skipping OpenShift baseDomain auto-detection tests: OpenShift CRDs not available in test environment")
}
// Clean up test object
_ = k8sClient.Delete(ctx, dns)
})

Context("when OpenShift DNS cluster config exists", func() {
It("should successfully auto-detect baseDomain", func() {
// Create a mock OpenShift DNS cluster config
dns := &unstructured.Unstructured{}
dns.SetGroupVersionKind(schema.GroupVersionKind{
Group: "config.openshift.io",
Version: "v1",
Kind: "DNS",
})
dns.SetName("cluster")
dns.Object["spec"] = map[string]interface{}{
"baseDomain": "example.com",
}

// Create the DNS object in the cluster
err := k8sClient.Create(ctx, dns)
Expect(err).NotTo(HaveOccurred())

// Test auto-detection
detectedBaseDomain, err := DiscoverBaseDomain(ctx, k8sClient, "test-namespace")
Expect(err).NotTo(HaveOccurred())
Expect(detectedBaseDomain).To(Equal("test-namespace.apps.example.com"))

// Cleanup
err = k8sClient.Delete(ctx, dns)
Expect(err).NotTo(HaveOccurred())
})
})

Context("when OpenShift DNS cluster config has empty baseDomain", func() {
It("should return an error", func() {
// Create a mock OpenShift DNS cluster config with empty baseDomain
dns := &unstructured.Unstructured{}
dns.SetGroupVersionKind(schema.GroupVersionKind{
Group: "config.openshift.io",
Version: "v1",
Kind: "DNS",
})
dns.SetName("cluster")
dns.Object["spec"] = map[string]interface{}{
"baseDomain": "",
}

// Create the DNS object in the cluster
err := k8sClient.Create(ctx, dns)
Expect(err).NotTo(HaveOccurred())

// Test auto-detection with empty baseDomain
_, err = DiscoverBaseDomain(ctx, k8sClient, "test-namespace")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("baseDomain not found or empty"))

// Cleanup
err = k8sClient.Delete(ctx, dns)
Expect(err).NotTo(HaveOccurred())
})
})
})

Context("when OpenShift DNS cluster config does not exist", func() {
It("should return an error", func() {
// Try to auto-detect when no DNS config exists
// This test will work even without OpenShift CRDs because it just checks error handling
_, err := DiscoverBaseDomain(ctx, k8sClient, "test-namespace")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("failed to auto-detect baseDomain"))
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ type JumpstarterReconciler struct {
// +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=route.openshift.io,resources=routes/status,verbs=get;update;patch

// OpenShift config resources (for baseDomain auto-detection)
// +kubebuilder:rbac:groups=config.openshift.io,resources=dnses,verbs=get;list;watch

// Monitoring resources
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete

Expand Down Expand Up @@ -127,6 +130,18 @@ func (r *JumpstarterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, nil
}

// Auto-detect baseDomain if not provided
if jumpstarter.Spec.BaseDomain == "" {
log.Info("BaseDomain not provided, attempting auto-detection from OpenShift DNS cluster config")
detectedBaseDomain, err := endpoints.DiscoverBaseDomain(ctx, r.Client, jumpstarter.Namespace)
if err != nil {
log.Error(err, "Failed to auto-detect baseDomain - baseDomain is required but was not provided and could not be auto-detected")
return ctrl.Result{}, fmt.Errorf("baseDomain is required but was not provided and could not be auto-detected: %w", err)
}
jumpstarter.Spec.BaseDomain = detectedBaseDomain
log.Info("Successfully auto-detected baseDomain", "baseDomain", detectedBaseDomain)
}

// Reconcile RBAC resources first
if err := r.reconcileRBAC(ctx, &jumpstarter); err != nil {
log.Error(err, "Failed to reconcile RBAC")
Expand Down