Skip to content

Commit ccb8e17

Browse files
authored
operator: sensible defaults to allow minimal configuration Jumpstarter resource creation (#202)
1 parent 5f7cfe5 commit ccb8e17

File tree

9 files changed

+126
-30
lines changed

9 files changed

+126
-30
lines changed

deploy/operator/api/v1alpha1/jumpstarter_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,12 @@ type JumpstarterSpec struct {
142142

143143
// Controller configuration for the main Jumpstarter API and gRPC services.
144144
// The controller handles gRPC and REST API requests from clients and exporters.
145+
// +kubebuilder:default={}
145146
Controller ControllerConfig `json:"controller,omitempty"`
146147

147148
// Router configuration for the Jumpstarter router service.
148149
// Routers handle gRPC traffic routing and load balancing.
150+
// +kubebuilder:default={}
149151
Routers RoutersConfig `json:"routers,omitempty"`
150152

151153
// Authentication configuration for client and exporter authentication.
@@ -158,6 +160,7 @@ type JumpstarterSpec struct {
158160
type RoutersConfig struct {
159161
// Container image for the router pods in 'registry/repository/image:tag' format.
160162
// If not specified, defaults to the latest stable version of the Jumpstarter router.
163+
// +kubebuilder:default="quay.io/jumpstarter-dev/jumpstarter-controller:latest"
161164
Image string `json:"image,omitempty"`
162165

163166
// Image pull policy for the router container.
@@ -192,6 +195,7 @@ type RoutersConfig struct {
192195
type ControllerConfig struct {
193196
// Container image for the controller pods in 'registry/repository/image:tag' format.
194197
// If not specified, defaults to the latest stable version of the Jumpstarter controller.
198+
// +kubebuilder:default="quay.io/jumpstarter-dev/jumpstarter-controller:latest"
195199
Image string `json:"image,omitempty"`
196200

197201
// Image pull policy for the controller container.

deploy/operator/bundle/manifests/jumpstarter-operator.clusterserviceversion.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ metadata:
1818
}
1919
]
2020
capabilities: Basic Install
21-
createdAt: "2025-11-25T08:56:27Z"
21+
createdAt: "2025-12-17T15:29:25Z"
2222
operators.operatorframework.io/builder: operator-sdk-v1.41.1
2323
operators.operatorframework.io/project_layout: go.kubebuilder.io/v4
2424
name: jumpstarter-operator.v0.8.0

deploy/operator/bundle/manifests/operator.jumpstarter.dev_jumpstarters.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ spec:
430430
pattern: ^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$
431431
type: string
432432
controller:
433+
default: {}
433434
description: |-
434435
Controller configuration for the main Jumpstarter API and gRPC services.
435436
The controller handles gRPC and REST API requests from clients and exporters.
@@ -1083,6 +1084,7 @@ spec:
10831084
type: object
10841085
type: object
10851086
image:
1087+
default: quay.io/jumpstarter-dev/jumpstarter-controller:latest
10861088
description: |-
10871089
Container image for the controller pods in 'registry/repository/image:tag' format.
10881090
If not specified, defaults to the latest stable version of the Jumpstarter controller.
@@ -1368,6 +1370,7 @@ spec:
13681370
type: object
13691371
type: object
13701372
routers:
1373+
default: {}
13711374
description: |-
13721375
Router configuration for the Jumpstarter router service.
13731376
Routers handle gRPC traffic routing and load balancing.
@@ -1626,6 +1629,7 @@ spec:
16261629
type: object
16271630
type: object
16281631
image:
1632+
default: quay.io/jumpstarter-dev/jumpstarter-controller:latest
16291633
description: |-
16301634
Container image for the router pods in 'registry/repository/image:tag' format.
16311635
If not specified, defaults to the latest stable version of the Jumpstarter router.

deploy/operator/config/crd/bases/operator.jumpstarter.dev_jumpstarters.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ spec:
430430
pattern: ^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$
431431
type: string
432432
controller:
433+
default: {}
433434
description: |-
434435
Controller configuration for the main Jumpstarter API and gRPC services.
435436
The controller handles gRPC and REST API requests from clients and exporters.
@@ -1083,6 +1084,7 @@ spec:
10831084
type: object
10841085
type: object
10851086
image:
1087+
default: quay.io/jumpstarter-dev/jumpstarter-controller:latest
10861088
description: |-
10871089
Container image for the controller pods in 'registry/repository/image:tag' format.
10881090
If not specified, defaults to the latest stable version of the Jumpstarter controller.
@@ -1368,6 +1370,7 @@ spec:
13681370
type: object
13691371
type: object
13701372
routers:
1373+
default: {}
13711374
description: |-
13721375
Router configuration for the Jumpstarter router service.
13731376
Routers handle gRPC traffic routing and load balancing.
@@ -1626,6 +1629,7 @@ spec:
16261629
type: object
16271630
type: object
16281631
image:
1632+
default: quay.io/jumpstarter-dev/jumpstarter-controller:latest
16291633
description: |-
16301634
Container image for the router pods in 'registry/repository/image:tag' format.
16311635
If not specified, defaults to the latest stable version of the Jumpstarter router.

deploy/operator/config/manifests/bases/jumpstarter-operator.clusterserviceversion.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,20 @@ apiVersion: operators.coreos.com/v1alpha1
22
kind: ClusterServiceVersion
33
metadata:
44
annotations:
5-
alm-examples: '[]'
5+
alm-examples: |-
6+
[
7+
{
8+
"apiVersion": "operator.jumpstarter.dev/v1alpha1",
9+
"kind": "Jumpstarter",
10+
"metadata": {
11+
"name": "jumpstarter",
12+
"namespace": "jumpstarter"
13+
},
14+
"spec": {
15+
"baseDomain": "jumpstarter.example.com"
16+
}
17+
}
18+
]
619
capabilities: Basic Install
720
name: jumpstarter-operator.v0.0.0
821
namespace: placeholder

deploy/operator/dist/install.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,7 @@ spec:
833833
pattern: ^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$
834834
type: string
835835
controller:
836+
default: {}
836837
description: |-
837838
Controller configuration for the main Jumpstarter API and gRPC services.
838839
The controller handles gRPC and REST API requests from clients and exporters.
@@ -1486,6 +1487,7 @@ spec:
14861487
type: object
14871488
type: object
14881489
image:
1490+
default: quay.io/jumpstarter-dev/jumpstarter-controller:latest
14891491
description: |-
14901492
Container image for the controller pods in 'registry/repository/image:tag' format.
14911493
If not specified, defaults to the latest stable version of the Jumpstarter controller.
@@ -1771,6 +1773,7 @@ spec:
17711773
type: object
17721774
type: object
17731775
routers:
1776+
default: {}
17741777
description: |-
17751778
Router configuration for the Jumpstarter router service.
17761779
Routers handle gRPC traffic routing and load balancing.
@@ -2029,6 +2032,7 @@ spec:
20292032
type: object
20302033
type: object
20312034
image:
2035+
default: quay.io/jumpstarter-dev/jumpstarter-controller:latest
20322036
description: |-
20332037
Container image for the router pods in 'registry/repository/image:tag' format.
20342038
If not specified, defaults to the latest stable version of the Jumpstarter router.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package endpoints
18+
19+
import (
20+
"fmt"
21+
22+
operatorv1alpha1 "github.com/jumpstarter-dev/jumpstarter-controller/deploy/operator/api/v1alpha1"
23+
)
24+
25+
// ensureEndpointServiceType ensures an endpoint has a service type enabled.
26+
// If no service type is enabled, it auto-selects Route (if available), Ingress (if available),
27+
// or ClusterIP as a fallback.
28+
func ensureEndpointServiceType(endpoint *operatorv1alpha1.Endpoint, routeAvailable, ingressAvailable bool) {
29+
// Skip if any service type is already enabled
30+
if (endpoint.Route != nil && endpoint.Route.Enabled) ||
31+
(endpoint.Ingress != nil && endpoint.Ingress.Enabled) ||
32+
(endpoint.LoadBalancer != nil && endpoint.LoadBalancer.Enabled) ||
33+
(endpoint.NodePort != nil && endpoint.NodePort.Enabled) ||
34+
(endpoint.ClusterIP != nil && endpoint.ClusterIP.Enabled) {
35+
return
36+
}
37+
38+
// Auto-select based on cluster capabilities, fallback to ClusterIP
39+
if routeAvailable {
40+
endpoint.Route = &operatorv1alpha1.RouteConfig{Enabled: true}
41+
} else if ingressAvailable {
42+
endpoint.Ingress = &operatorv1alpha1.IngressConfig{Enabled: true}
43+
} else {
44+
endpoint.ClusterIP = &operatorv1alpha1.ClusterIPConfig{Enabled: true}
45+
}
46+
}
47+
48+
// ApplyEndpointDefaults generates default endpoints for a JumpstarterSpec
49+
// based on the baseDomain and cluster capabilities (Route vs Ingress availability).
50+
// It also ensures all existing endpoints have a service type enabled.
51+
func ApplyEndpointDefaults(spec *operatorv1alpha1.JumpstarterSpec, routeAvailable, ingressAvailable bool) {
52+
// Skip endpoint generation if no baseDomain is set
53+
if spec.BaseDomain == "" {
54+
return
55+
}
56+
57+
// Generate default controller gRPC endpoint if none specified
58+
if len(spec.Controller.GRPC.Endpoints) == 0 {
59+
endpoint := operatorv1alpha1.Endpoint{
60+
Address: fmt.Sprintf("grpc.%s", spec.BaseDomain),
61+
}
62+
ensureEndpointServiceType(&endpoint, routeAvailable, ingressAvailable)
63+
spec.Controller.GRPC.Endpoints = []operatorv1alpha1.Endpoint{endpoint}
64+
} else {
65+
// Ensure existing endpoints have a service type enabled
66+
for i := range spec.Controller.GRPC.Endpoints {
67+
ensureEndpointServiceType(&spec.Controller.GRPC.Endpoints[i], routeAvailable, ingressAvailable)
68+
}
69+
}
70+
71+
// Generate default router gRPC endpoints if none specified
72+
if len(spec.Routers.GRPC.Endpoints) == 0 {
73+
endpoint := operatorv1alpha1.Endpoint{
74+
// Use $(replica) placeholder for per-replica addresses
75+
Address: fmt.Sprintf("router-$(replica).%s", spec.BaseDomain),
76+
}
77+
ensureEndpointServiceType(&endpoint, routeAvailable, ingressAvailable)
78+
spec.Routers.GRPC.Endpoints = []operatorv1alpha1.Endpoint{endpoint}
79+
} else {
80+
// Ensure existing endpoints have a service type enabled
81+
for i := range spec.Routers.GRPC.Endpoints {
82+
ensureEndpointServiceType(&spec.Routers.GRPC.Endpoints[i], routeAvailable, ingressAvailable)
83+
}
84+
}
85+
}

deploy/operator/internal/controller/jumpstarter/endpoints/endpoints.go

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ func NewReconciler(client client.Client, scheme *runtime.Scheme, config *rest.Co
6060
}
6161
}
6262

63+
// ApplyDefaults applies endpoint defaults to a JumpstarterSpec using the
64+
// reconciler's discovered cluster capabilities (Route vs Ingress availability).
65+
func (r *Reconciler) ApplyDefaults(spec *operatorv1alpha1.JumpstarterSpec) {
66+
ApplyEndpointDefaults(spec, r.RouteAvailable, r.IngressAvailable)
67+
}
68+
6369
// createOrUpdateService creates or updates a service with proper handling of immutable fields
6470
// and owner references. This is the unified service creation method.
6571
func (r *Reconciler) createOrUpdateService(ctx context.Context, service *corev1.Service, owner metav1.Object) error {
@@ -195,20 +201,6 @@ func (r *Reconciler) ReconcileControllerEndpoint(ctx context.Context, owner meta
195201
}
196202
}
197203

198-
// If no service type is explicitly enabled, create a default ClusterIP service
199-
if (endpoint.LoadBalancer == nil || !endpoint.LoadBalancer.Enabled) &&
200-
(endpoint.NodePort == nil || !endpoint.NodePort.Enabled) &&
201-
(endpoint.ClusterIP == nil || !endpoint.ClusterIP.Enabled) &&
202-
(endpoint.Ingress == nil || !endpoint.Ingress.Enabled) &&
203-
(endpoint.Route == nil || !endpoint.Route.Enabled) {
204-
205-
// TODO: Default to Route or Ingress depending of the type of cluster
206-
if err := r.createService(ctx, owner, servicePort, "", corev1.ServiceTypeClusterIP,
207-
podSelector, baseLabels, nil, nil); err != nil {
208-
return err
209-
}
210-
}
211-
212204
return nil
213205
}
214206

@@ -287,20 +279,6 @@ func (r *Reconciler) ReconcileRouterReplicaEndpoint(ctx context.Context, owner m
287279
}
288280
}
289281

290-
// If no service type is explicitly enabled, create a default ClusterIP service
291-
if (endpoint.LoadBalancer == nil || !endpoint.LoadBalancer.Enabled) &&
292-
(endpoint.NodePort == nil || !endpoint.NodePort.Enabled) &&
293-
(endpoint.ClusterIP == nil || !endpoint.ClusterIP.Enabled) &&
294-
(endpoint.Ingress == nil || !endpoint.Ingress.Enabled) &&
295-
(endpoint.Route == nil || !endpoint.Route.Enabled) {
296-
if err := r.createService(ctx, owner, servicePort, "", corev1.ServiceTypeClusterIP,
297-
podSelector, baseLabels, nil, nil); err != nil {
298-
return err
299-
}
300-
}
301-
302-
// Note: Ingress resources are now created above. Route resources still need to be implemented.
303-
304282
return nil
305283
}
306284

deploy/operator/internal/controller/jumpstarter/jumpstarter_controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ func (r *JumpstarterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
128128
return ctrl.Result{}, nil
129129
}
130130

131+
// Apply runtime-computed defaults (endpoints based on baseDomain and cluster capabilities)
132+
// Static defaults are handled by kubebuilder annotations in the CRD schema
133+
r.EndpointReconciler.ApplyDefaults(&jumpstarter.Spec)
134+
131135
// Reconcile RBAC resources first
132136
if err := r.reconcileRBAC(ctx, &jumpstarter); err != nil {
133137
log.Error(err, "Failed to reconcile RBAC")

0 commit comments

Comments
 (0)