Unverified Commit 4f686262 authored by Julien Mancuso's avatar Julien Mancuso Committed by GitHub
Browse files

feat: include operator version in DGD (#6121)


Signed-off-by: default avatarJulien Mancuso <jmancuso@nvidia.com>
parent 5602dd2f
......@@ -19,11 +19,11 @@ maintainers:
url: https://www.nvidia.com
description: A Helm chart for NVIDIA Dynamo Platform.
type: application
version: 0.9.0
version: 1.0.0-dev
home: https://nvidia.com
dependencies:
- name: dynamo-operator
version: 0.7.1
version: 1.0.0-dev
repository: file://components/operator
condition: dynamo-operator.enabled
- name: nats
......
......@@ -27,9 +27,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.7.1
version: 1.0.0-dev
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.7.1"
appVersion: "1.0.0-dev"
......@@ -143,7 +143,7 @@ spec:
- --namespace-scope-lease-renew-interval={{ .Values.namespaceRestriction.lease.renewInterval }}
{{- end }}
{{- end }}
- --operator-version={{ .Values.controllerManager.manager.image.tag | default .Chart.AppVersion }}
- --operator-version={{ .Chart.AppVersion }}
{{- if .Values.webhook.enabled }}
- --enable-webhooks=true
{{- end }}
......
......@@ -98,6 +98,14 @@ rules:
resourceNames: ["{{ include "dynamo-operator.fullname" . }}-validating"]
{{- end }}
verbs: ["get", "patch"]
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["mutatingwebhookconfigurations"]
{{- if .Values.namespaceRestriction.enabled }}
resourceNames: ["{{ include "dynamo-operator.fullname" . }}-mutating-{{ .Release.Namespace }}"]
{{- else }}
resourceNames: ["{{ include "dynamo-operator.fullname" . }}-mutating"]
{{- end }}
verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
......@@ -176,9 +184,11 @@ spec:
SECRET_NAME="{{ .Values.webhook.certificateSecret.name }}"
NAMESPACE="{{ .Release.Namespace }}"
{{- if .Values.namespaceRestriction.enabled }}
WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-validating-{{ .Release.Namespace }}"
VALIDATING_WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-validating-{{ .Release.Namespace }}"
MUTATING_WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-mutating-{{ .Release.Namespace }}"
{{- else }}
WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-validating"
VALIDATING_WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-validating"
MUTATING_WEBHOOK_NAME="{{ include "dynamo-operator.fullname" . }}-mutating"
{{- end }}
echo "⏳ Waiting for certificate secret to be available..."
......@@ -207,8 +217,8 @@ spec:
fi
echo "📝 Patching ValidatingWebhookConfiguration..."
# Patch all webhooks (DynamoComponentDeployment, DynamoGraphDeployment, DynamoModel, DynamoGraphDeploymentRequest)
kubectl patch validatingwebhookconfiguration ${WEBHOOK_NAME} \
# Patch all validating webhooks (DynamoComponentDeployment, DynamoGraphDeployment, DynamoModel, DynamoGraphDeploymentRequest)
kubectl patch validatingwebhookconfiguration ${VALIDATING_WEBHOOK_NAME} \
--type='json' -p="[
{
\"op\": \"add\",
......@@ -232,6 +242,17 @@ spec:
}
]"
echo "📝 Patching MutatingWebhookConfiguration..."
# Patch mutating webhook (DynamoGraphDeployment defaulting)
kubectl patch mutatingwebhookconfiguration ${MUTATING_WEBHOOK_NAME} \
--type='json' -p="[
{
\"op\": \"add\",
\"path\": \"/webhooks/0/clientConfig/caBundle\",
\"value\": \"${CA_BUNDLE}\"
}
]"
echo "✅ CA bundle injected successfully!"
echo "🎉 Webhook configuration complete!"
securityContext:
......
......@@ -169,5 +169,57 @@ webhooks:
- dynamographdeploymentrequests
sideEffects: None
timeoutSeconds: {{ .Values.webhook.timeoutSeconds }}
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
{{- if .Values.namespaceRestriction.enabled }}
name: {{ include "dynamo-operator.fullname" . }}-mutating-{{ .Release.Namespace }}
{{- else }}
name: {{ include "dynamo-operator.fullname" . }}-mutating
{{- end }}
labels:
app.kubernetes.io/component: webhook
app.kubernetes.io/created-by: dynamo-operator
app.kubernetes.io/part-of: dynamo-operator
{{- include "dynamo-operator.labels" . | nindent 4 }}
{{- if .Values.webhook.certManager.enabled }}
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "dynamo-operator.fullname" . }}-serving-cert
{{- end }}
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
{{- if and (not .Values.webhook.certManager.enabled) .Values.webhook.certificateSecret.external }}
{{- if .Values.webhook.caBundle }}
caBundle: {{ .Values.webhook.caBundle }}
{{- end }}
{{- end }}
service:
name: {{ include "dynamo-operator.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /mutate-nvidia-com-v1alpha1-dynamographdeployment
failurePolicy: {{ .Values.webhook.failurePolicy }}
name: mdynamographdeployment.kb.io
{{- if .Values.webhook.namespaceSelector }}
namespaceSelector:
{{- toYaml .Values.webhook.namespaceSelector | nindent 4 }}
{{- else if .Values.namespaceRestriction.enabled }}
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ .Release.Namespace }}
{{- end }}
rules:
- apiGroups:
- nvidia.com
apiVersions:
- v1alpha1
operations:
- CREATE
resources:
- dynamographdeployments
sideEffects: None
timeoutSeconds: {{ .Values.webhook.timeoutSeconds }}
{{- end }}
......@@ -54,6 +54,7 @@ import (
lwsscheme "sigs.k8s.io/lws/client-go/clientset/versioned/scheme"
volcanoscheme "volcano.sh/apis/pkg/client/clientset/versioned/scheme"
semver "github.com/Masterminds/semver/v3"
nvidiacomv1alpha1 "github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/controller"
......@@ -66,6 +67,7 @@ import (
"github.com/ai-dynamo/dynamo/deploy/operator/internal/secret"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/secrets"
internalwebhook "github.com/ai-dynamo/dynamo/deploy/operator/internal/webhook"
webhookdefaulting "github.com/ai-dynamo/dynamo/deploy/operator/internal/webhook/defaulting"
webhookvalidation "github.com/ai-dynamo/dynamo/deploy/operator/internal/webhook/validation"
grovev1alpha1 "github.com/ai-dynamo/grove/operator/api/core/v1alpha1"
istioclientsetscheme "istio.io/client-go/pkg/clientset/versioned/scheme"
......@@ -257,6 +259,14 @@ func main() {
os.Exit(1)
}
// Validate and normalize operator version to semver
if _, err := semver.NewVersion(operatorVersion); err != nil {
setupLog.Info("WARNING: operator-version is not valid semver, falling back to 0.0.0-unknown",
"provided", operatorVersion, "error", err.Error())
operatorVersion = "0.0.0-unknown"
}
setupLog.Info("Operator version configured", "version", operatorVersion)
// Validate discoveryBackend value
if discoveryBackend != "kubernetes" && discoveryBackend != "etcd" {
setupLog.Error(nil, "invalid discovery-backend value, must be 'kubernetes' or 'etcd'", "value", discoveryBackend)
......@@ -739,6 +749,17 @@ func main() {
}
setupLog.Info("Validation webhooks registered successfully")
// Register defaulting (mutating) webhook handlers
setupLog.Info("Registering defaulting webhooks")
dgdDefaulter := webhookdefaulting.NewDGDDefaulter(operatorVersion)
if err = dgdDefaulter.RegisterWithManager(mgr); err != nil {
setupLog.Error(err, "unable to register webhook", "webhook", "DynamoGraphDeployment-defaulting")
os.Exit(1)
}
setupLog.Info("Defaulting webhooks registered successfully")
}
//+kubebuilder:scaffold:builder
......
......@@ -4,6 +4,7 @@ go 1.25.0
require (
emperror.dev/errors v0.8.1
github.com/Masterminds/semver/v3 v3.4.0
github.com/ai-dynamo/grove/operator/api v0.1.0-alpha.6
github.com/bsm/gomega v1.27.10
github.com/go-logr/logr v1.4.3
......@@ -30,7 +31,6 @@ require (
)
require (
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
......
......@@ -81,6 +81,10 @@ const (
DefaultGroveTerminationDelay = 15 * time.Minute
// Operator origin version: stamped on DGD at creation time by mutating webhook.
// Records which operator version created the resource, enabling version-gated behavior changes.
KubeAnnotationDynamoOperatorOriginVersion = "nvidia.com/dynamo-operator-origin-version"
// Metrics related constants
KubeAnnotationEnableMetrics = "nvidia.com/enable-metrics" // User-provided annotation to control metrics
KubeLabelMetricsEnabled = "nvidia.com/metrics-enabled" // Controller-managed label for pod selection
......
......@@ -270,7 +270,7 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD
labels[commonconsts.KubeLabelDynamoNamespace] = dynamoNamespace
labels[commonconsts.KubeLabelDynamoGraphDeploymentName] = parentDynamoGraphDeployment.Name
// Propagate metrics annotation from parent deployment if present
// Propagate annotations from parent deployment if present
if parentDynamoGraphDeployment.Annotations != nil {
if deployment.Spec.Annotations == nil {
deployment.Spec.Annotations = make(map[string]string)
......@@ -281,6 +281,10 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD
if val, exists := parentDynamoGraphDeployment.Annotations[commonconsts.KubeAnnotationDynamoDiscoveryBackend]; exists {
deployment.Spec.Annotations[commonconsts.KubeAnnotationDynamoDiscoveryBackend] = val
}
// Propagate operator origin version for version-gated behavior in backends
if val, exists := parentDynamoGraphDeployment.Annotations[commonconsts.KubeAnnotationDynamoOperatorOriginVersion]; exists {
deployment.Spec.Annotations[commonconsts.KubeAnnotationDynamoOperatorOriginVersion] = val
}
}
// Apply restart annotation if this service should be restarted.
......@@ -1211,6 +1215,14 @@ func GenerateGrovePodCliqueSet(
component.Annotations[commonconsts.KubeAnnotationDynamoDiscoveryBackend] = discoveryBackend
}
// Propagate operator origin version for version-gated behavior in backends
if val, exists := dynamoDeployment.Annotations[commonconsts.KubeAnnotationDynamoOperatorOriginVersion]; exists {
if component.Annotations == nil {
component.Annotations = make(map[string]string)
}
component.Annotations[commonconsts.KubeAnnotationDynamoOperatorOriginVersion] = val
}
// Get checkpoint info for this service if available
var checkpointInfo *checkpoint.CheckpointInfo
if checkpointInfoByService != nil {
......
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* 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 defaulting
import (
"context"
"fmt"
nvidiacomv1alpha1 "github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
const (
dgdDefaultingWebhookName = "dynamographdeployment-defaulting-webhook"
dgdDefaultingWebhookPath = "/mutate-nvidia-com-v1alpha1-dynamographdeployment"
)
// DGDDefaulter is a mutating webhook handler that stamps DynamoGraphDeployments
// with the operator version on CREATE. This provides a general-purpose mechanism
// for version-gated behavior changes in the controller.
type DGDDefaulter struct {
OperatorVersion string
}
// NewDGDDefaulter creates a new DGDDefaulter with the given operator version.
func NewDGDDefaulter(operatorVersion string) *DGDDefaulter {
return &DGDDefaulter{
OperatorVersion: operatorVersion,
}
}
// Default implements admission.CustomDefaulter.
// On CREATE: stamps nvidia.com/dynamo-operator-origin-version with the operator version.
// On UPDATE: does nothing -- the origin version is immutable once set.
func (d *DGDDefaulter) Default(ctx context.Context, obj runtime.Object) error {
logger := log.FromContext(ctx).WithName(dgdDefaultingWebhookName)
dgd, ok := obj.(*nvidiacomv1alpha1.DynamoGraphDeployment)
if !ok {
return fmt.Errorf("expected DynamoGraphDeployment but got %T", obj)
}
req, err := admission.RequestFromContext(ctx)
if err != nil {
logger.Error(err, "failed to get admission request from context, skipping defaulting")
return nil
}
if req.Operation == admissionv1.Create {
if dgd.Annotations == nil {
dgd.Annotations = make(map[string]string)
}
// Stamp operator version on creation (don't overwrite if already set)
if _, exists := dgd.Annotations[consts.KubeAnnotationDynamoOperatorOriginVersion]; !exists {
dgd.Annotations[consts.KubeAnnotationDynamoOperatorOriginVersion] = d.OperatorVersion
logger.Info("stamped operator origin version on DGD",
"name", dgd.Name,
"namespace", dgd.Namespace,
"version", d.OperatorVersion)
}
}
return nil
}
// RegisterWithManager registers the defaulting webhook with the manager.
func (d *DGDDefaulter) RegisterWithManager(mgr manager.Manager) error {
webhook := admission.
WithCustomDefaulter(mgr.GetScheme(), &nvidiacomv1alpha1.DynamoGraphDeployment{}, d).
WithRecoverPanic(true)
mgr.GetWebhookServer().Register(dgdDefaultingWebhookPath, webhook)
return nil
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* 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 defaulting
import (
"context"
"testing"
nvidiacomv1alpha1 "github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// admissionCtx builds a context carrying an admission request for the given operation.
func admissionCtx(op admissionv1.Operation) context.Context {
return admission.NewContextWithRequest(context.Background(), admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Operation: op,
},
})
}
func TestDGDDefaulter_Default(t *testing.T) {
const testVersion = "0.8.0"
tests := []struct {
name string
operatorVersion string
ctx context.Context
dgd *nvidiacomv1alpha1.DynamoGraphDeployment
wantAnnotation string
wantErr bool
}{
{
name: "CREATE stamps operator version on new DGD without annotations",
operatorVersion: testVersion,
ctx: admissionCtx(admissionv1.Create),
dgd: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dgd",
Namespace: "default",
},
},
wantAnnotation: testVersion,
},
{
name: "CREATE stamps operator version on DGD with existing annotations",
operatorVersion: testVersion,
ctx: admissionCtx(admissionv1.Create),
dgd: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dgd",
Namespace: "default",
Annotations: map[string]string{
"some-other-annotation": "some-value",
},
},
},
wantAnnotation: testVersion,
},
{
name: "CREATE does not overwrite pre-existing origin version",
operatorVersion: testVersion,
ctx: admissionCtx(admissionv1.Create),
dgd: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dgd",
Namespace: "default",
Annotations: map[string]string{
consts.KubeAnnotationDynamoOperatorOriginVersion: "0.7.0",
},
},
},
wantAnnotation: "0.7.0",
},
{
name: "UPDATE does not stamp annotation",
operatorVersion: testVersion,
ctx: admissionCtx(admissionv1.Update),
dgd: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dgd",
Namespace: "default",
},
},
wantAnnotation: "",
},
{
name: "UPDATE preserves existing annotation",
operatorVersion: testVersion,
ctx: admissionCtx(admissionv1.Update),
dgd: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dgd",
Namespace: "default",
Annotations: map[string]string{
consts.KubeAnnotationDynamoOperatorOriginVersion: "0.7.0",
},
},
},
wantAnnotation: "0.7.0",
},
{
name: "DELETE does not stamp annotation",
operatorVersion: testVersion,
ctx: admissionCtx(admissionv1.Delete),
dgd: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dgd",
Namespace: "default",
},
},
wantAnnotation: "",
},
{
name: "no admission request in context skips defaulting gracefully",
operatorVersion: testVersion,
ctx: context.Background(),
dgd: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dgd",
Namespace: "default",
},
},
wantAnnotation: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defaulter := NewDGDDefaulter(tt.operatorVersion)
err := defaulter.Default(tt.ctx, tt.dgd)
if (err != nil) != tt.wantErr {
t.Errorf("Default() error = %v, wantErr %v", err, tt.wantErr)
return
}
got := ""
if tt.dgd.Annotations != nil {
got = tt.dgd.Annotations[consts.KubeAnnotationDynamoOperatorOriginVersion]
}
if got != tt.wantAnnotation {
t.Errorf("annotation %q = %q, want %q",
consts.KubeAnnotationDynamoOperatorOriginVersion, got, tt.wantAnnotation)
}
})
}
}
......@@ -23,7 +23,9 @@ import (
"fmt"
"sort"
semver "github.com/Masterminds/semver/v3"
nvidiacomv1alpha1 "github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
internalwebhook "github.com/ai-dynamo/dynamo/deploy/operator/internal/webhook"
authenticationv1 "k8s.io/api/authentication/v1"
ctrl "sigs.k8s.io/controller-runtime"
......@@ -62,6 +64,11 @@ func (v *DynamoGraphDeploymentValidator) Validate(ctx context.Context) (admissio
return nil, fmt.Errorf("spec.services must have at least one service")
}
// Validate annotations
if err := v.validateAnnotations(); err != nil {
return nil, err
}
// Validate PVCs
if err := v.validatePVCs(); err != nil {
return nil, err
......@@ -335,6 +342,26 @@ func (v *DynamoGraphDeploymentValidator) validateRestartStrategyOrder() error {
return err
}
// validateAnnotations validates known DGD annotations have valid values.
func (v *DynamoGraphDeploymentValidator) validateAnnotations() error {
annotations := v.deployment.GetAnnotations()
if annotations == nil {
return nil
}
var errs []error
// Validate operator origin version is valid semver (if present)
if value, exists := annotations[consts.KubeAnnotationDynamoOperatorOriginVersion]; exists {
if _, err := semver.NewVersion(value); err != nil {
errs = append(errs, fmt.Errorf("annotation %s has invalid value %q: must be valid semver",
consts.KubeAnnotationDynamoOperatorOriginVersion, value))
}
}
return errors.Join(errs...)
}
func getUnique[T comparable](slice []T) []T {
seen := make(map[T]struct{}, len(slice))
uniqueSlice := make([]T, 0, len(slice))
......
......@@ -24,6 +24,7 @@ import (
"testing"
nvidiacomv1alpha1 "github.com/ai-dynamo/dynamo/deploy/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/operator/internal/consts"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
......@@ -507,6 +508,95 @@ func TestDynamoGraphDeploymentValidator_Validate(t *testing.T) {
},
wantErr: false,
},
// Annotation validation test cases
{
name: "no annotations is valid",
deployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-graph",
Namespace: "default",
},
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"main": {},
},
},
},
wantErr: false,
},
{
name: "valid annotation dynamo-operator-origin-version with semver",
deployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-graph",
Namespace: "default",
Annotations: map[string]string{
consts.KubeAnnotationDynamoOperatorOriginVersion: "1.0.0",
},
},
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"main": {},
},
},
},
wantErr: false,
},
{
name: "valid annotation dynamo-operator-origin-version with pre-release",
deployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-graph",
Namespace: "default",
Annotations: map[string]string{
consts.KubeAnnotationDynamoOperatorOriginVersion: "1.0.0-dev",
},
},
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"main": {},
},
},
},
wantErr: false,
},
{
name: "valid annotation dynamo-operator-origin-version fallback version",
deployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-graph",
Namespace: "default",
Annotations: map[string]string{
consts.KubeAnnotationDynamoOperatorOriginVersion: "0.0.0-unknown",
},
},
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"main": {},
},
},
},
wantErr: false,
},
{
name: "invalid annotation dynamo-operator-origin-version not semver",
deployment: &nvidiacomv1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-graph",
Namespace: "default",
Annotations: map[string]string{
consts.KubeAnnotationDynamoOperatorOriginVersion: "not-a-version",
},
},
Spec: nvidiacomv1alpha1.DynamoGraphDeploymentSpec{
Services: map[string]*nvidiacomv1alpha1.DynamoComponentDeploymentSharedSpec{
"main": {},
},
},
},
wantErr: true,
errMsg: `annotation nvidia.com/dynamo-operator-origin-version has invalid value "not-a-version": must be valid semver`,
},
}
for _, tt := range tests {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment