Skip to content

Commit 4c65e37

Browse files
Yuriy TeodorovychYuriy Teodorovych
authored andcommitted
Merge branch 'main' into yt-add-go-coverage-tests
2 parents a4ee537 + ded9da5 commit 4c65e37

File tree

10 files changed

+676
-54
lines changed

10 files changed

+676
-54
lines changed

.github/hack/install-odh.sh

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
# Prerequisites: cert-manager and LWS operators (run install-cert-manager-and-lws.sh first).
66
#
77
# Environment variables:
8-
# OPERATOR_CATALOG - Custom catalog image (optional). When unset, uses community-operators (ODH 3.3).
8+
# OPERATOR_CATALOG - Custom catalog image (optional). When unset, uses community-operators.
99
# Set to e.g. quay.io/opendatahub/opendatahub-operator-catalog:latest for custom builds.
10-
# OPERATOR_CHANNEL - Subscription channel (default: fast-3 for community, fast for custom catalog)
10+
# OPERATOR_CHANNEL - Subscription channel (default: fast-3)
11+
# OPERATOR_STARTING_CSV - Pin Subscription startingCSV (default: opendatahub-operator.v3.4.0-ea.1). Set to "-" to omit.
12+
# OPERATOR_INSTALL_PLAN_APPROVAL - Manual (default) or Automatic; use "-" to omit.
13+
# Manual: blocks auto-upgrades; this script auto-approves only the first InstallPlan so install does not stall.
1114
# OPERATOR_IMAGE - Custom operator image to patch into CSV (optional)
1215
#
1316
# Usage: ./install-odh.sh
@@ -21,6 +24,8 @@ DATA_DIR="${REPO_ROOT}/scripts/data"
2124
NAMESPACE="${OPERATOR_NAMESPACE:-opendatahub}"
2225
OPERATOR_CATALOG="${OPERATOR_CATALOG:-}"
2326
OPERATOR_CHANNEL="${OPERATOR_CHANNEL:-}"
27+
OPERATOR_STARTING_CSV="${OPERATOR_STARTING_CSV:-}"
28+
OPERATOR_INSTALL_PLAN_APPROVAL="${OPERATOR_INSTALL_PLAN_APPROVAL:-}"
2429
OPERATOR_IMAGE="${OPERATOR_IMAGE:-}"
2530

2631
# Source deployment helpers
@@ -59,28 +64,38 @@ patch_operator_csv_if_needed() {
5964
echo "=== Installing OpenDataHub operator ==="
6065
echo ""
6166

62-
# 1. Catalog setup: use community-operators (ODH 3.3) by default, or custom catalog when OPERATOR_CATALOG is set
67+
# 1. Catalog setup: community-operators by default, or custom catalog when OPERATOR_CATALOG is set
6368
echo "1. Setting up ODH catalog..."
6469
if [[ -n "$OPERATOR_CATALOG" ]]; then
6570
echo " Using custom catalog: $OPERATOR_CATALOG"
6671
create_custom_catalogsource "odh-custom-catalog" "openshift-marketplace" "$OPERATOR_CATALOG"
6772
catalog_source="odh-custom-catalog"
68-
channel="${OPERATOR_CHANNEL:-fast}"
73+
channel="${OPERATOR_CHANNEL:-fast-3}"
6974
else
70-
echo " Using community-operators (ODH 3.3)"
75+
echo " Using community-operators"
7176
catalog_source="community-operators"
7277
channel="${OPERATOR_CHANNEL:-fast-3}"
7378
fi
7479

80+
# Pin to ODH 3.4 EA1 unless overridden (omit with OPERATOR_STARTING_CSV=- to follow channel head)
81+
starting_csv="${OPERATOR_STARTING_CSV:-opendatahub-operator.v3.4.0-ea.1}"
82+
[[ "$starting_csv" == "-" ]] && starting_csv=""
83+
84+
# Manual = no auto-upgrades; install_olm_operator still approves the first InstallPlan programmatically
85+
plan_approval="${OPERATOR_INSTALL_PLAN_APPROVAL:-Manual}"
86+
[[ "$plan_approval" == "-" ]] && plan_approval=""
87+
7588
# 2. Install ODH operator via OLM
7689
echo "2. Installing ODH operator..."
7790
install_olm_operator \
7891
"opendatahub-operator" \
7992
"$NAMESPACE" \
8093
"$catalog_source" \
8194
"$channel" \
82-
"" \
83-
"AllNamespaces"
95+
"$starting_csv" \
96+
"AllNamespaces" \
97+
"openshift-marketplace" \
98+
"$plan_approval"
8499

85100
# 3. Patch CSV with custom image if specified
86101
if [[ -n "$OPERATOR_IMAGE" ]]; then

deployment/base/maas-controller/crd/bases/maas.opendatahub.io_maasmodelrefs.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ spec:
5555
spec:
5656
description: MaaSModelSpec defines the desired state of MaaSModelRef
5757
properties:
58+
credentialRef:
59+
description: |-
60+
CredentialRef references a Kubernetes Secret containing the provider API key.
61+
The Secret must contain a data key "api-key" with the credential value.
62+
Only used when modelRef.kind=ExternalModel.
63+
properties:
64+
name:
65+
description: Name is the name of the Secret
66+
maxLength: 253
67+
minLength: 1
68+
type: string
69+
namespace:
70+
description: Namespace is the namespace of the Secret. Defaults
71+
to the MaaSModelRef namespace if omitted.
72+
maxLength: 253
73+
type: string
74+
required:
75+
- name
76+
type: object
5877
endpointOverride:
5978
description: |-
6079
EndpointOverride, when set, overrides the endpoint URL that the controller
@@ -64,6 +83,16 @@ spec:
6483
modelRef:
6584
description: ModelRef references the actual model endpoint
6685
properties:
86+
endpoint:
87+
description: |-
88+
Endpoint is the FQDN of the external provider (no scheme or path).
89+
e.g. "api.openai.com". Only used when kind=ExternalModel.
90+
This field is metadata for downstream consumers (e.g. BBR provider-resolver plugin)
91+
and is not used by the controller for endpoint derivation. Use spec.endpointOverride
92+
to override the controller-derived endpoint.
93+
maxLength: 253
94+
pattern: ^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)*$
95+
type: string
6796
kind:
6897
description: Kind determines which fields are available
6998
enum:
@@ -73,10 +102,20 @@ spec:
73102
name:
74103
description: Name is the name of the model resource
75104
type: string
105+
provider:
106+
description: |-
107+
Provider identifies the API format and auth type for external models.
108+
e.g. "openai", "anthropic". Only used when kind=ExternalModel.
109+
maxLength: 63
110+
type: string
76111
required:
77112
- kind
78113
- name
79114
type: object
115+
x-kubernetes-validations:
116+
- message: provider is required when kind is ExternalModel
117+
rule: self.kind != 'ExternalModel' || has(self.provider) && self.provider
118+
!= ''
80119
required:
81120
- modelRef
82121
type: object

maas-controller/api/maas/v1alpha1/maasmodelref_types.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,50 @@ type MaaSModelSpec struct {
2929
// or Gateway/HTTPRoute).
3030
// +optional
3131
EndpointOverride string `json:"endpointOverride,omitempty"`
32+
// CredentialRef references a Kubernetes Secret containing the provider API key.
33+
// The Secret must contain a data key "api-key" with the credential value.
34+
// Only used when modelRef.kind=ExternalModel.
35+
// +optional
36+
CredentialRef *CredentialReference `json:"credentialRef,omitempty"`
37+
}
38+
39+
// CredentialReference references a Kubernetes Secret with provider API credentials.
40+
type CredentialReference struct {
41+
// Name is the name of the Secret
42+
// +kubebuilder:validation:MinLength=1
43+
// +kubebuilder:validation:MaxLength=253
44+
Name string `json:"name"`
45+
// Namespace is the namespace of the Secret. Defaults to the MaaSModelRef namespace if omitted.
46+
// +kubebuilder:validation:MaxLength=253
47+
// +optional
48+
Namespace string `json:"namespace,omitempty"`
3249
}
3350

3451
// ModelReference references a model endpoint in the same namespace
52+
// +kubebuilder:validation:XValidation:rule="self.kind != 'ExternalModel' || has(self.provider) && self.provider != ''",message="provider is required when kind is ExternalModel"
3553
type ModelReference struct {
3654
// Kind determines which fields are available
3755
// +kubebuilder:validation:Enum=LLMInferenceService;ExternalModel
3856
Kind string `json:"kind"`
3957

4058
// Name is the name of the model resource
4159
Name string `json:"name"`
60+
61+
// Provider identifies the API format and auth type for external models.
62+
// e.g. "openai", "anthropic". Only used when kind=ExternalModel.
63+
// +kubebuilder:validation:MaxLength=63
64+
// +optional
65+
Provider string `json:"provider,omitempty"`
66+
67+
// Endpoint is the FQDN of the external provider (no scheme or path).
68+
// e.g. "api.openai.com". Only used when kind=ExternalModel.
69+
// This field is metadata for downstream consumers (e.g. BBR provider-resolver plugin)
70+
// and is not used by the controller for endpoint derivation. Use spec.endpointOverride
71+
// to override the controller-derived endpoint.
72+
// +kubebuilder:validation:MaxLength=253
73+
// +kubebuilder:validation:Pattern=`^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)*$`
74+
// +optional
75+
Endpoint string `json:"endpoint,omitempty"`
4276
}
4377

4478
// MaaSModelStatus defines the observed state of MaaSModelRef

maas-controller/api/maas/v1alpha1/zz_generated.deepcopy.go

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

maas-controller/pkg/controller/maas/maasmodelref_controller.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func (r *MaaSModelRefReconciler) gatewayNamespace() string {
8282
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch
8383
//+kubebuilder:rbac:groups=kuadrant.io,resources=authpolicies,verbs=get;list;watch;create;update;patch;delete
8484
//+kubebuilder:rbac:groups=serving.kserve.io,resources=llminferenceservices,verbs=get;list;watch
85+
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get
8586

8687
const maasModelFinalizer = "maas.opendatahub.io/model-cleanup"
8788

@@ -133,6 +134,12 @@ func (r *MaaSModelRefReconciler) Reconcile(ctx context.Context, req ctrl.Request
133134
return ctrl.Result{}, nil
134135
}
135136

137+
// Validate credentialRef namespace matches the CR namespace (prevent cross-namespace Secret access)
138+
if ref := model.Spec.CredentialRef; ref != nil && ref.Namespace != "" && ref.Namespace != model.Namespace {
139+
r.updateStatus(ctx, model, "Failed", "spec.credentialRef.namespace must match metadata.namespace", statusSnapshot)
140+
return ctrl.Result{}, nil
141+
}
142+
136143
if err := handler.ReconcileRoute(ctx, log, model); err != nil {
137144
if errors.Is(err, ErrKindNotImplemented) {
138145
r.updateStatusWithReason(ctx, model, "Failed", fmt.Sprintf("kind not implemented: %s", kind), "Unsupported", statusSnapshot)

0 commit comments

Comments
 (0)