Adding Features to NIC
Adding a New Annotation (Ingress Only)
Annotations apply ONLY to Ingress objects, never to VirtualServer or VirtualServerRoute.
- Add constant in
internal/configs/annotations.go(e.g.,MyAnnotation = "nginx.org/my-annotation") - Add field to
ConfigParamsininternal/configs/config_params.go - Parse it in
parseAnnotations()ininternal/configs/annotations.go - Update
masterDenylist/minionDenylistif it should not be on master/minion - Apply it in
generateNginxCfg()ininternal/configs/ingress.go - Add the NGINX directive in
internal/configs/version1/nginx.ingress.tmplandinternal/configs/version1/nginx-plus.ingress.tmpl - Add validation in
internal/k8s/validation.goannotation validation chains - Add tests in
annotations_test.goandingress_test.go - Add a snapshot case in
internal/configs/version1/template_test.gowhose fixture sets the newConfigParamsfield, then runmake test-update-snapsand confirm the directive appears ininternal/configs/version1/__snapshots__/for every edition the annotation supports -- both OSS and Plus for shared directives, Plus golden files only for Plus-only ones
Gotchas
- Never forget both OSS and Plus templates -- they are separate files. The exception is a Plus-only directive, which belongs in the Plus template only
- Use
containsDangerousChars()for any user-provided string that ends up in NGINX config parseAnnotations()silently ignores unknown annotations -- add the constant first- An unchanged
__snapshots__diff after a.tmpledit means no fixture exercises the new branch -- the annotation is untested
Adding a New VirtualServer/VSR Field
- Add field to the appropriate struct in
pkg/apis/configuration/v1/types.gowith kubebuilder markers - Run
make update-codegenandmake update-crds - Add validation in
pkg/apis/configuration/validation/virtualserver.go - Add to the version2 template struct in
internal/configs/version2/http.go - Wire in
internal/configs/virtualserver.go(GenerateVirtualServerConfigor helper) - Add template rendering in
nginx.virtualserver.tmpl/nginx-plus.virtualserver.tmpl - Add a snapshot case in
internal/configs/version2/templates_test.gothat populates the new field in the fixture, then runmake test-update-snapsand verify the directive appears ininternal/configs/version2/__snapshots__/for every edition the field supports -- both OSS and Plus for shared directives, Plus golden files only for Plus-only ones - Check whether Ingress (v1) needs the same capability as an annotation
JSON Tag Conventions
- NGINX-proxy-related fields: kebab-case (
json:"lb-method",json:"fail-timeout") - K8s/application fields: camelCase (
json:"ingressClassName",json:"rewritePath")
Pointer vs Value Types
*int,*bool,*SomeStruct= optional/nullable- Plain
int,bool= required or zero-value default - Booleans defaulting to
falsemust be non-pointer
Kubebuilder Markers
| Marker | Purpose |
|---|---|
+kubebuilder:validation:Required |
Field must be present |
+kubebuilder:validation:Optional |
Field is optional |
+kubebuilder:validation:Pattern= `regex` |
Regex validation |
+kubebuilder:validation:Minimum=N |
Numeric minimum |
+kubebuilder:validation:MinItems=N / MaxItems=N |
Array length |
+kubebuilder:validation:MaxLength=N |
Max string length |
+kubebuilder:default=value |
Default value |
+kubebuilder:validation:XValidation:rule="CEL" |
Cross-field CEL validation |
CEL Validation Examples
// Prevent wildcard origin with credentials
// +kubebuilder:validation:XValidation:rule="!(self.allowOrigin.exists(origin, origin == '*') && has(self.allowCredentials) && self.allowCredentials == true)",message="..."
// Require time when allowedCodes is set
// +kubebuilder:validation:XValidation:rule="!has(self.allowedCodes) || (has(self.allowedCodes) && has(self.time))",message="..."
Gotchas
- Never skip
make update-codegenafter changingtypes.go - Never edit
zz_generated.deepcopy.gomanually make update-crdsalso refreshesdeploy/crds*.yamlanddocs/crd/-- commit all of itcharts/nginx-ingress/crdsis a symlink toconfig/crd/bases/-- never edit it directly- Version 2 has a single
Serverblock; Version 1 has multipleServerblocks
Adding a Helm Chart Value
- Add to
charts/nginx-ingress/values.yamlwith##documentation above the field - Add JSON schema in
charts/nginx-ingress/values.schema.json - If it maps to a CLI arg: add to
nginx-ingress.argsincharts/nginx-ingress/templates/_helpers.tpl - If it maps to a ConfigMap key: add to
charts/nginx-ingress/templates/controller-configmap.yaml - If it needs volumes/mounts: add to the volume helpers in
_helpers.tpl - Create a testdata file in
charts/tests/testdata/<feature>.yaml - Add test case in
charts/tests/helmunit_test.go - Run
make test-update-snapsand confirmcharts/tests/__snapshots__/contains the rendered value
Gotchas
- Always update all three workload templates (deployment, daemonset, statefulset) when they share logic via helpers
- Always update
values.schema.jsonalongsidevalues.yaml - Helm tests use terratest + go-snaps:
charts/tests/helmunit_test.go, gated behind thehelmunitbuild tag - A chart change with no
charts/tests/__snapshots__diff means no testdata file renders the new value