Skip to content

Commit 7aa0c63

Browse files
committed
VWC logic now moved to operator code for both OLM and Helm.
Signed-off-by: Aryan <[email protected]>
1 parent 3ff2e94 commit 7aa0c63

File tree

7 files changed

+247
-47
lines changed

7 files changed

+247
-47
lines changed

bundle/manifests/k8s-nim-operator.clusterserviceversion.yaml

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,18 @@ spec:
13451345
- watch
13461346
- create
13471347
- delete
1348+
- apiGroups:
1349+
- admissionregistration.k8s.io
1350+
resources:
1351+
- validatingwebhookconfigurations
1352+
verbs:
1353+
- get
1354+
- list
1355+
- watch
1356+
- create
1357+
- update
1358+
- patch
1359+
- delete
13481360
- apiGroups:
13491361
- gateway.networking.k8s.io
13501362
resources:
@@ -1413,6 +1425,8 @@ spec:
14131425
fieldPath: metadata.namespace
14141426
- name: ENABLE_WEBHOOKS
14151427
value: "true"
1428+
- name: OPERATOR_NAME_PREFIX
1429+
value: "k8s-nim-operator"
14161430
image: 'ghcr.io/nvidia/k8s-nim-operator:main'
14171431
imagePullPolicy: Always
14181432
livenessProbe:
@@ -1426,6 +1440,10 @@ spec:
14261440
successThreshold: 1
14271441
timeoutSeconds: 1
14281442
name: manager
1443+
volumeMounts:
1444+
- name: cert
1445+
mountPath: /tmp/k8s-webhook-server/serving-certs
1446+
readOnly: true
14291447
readinessProbe:
14301448
failureThreshold: 3
14311449
httpGet:
@@ -1447,6 +1465,11 @@ spec:
14471465
allowPrivilegeEscalation: false
14481466
terminationMessagePath: /dev/termination-log
14491467
terminationMessagePolicy: File
1468+
volumes:
1469+
- name: cert
1470+
secret:
1471+
secretName: k8s-nim-operator-webhook-server-cert
1472+
defaultMode: 420
14501473
dnsPolicy: ClusterFirst
14511474
imagePullSecrets: []
14521475
restartPolicy: Always
@@ -1469,46 +1492,4 @@ spec:
14691492
- type: MultiNamespace
14701493
supported: false
14711494
- type: AllNamespaces
1472-
supported: true
1473-
webhookdefinitions:
1474-
- type: ValidatingAdmissionWebhook
1475-
admissionReviewVersions:
1476-
- v1
1477-
containerPort: 9443
1478-
targetPort: 9443
1479-
deploymentName: k8s-nim-operator
1480-
failurePolicy: Fail
1481-
generateName: vnimcache-v1alpha1.kb.io
1482-
rules:
1483-
- apiGroups:
1484-
- apps.nvidia.com
1485-
apiVersions:
1486-
- v1alpha1
1487-
operations:
1488-
- CREATE
1489-
- UPDATE
1490-
resources:
1491-
- nimcaches
1492-
sideEffects: None
1493-
webhookPath: /validate-apps-nvidia-com-v1alpha1-nimcache
1494-
- type: ValidatingAdmissionWebhook
1495-
admissionReviewVersions:
1496-
- v1
1497-
containerPort: 9443
1498-
targetPort: 9443
1499-
deploymentName: k8s-nim-operator
1500-
failurePolicy: Fail
1501-
generateName: vnimservice-v1alpha1.kb.io
1502-
rules:
1503-
- apiGroups:
1504-
- apps.nvidia.com
1505-
apiVersions:
1506-
- v1alpha1
1507-
operations:
1508-
- CREATE
1509-
- UPDATE
1510-
resources:
1511-
- nimservices
1512-
sideEffects: None
1513-
webhookPath: /validate-apps-nvidia-com-v1alpha1-nimservice
1514-
1495+
supported: true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: k8s-nim-operator-webhook-service
5+
labels:
6+
app.kubernetes.io/name: k8s-nim-operator
7+
app.kubernetes.io/instance: nim-operator
8+
control-plane: controller-manager
9+
annotations:
10+
service.beta.openshift.io/serving-cert-secret-name: k8s-nim-operator-webhook-server-cert
11+
spec:
12+
selector:
13+
app.kubernetes.io/name: k8s-nim-operator
14+
app.kubernetes.io/instance: nim-operator
15+
control-plane: controller-manager
16+
ports:
17+
- port: 443
18+
targetPort: 9443
19+
protocol: TCP

cmd/main.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"context"
2021
"crypto/tls"
2122
"flag"
2223
"os"
@@ -34,6 +35,7 @@ import (
3435
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3536
_ "k8s.io/client-go/plugin/pkg/client/auth"
3637
ctrl "sigs.k8s.io/controller-runtime"
38+
crclient "sigs.k8s.io/controller-runtime/pkg/client"
3739
"sigs.k8s.io/controller-runtime/pkg/healthz"
3840
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3941
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
@@ -45,7 +47,9 @@ import (
4547

4648
appsv1alpha1 "github.com/NVIDIA/k8s-nim-operator/api/apps/v1alpha1"
4749
"github.com/NVIDIA/k8s-nim-operator/internal/conditions"
50+
"github.com/NVIDIA/k8s-nim-operator/internal/config"
4851
"github.com/NVIDIA/k8s-nim-operator/internal/controller"
52+
"github.com/NVIDIA/k8s-nim-operator/internal/k8sutil"
4953
"github.com/NVIDIA/k8s-nim-operator/internal/render"
5054
webhookappsv1alpha1 "github.com/NVIDIA/k8s-nim-operator/internal/webhook/apps/v1alpha1"
5155
// +kubebuilder:scaffold:imports
@@ -264,17 +268,38 @@ func main() {
264268

265269
// nolint:goconst
266270
// Parse ENABLE_WEBHOOKS environment variable once as a boolean.
267-
var enableWebhooks bool
268271
if val, ok := os.LookupEnv("ENABLE_WEBHOOKS"); ok {
269272
var err error
270-
enableWebhooks, err = strconv.ParseBool(val)
273+
enableWebhooks, err := strconv.ParseBool(val)
271274
if err != nil {
272275
setupLog.Error(err, "invalid value for ENABLE_WEBHOOKS, expected boolean")
273276
os.Exit(1)
274277
}
278+
config.EnableWebhooks = enableWebhooks
279+
}
280+
if val, ok := os.LookupEnv("OPERATOR_NAME_PREFIX"); ok {
281+
config.OperatorNamePrefix = val
282+
}
283+
if val, ok := os.LookupEnv("OPERATOR_NAMESPACE"); ok {
284+
config.OperatorNamespace = val
275285
}
276286

277-
if enableWebhooks {
287+
cfg := ctrl.GetConfigOrDie()
288+
liveClient, err := crclient.New(cfg, crclient.Options{Scheme: scheme})
289+
if err != nil {
290+
setupLog.Error(err, "unable to construct live client")
291+
os.Exit(1)
292+
}
293+
ctx := context.Background()
294+
orch, err := k8sutil.GetOrchestratorType(ctx, liveClient) // uses direct REST calls
295+
if err != nil {
296+
setupLog.Error(err, "failed to detect orchestrator type")
297+
os.Exit(1)
298+
}
299+
config.OrchestratorType = orch
300+
setupLog.Info("detected orchestrator", "type", orch)
301+
302+
if config.EnableWebhooks {
278303
if err := webhookappsv1alpha1.SetupNIMCacheWebhookWithManager(mgr); err != nil {
279304
setupLog.Error(err, "unable to create webhook", "webhook", "NIMCache")
280305
os.Exit(1)
@@ -284,6 +309,17 @@ func main() {
284309
setupLog.Error(err, "unable to create webhook", "webhook", "NIMService")
285310
os.Exit(1)
286311
}
312+
// Set up cluster-level ValidatingWebhookConfiguration.
313+
if err := webhookappsv1alpha1.EnsureValidatingWebhook(
314+
context.TODO(),
315+
mgr.GetAPIReader(),
316+
mgr.GetClient(),
317+
config.OperatorNamespace,
318+
config.OperatorNamePrefix,
319+
); err != nil {
320+
setupLog.Error(err, "unable to ensure ValidatingWebhookConfiguration")
321+
os.Exit(1)
322+
}
287323
}
288324
// +kubebuilder:scaffold:builder
289325

deployments/helm/k8s-nim-operator/templates/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ spec:
5151
valueFrom:
5252
fieldRef:
5353
fieldPath: metadata.namespace
54+
- name: OPERATOR_NAME_PREFIX
55+
value: {{ include "k8s-nim-operator.fullname" . }}
5456
- name: ENABLE_WEBHOOKS
5557
value: "{{ .Values.operator.admissionController.enabled }}"
5658
livenessProbe:

deployments/helm/k8s-nim-operator/templates/manager-rbac.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,10 @@ rules:
582582
- get
583583
- list
584584
- watch
585-
- patch
585+
- create
586586
- update
587+
- patch
588+
- delete
587589
- apiGroups:
588590
- gateway.networking.k8s.io
589591
resources:

internal/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package config
2+
3+
import "github.com/NVIDIA/k8s-nim-operator/internal/k8sutil"
4+
5+
var (
6+
EnableWebhooks bool
7+
OperatorNamePrefix string
8+
OperatorNamespace string
9+
OrchestratorType k8sutil.OrchestratorType
10+
)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package v1alpha1
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
admissionv1 "k8s.io/api/admissionregistration/v1"
8+
"k8s.io/apimachinery/pkg/api/errors"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/types"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
13+
"github.com/NVIDIA/k8s-nim-operator/internal/config"
14+
"github.com/NVIDIA/k8s-nim-operator/internal/k8sutil"
15+
)
16+
17+
// EnsureValidatingWebhook creates or updates the ValidatingWebhookConfiguration
18+
// that used to be templated by Helm. It is a best-effort reconciliation and
19+
// returns an error only when we cannot make the desired state match the spec.
20+
func EnsureValidatingWebhook(
21+
ctx context.Context,
22+
apiReader client.Reader,
23+
writer client.Client,
24+
namespace string,
25+
fullNamePrefix string,
26+
) error {
27+
// Desired validatingwebhookconfiguration spec.
28+
desired := buildConfigurationSpec(namespace, fullNamePrefix)
29+
30+
// Check if there is already a spec.
31+
existing := &admissionv1.ValidatingWebhookConfiguration{}
32+
err := apiReader.Get(ctx, types.NamespacedName{Name: desired.Name}, existing)
33+
if err != nil && !errors.IsNotFound(err) {
34+
return err
35+
}
36+
37+
if errors.IsNotFound(err) {
38+
return writer.Create(ctx, desired)
39+
}
40+
41+
// Deep-compare; update only if something differs.
42+
cur, _ := json.Marshal(existing.Webhooks)
43+
want, _ := json.Marshal(desired.Webhooks)
44+
45+
if string(cur) == string(want) {
46+
return nil
47+
}
48+
49+
existing.Webhooks = desired.Webhooks
50+
existing.Annotations = desired.Annotations
51+
return writer.Update(ctx, existing)
52+
}
53+
54+
// buildDesired reproduces the spec that used to be in Helm.
55+
func buildConfigurationSpec(namespace, namePrefix string) *admissionv1.ValidatingWebhookConfiguration {
56+
pathCache := "/validate-apps-nvidia-com-v1alpha1-nimcache"
57+
pathService := "/validate-apps-nvidia-com-v1alpha1-nimservice"
58+
59+
// Use appropriate annotations/labels as per deployment mode.
60+
var annotations map[string]string
61+
var labels map[string]string
62+
var clientconfignimcache admissionv1.WebhookClientConfig
63+
var clientconfignimservice admissionv1.WebhookClientConfig
64+
65+
clientconfignimcache = admissionv1.WebhookClientConfig{
66+
Service: &admissionv1.ServiceReference{
67+
Namespace: namespace,
68+
Name: namePrefix + "-webhook-service",
69+
Path: &pathCache,
70+
},
71+
}
72+
clientconfignimservice = admissionv1.WebhookClientConfig{
73+
Service: &admissionv1.ServiceReference{
74+
Namespace: namespace,
75+
Name: namePrefix + "-webhook-service",
76+
Path: &pathService,
77+
},
78+
}
79+
80+
// Deployment specific values.
81+
if config.OrchestratorType == k8sutil.K8s {
82+
annotations = map[string]string{"cert-manager.io/inject-ca-from": namespace + "/" + namePrefix + "-serving-cert"}
83+
labels = map[string]string{
84+
"app.kubernetes.io/name": "k8s-nim-operator",
85+
"app.kubernetes.io/managed-by": "helm",
86+
}
87+
} else {
88+
annotations = map[string]string{"service.beta.openshift.io/inject-cabundle": "true"}
89+
labels = map[string]string{
90+
"app.kubernetes.io/name": "k8s-nim-operator",
91+
"app.kubernetes.io/managed-by": "openshift",
92+
}
93+
}
94+
95+
return &admissionv1.ValidatingWebhookConfiguration{
96+
ObjectMeta: metav1.ObjectMeta{
97+
Name: namePrefix + "-validating-webhook-configuration",
98+
Annotations: annotations,
99+
Labels: labels,
100+
},
101+
Webhooks: []admissionv1.ValidatingWebhook{
102+
{
103+
Name: "vnimcache-v1alpha1.kb.io",
104+
AdmissionReviewVersions: []string{"v1"},
105+
ClientConfig: clientconfignimcache,
106+
FailurePolicy: func() *admissionv1.FailurePolicyType {
107+
fp := admissionv1.Fail
108+
return &fp
109+
}(),
110+
SideEffects: func() *admissionv1.SideEffectClass {
111+
s := admissionv1.SideEffectClassNone
112+
return &s
113+
}(),
114+
Rules: []admissionv1.RuleWithOperations{{
115+
Operations: []admissionv1.OperationType{
116+
admissionv1.Create, admissionv1.Update,
117+
},
118+
Rule: admissionv1.Rule{
119+
APIGroups: []string{"apps.nvidia.com"},
120+
APIVersions: []string{"v1alpha1"},
121+
Resources: []string{"nimcaches"},
122+
},
123+
}},
124+
},
125+
{
126+
Name: "vnimservice-v1alpha1.kb.io",
127+
AdmissionReviewVersions: []string{"v1"},
128+
ClientConfig: clientconfignimservice,
129+
FailurePolicy: func() *admissionv1.FailurePolicyType {
130+
fp := admissionv1.Fail
131+
return &fp
132+
}(),
133+
SideEffects: func() *admissionv1.SideEffectClass {
134+
s := admissionv1.SideEffectClassNone
135+
return &s
136+
}(),
137+
Rules: []admissionv1.RuleWithOperations{{
138+
Operations: []admissionv1.OperationType{
139+
admissionv1.Create, admissionv1.Update,
140+
},
141+
Rule: admissionv1.Rule{
142+
APIGroups: []string{"apps.nvidia.com"},
143+
APIVersions: []string{"v1alpha1"},
144+
Resources: []string{"nimservices"},
145+
},
146+
}},
147+
},
148+
},
149+
}
150+
}

0 commit comments

Comments
 (0)