Repository: kubernetes-sigs/node-feature-discovery
Pull request #1633 (2024-03-19 14:33:22)

apis/nfd/validate: loosen validation of feature annotations
Don't require that the annotation value must conform to the (strict)
requirements of label values. In the Kubernetes API annotation values do
not have other restrictions than that the total size (keys and values)
of _all_ annotations combined of an object must not exceed 256kB.

This patch sets a maximum size limit of 1kB for the value of a single
feature annotation created by NFD. This limit is rather arbitrary but
should be enough for the NFD usage scenarios (until proven wrong).

Also add license header to the test.go file and fix one bug in
MatchFeature validation.

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/node-feature-discovery/pull/1633#" title="Author self-approved">marquiz</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnode-feature-discovery).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/OWNERS)~~ [marquiz]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *kubernetes-sigs-nfd* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | eb92983938df506cf94138ea0f7725bbc49e8ef7 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/kubernetes-sigs-nfd/deploys/65f9a235be297700084a7b62 |
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 31.20%. Comparing base [(`0ad5e50`)](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/commit/0ad5e50f245f7f185b340578a64e4125b577d81a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) to head [(`eb92983`)](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs).

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633/graphs/tree.svg?width=650&height=150&src=pr&token=mEZgtJQ1vg&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs)](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs)

```diff
@@            Coverage Diff             @@
##           master    #1633      +/-   ##
==========================================
+ Coverage   30.52%   31.20%   +0.67%     
==========================================
  Files         102      102              
  Lines        9627     9632       +5     
==========================================
+ Hits         2939     3006      +67     
+ Misses       6417     6358      -59     
+ Partials      271      268       -3     
```


| [Files](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) | Coverage Δ | |
|---|---|---|
| [pkg/apis/nfd/validate/validate.go](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs#diff-cGtnL2FwaXMvbmZkL3ZhbGlkYXRlL3ZhbGlkYXRlLmdv) | `100.00% <100.00%> (+44.92%)` | :arrow_up: |

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 31.20%. Comparing base [(`0ad5e50`)](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/commit/0ad5e50f245f7f185b340578a64e4125b577d81a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) to head [(`eb92983`)](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs).

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633/graphs/tree.svg?width=650&height=150&src=pr&token=mEZgtJQ1vg&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs)](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs)

```diff
@@            Coverage Diff             @@
##           master    #1633      +/-   ##
==========================================
+ Coverage   30.52%   31.20%   +0.67%     
==========================================
  Files         102      102              
  Lines        9627     9632       +5     
==========================================
+ Hits         2939     3006      +67     
+ Misses       6417     6358      -59     
+ Partials      271      268       -3     
```


| [Files](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) | Coverage Δ | |
|---|---|---|
| [pkg/apis/nfd/validate/validate.go](https://app.codecov.io/gh/kubernetes-sigs/node-feature-discovery/pull/1633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs#diff-cGtnL2FwaXMvbmZkL3ZhbGlkYXRlL3ZhbGlkYXRlLmdv) | `100.00% <100.00%> (+44.92%)` | :arrow_up: |

</details>
marquiz: /assign @ArangoGutierrez 

Patch:
diff --git a/api/nfd/go.mod b/api/nfd/go.mod
index f1e4ce1ea..c763e745c 100644
--- a/api/nfd/go.mod
+++ b/api/nfd/go.mod
@@ -7,23 +7,46 @@ require (
 	github.com/stretchr/testify v1.8.4
 	k8s.io/api v0.29.0
 	k8s.io/apimachinery v0.29.0
+	k8s.io/client-go v0.29.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
+	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
 	github.com/go-logr/logr v1.3.0 // indirect
+	github.com/go-openapi/jsonpointer v0.19.6 // indirect
+	github.com/go-openapi/jsonreference v0.20.2 // indirect
+	github.com/go-openapi/swag v0.22.3 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/google/gnostic-models v0.6.8 // indirect
+	github.com/google/go-cmp v0.6.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
+	github.com/google/uuid v1.5.0 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	golang.org/x/net v0.20.0 // indirect
+	golang.org/x/oauth2 v0.13.0 // indirect
+	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/term v0.16.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
+	golang.org/x/tools v0.17.0 // indirect
+	google.golang.org/appengine v1.6.8 // indirect
+	google.golang.org/protobuf v1.32.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	k8s.io/klog/v2 v2.110.1 // indirect
+	k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
 	k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
+	sigs.k8s.io/yaml v1.3.0 // indirect
 )
diff --git a/api/nfd/go.sum b/api/nfd/go.sum
index a51fc0e40..025f83221 100644
--- a/api/nfd/go.sum
+++ b/api/nfd/go.sum
@@ -1,29 +1,68 @@
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
+github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
+github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
 github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
+github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
+github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
+github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
+github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
+github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
+github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -31,40 +70,76 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
 golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
+golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
+golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
+golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
+google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -73,14 +148,19 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A=
 k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA=
 k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o=
 k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis=
+k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8=
+k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38=
 k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
 k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
+k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
+k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
 k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
 k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
diff --git a/api/nfd/v1alpha1/annotations_labels.go b/api/nfd/v1alpha1/annotations_labels.go
index b1f848245..a447f1dcd 100644
--- a/api/nfd/v1alpha1/annotations_labels.go
+++ b/api/nfd/v1alpha1/annotations_labels.go
@@ -74,4 +74,7 @@ const (
 
 	// FeatureAnnotationSubNsSuffix is the suffix for allowed feature annotation sub-namespaces.
 	FeatureAnnotationSubNsSuffix = "." + FeatureAnnotationNs
+
+	// FeatureAnnotationValueSizeLimit is the maximum allowed length for the value of a feature annotation.
+	FeatureAnnotationValueSizeLimit = 1 << 10
 )
diff --git a/api/nfd/v1alpha1/zz_generated.deepcopy.go b/api/nfd/v1alpha1/zz_generated.deepcopy.go
index 94748d2ad..d4d4cc5de 100644
--- a/api/nfd/v1alpha1/zz_generated.deepcopy.go
+++ b/api/nfd/v1alpha1/zz_generated.deepcopy.go
@@ -1,29 +1,12 @@
 //go:build !ignore_autogenerated
-// +build !ignore_autogenerated
 
-/*
-Copyright 2024 The Kubernetes Authors.
-
-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.
-*/
-
-// Code generated by deepcopy-gen. DO NOT EDIT.
+// Code generated by controller-gen. DO NOT EDIT.
 
 package v1alpha1
 
 import (
-	v1 "k8s.io/api/core/v1"
-	runtime "k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 )
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@@ -36,7 +19,6 @@ func (in *AttributeFeatureSet) DeepCopyInto(out *AttributeFeatureSet) {
 			(*out)[key] = val
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AttributeFeatureSet.
@@ -57,7 +39,6 @@ func (in FeatureMatcher) DeepCopyInto(out *FeatureMatcher) {
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
-		return
 	}
 }
 
@@ -85,7 +66,8 @@ func (in *FeatureMatcherTerm) DeepCopyInto(out *FeatureMatcherTerm) {
 				if val == nil {
 					(*out)[key] = nil
 				} else {
-					in, out := &val, &outVal
+					inVal := (*in)[key]
+					in, out := &inVal, &outVal
 					*out = new(MatchExpression)
 					(*in).DeepCopyInto(*out)
 				}
@@ -98,7 +80,6 @@ func (in *FeatureMatcherTerm) DeepCopyInto(out *FeatureMatcherTerm) {
 		*out = new(MatchExpression)
 		(*in).DeepCopyInto(*out)
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureMatcherTerm.
@@ -135,7 +116,6 @@ func (in *Features) DeepCopyInto(out *Features) {
 			(*out)[key] = *val.DeepCopy()
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Features.
@@ -158,7 +138,6 @@ func (in *FlagFeatureSet) DeepCopyInto(out *FlagFeatureSet) {
 			(*out)[key] = val
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlagFeatureSet.
@@ -181,7 +160,6 @@ func (in *InstanceFeature) DeepCopyInto(out *InstanceFeature) {
 			(*out)[key] = val
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceFeature.
@@ -204,7 +182,6 @@ func (in *InstanceFeatureSet) DeepCopyInto(out *InstanceFeatureSet) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceFeatureSet.
@@ -227,7 +204,6 @@ func (in *MatchAnyElem) DeepCopyInto(out *MatchAnyElem) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchAnyElem.
@@ -248,7 +224,6 @@ func (in *MatchExpression) DeepCopyInto(out *MatchExpression) {
 		*out = make(MatchValue, len(*in))
 		copy(*out, *in)
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchExpression.
@@ -271,13 +246,13 @@ func (in MatchExpressionSet) DeepCopyInto(out *MatchExpressionSet) {
 			if val == nil {
 				(*out)[key] = nil
 			} else {
-				in, out := &val, &outVal
+				inVal := (*in)[key]
+				in, out := &inVal, &outVal
 				*out = new(MatchExpression)
 				(*in).DeepCopyInto(*out)
 			}
 			(*out)[key] = outVal
 		}
-		return
 	}
 }
 
@@ -297,7 +272,6 @@ func (in MatchValue) DeepCopyInto(out *MatchValue) {
 		in := &in
 		*out = make(MatchValue, len(*in))
 		copy(*out, *in)
-		return
 	}
 }
 
@@ -314,7 +288,6 @@ func (in MatchValue) DeepCopy() MatchValue {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Nil) DeepCopyInto(out *Nil) {
 	*out = *in
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Nil.
@@ -333,7 +306,6 @@ func (in *NodeFeature) DeepCopyInto(out *NodeFeature) {
 	out.TypeMeta = in.TypeMeta
 	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
 	in.Spec.DeepCopyInto(&out.Spec)
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeature.
@@ -366,7 +338,6 @@ func (in *NodeFeatureList) DeepCopyInto(out *NodeFeatureList) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureList.
@@ -393,7 +364,6 @@ func (in *NodeFeatureRule) DeepCopyInto(out *NodeFeatureRule) {
 	out.TypeMeta = in.TypeMeta
 	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
 	in.Spec.DeepCopyInto(&out.Spec)
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureRule.
@@ -426,7 +396,6 @@ func (in *NodeFeatureRuleList) DeepCopyInto(out *NodeFeatureRuleList) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureRuleList.
@@ -457,7 +426,6 @@ func (in *NodeFeatureRuleSpec) DeepCopyInto(out *NodeFeatureRuleSpec) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureRuleSpec.
@@ -481,7 +449,6 @@ func (in *NodeFeatureSpec) DeepCopyInto(out *NodeFeatureSpec) {
 			(*out)[key] = val
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureSpec.
@@ -546,7 +513,6 @@ func (in *Rule) DeepCopyInto(out *Rule) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	return
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule.
diff --git a/hack/update_codegen.sh b/hack/update_codegen.sh
index bcd697d9f..052cb6551 100755
--- a/hack/update_codegen.sh
+++ b/hack/update_codegen.sh
@@ -18,55 +18,51 @@ set -o errexit
 set -o nounset
 set -o pipefail
 
-TMP_VENDOR_DIR=gen-vendor
-
-function cleanup() {
-  echo "Cleaning up..."
-  rm -rf ${TMP_VENDOR_DIR}
-  rm sigs.k8s.io
-  # We need to clean up the go.mod file since code-generator adds temporary library to the go.mod file.
-  "${GO_CMD}" mod tidy
-}
-
-trap cleanup EXIT
 GO_CMD=${1:-go}
 NFD_ROOT=$(realpath $(dirname ${BASH_SOURCE[0]})/..)
 
-"${GO_CMD}" mod vendor
-
-# Go generate
-"${GO_CMD}" generate ./cmd/... ./pkg/... ./source/...
-
-# Generate CRDs
-controller-gen object crd output:crd:stdout paths=./api/... > deployment/base/nfd-crds/nfd-api-crds.yaml
-mkdir -p deployment/helm/node-feature-discovery/crds
-cp deployment/base/nfd-crds/nfd-api-crds.yaml deployment/helm/node-feature-discovery/crds
+go mod vendor
 
-# Generate clientset and informers
-mv vendor ${TMP_VENDOR_DIR}
-CODEGEN_PKG=${CODEGEN_PKG:-$(ls -d -1 ./${TMP_VENDOR_DIR}/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
+CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${NFD_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
 
 cd $(dirname ${BASH_SOURCE[0]})/..
 
 source "${CODEGEN_PKG}/kube_codegen.sh"
 
-# TODO: https://github.com/kubernetes/code-generator/issues/165 has been closed
-# we need to bump the version of code-generator to v0.30.0 once it's released
+# TODO: remove the workaround when the issue is solved in the code-generator
+# (https://github.com/kubernetes/code-generator/issues/165).
+# Here, we create the soft link named "sigs.k8s.io" to the parent directory of
+# node-feature-discovery to ensure the layout required by the kube_codegen.sh script.
 ln -s .. sigs.k8s.io
+trap "rm sigs.k8s.io" EXIT
+
+CODEGEN_PKG=${CODEGEN_PKG:-$(ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
+
+source "${CODEGEN_PKG}/kube_codegen.sh"
+
+go generate ./cmd/... ./pkg/... ./source/...
+
+controller-gen object crd output:crd:stdout paths=./apis/... > deployment/base/nfd-crds/nfd-api-crds.yaml
+
+mkdir -p deployment/helm/node-feature-discovery/crds
+cp deployment/base/nfd-crds/nfd-api-crds.yaml deployment/helm/node-feature-discovery/crds
 
 # Generating conversion and defaults functions
 kube::codegen::gen_helpers \
-  --input-pkg-root sigs.k8s.io/node-feature-discovery/api/nfd \
+  --input-pkg-root sigs.k8s.io/node-feature-discovery/apis \
   --output-base "${NFD_ROOT}" \
   --boilerplate ${NFD_ROOT}/hack/boilerplate.go.txt
 
-# HACK: manually patching the auto-generated code as code-generator cannot
-# properly handle deepcopy of MatchExpressionSet.
-sed s'/out = new(map\[string\]\*MatchExpression)/out = new(MatchExpressionSet)/' -i api/nfd/v1alpha1/zz_generated.deepcopy.go
-
 kube::codegen::gen_client \
-  --input-pkg-root sigs.k8s.io/node-feature-discovery/api \
-  --output-pkg-root sigs.k8s.io/node-feature-discovery/api/generated \
+  --input-pkg-root sigs.k8s.io/node-feature-discovery/apis \
+  --output-pkg-root sigs.k8s.io/node-feature-discovery/generated \
   --output-base "${NFD_ROOT}" \
   --boilerplate ${NFD_ROOT}/hack/boilerplate.go.txt \
   --with-watch
+
+# HACK: manually patching the auto-generated code as code-generator cannot
+# properly handle deepcopy of MatchExpressionSet.
+sed s'/out = new(map\[string\]\*MatchExpression)/out = new(MatchExpressionSet)/' -i apis/nfd/v1alpha1/zz_generated.deepcopy.go
+
+# We need to clean up the go.mod file since code-generator adds temporary library to the go.mod file.
+"${GO_CMD}" mod tidy
\ No newline at end of file
diff --git a/pkg/apis/nfd/validate/validate.go b/pkg/apis/nfd/validate/validate.go
index 4f73b454f..daa65bb02 100644
--- a/pkg/apis/nfd/validate/validate.go
+++ b/pkg/apis/nfd/validate/validate.go
@@ -57,7 +57,7 @@ func MatchFeatures(matchFeature nfdv1alpha1.FeatureMatcher) []error {
 	var validationErr []error
 
 	for _, match := range matchFeature {
-		nameSplit := strings.SplitN(match.Feature, ".", 2)
+		nameSplit := strings.Split(match.Feature, ".")
 		if len(nameSplit) != 2 {
 			validationErr = append(validationErr, fmt.Errorf("invalid feature name %v (not <domain>.<feature>), cannot be used for templating", match.Feature))
 		}
@@ -155,8 +155,8 @@ func Annotation(key, value string) error {
 	}
 
 	// Validate annotation value
-	if errs := k8svalidation.IsValidLabelValue(value); len(errs) > 0 {
-		return fmt.Errorf("invalid value %q: %s", value, strings.Join(errs, "; "))
+	if len(value) > nfdv1alpha1.FeatureAnnotationValueSizeLimit {
+		return fmt.Errorf("invalid value: too long: feature annotations must not be longer than %d characters", nfdv1alpha1.FeatureAnnotationValueSizeLimit)
 	}
 
 	return nil
diff --git a/pkg/labeler/labeler.pb.go b/pkg/labeler/labeler.pb.go
index 742f4a77e..23469ee7d 100644
--- a/pkg/labeler/labeler.pb.go
+++ b/pkg/labeler/labeler.pb.go
@@ -158,37 +158,35 @@ var File_labeler_proto protoreflect.FileDescriptor
 
 var file_labeler_proto_rawDesc = []byte{
 	0x0a, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-	0x07, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x1a, 0x43, 0x73, 0x69, 0x67, 0x73, 0x2e, 0x6b,
-	0x38, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2d, 0x66, 0x65, 0x61, 0x74, 0x75,
-	0x72, 0x65, 0x2d, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69,
-	0x2f, 0x6e, 0x66, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x67, 0x65,
-	0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfa, 0x01,
-	0x0a, 0x10, 0x53, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x66, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
-	0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x66, 0x64, 0x56, 0x65, 0x72, 0x73,
-	0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65,
-	0x12, 0x3d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x25, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x61,
-	0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65,
-	0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12,
-	0x2e, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x65, 0x61,
-	0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a,
-	0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
-	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
-	0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x10, 0x0a, 0x0e, 0x53, 0x65,
-	0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0x4c, 0x0a, 0x07,
-	0x4c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x4c, 0x61,
-	0x62, 0x65, 0x6c, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x2e, 0x53,
-	0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x17, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x61, 0x62,
-	0x65, 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x30, 0x5a, 0x2e, 0x73, 0x69,
-	0x67, 0x73, 0x2e, 0x6b, 0x38, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2d, 0x66,
-	0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79,
-	0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x33,
+	0x07, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x1a, 0x25, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70,
+	0x69, 0x73, 0x2f, 0x6e, 0x66, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f,
+	0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
+	0xfa, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x66, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73,
+	0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x66, 0x64, 0x56, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61,
+	0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74,
+	0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61,
+	0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c,
+	0x73, 0x12, 0x2e, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46,
+	0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
+	0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
+	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
+	0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x10, 0x0a, 0x0e,
+	0x53, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0x4c,
+	0x0a, 0x07, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x09, 0x53, 0x65, 0x74,
+	0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72,
+	0x2e, 0x53, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x4c,
+	0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x30, 0x5a, 0x2e,
+	0x73, 0x69, 0x67, 0x73, 0x2e, 0x6b, 0x38, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x6e, 0x6f, 0x64, 0x65,
+	0x2d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65,
+	0x72, 0x79, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x65, 0x72, 0x62, 0x06,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/pkg/labeler/labeler.proto b/pkg/labeler/labeler.proto
index 57cd8daaa..10630d229 100644
--- a/pkg/labeler/labeler.proto
+++ b/pkg/labeler/labeler.proto
@@ -18,7 +18,7 @@ syntax = "proto3";
 
 option go_package = "sigs.k8s.io/node-feature-discovery/pkg/labeler";
 
-import "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1/generated.proto";
+import "pkg/apis/nfd/v1alpha1/generated.proto";
 
 package labeler;
 


Test patch:
diff --git a/pkg/apis/nfd/validate/validate_test.go b/pkg/apis/nfd/validate/validate_test.go
index db49a805f..7786019a0 100644
--- a/pkg/apis/nfd/validate/validate_test.go
+++ b/pkg/apis/nfd/validate/validate_test.go
@@ -1,10 +1,28 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+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 validate
 
 import (
+	"fmt"
+	"sort"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
+	nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
 )
 
 func TestAnnotation(t *testing.T) {
@@ -22,15 +40,21 @@ func TestAnnotation(t *testing.T) {
 		},
 		{
 			name:  "Invalid annotation key",
-			key:   "invalid-key",
+			key:   "_invalid-key_",
+			value: "true",
+			want:  "invalid annotation key \"_invalid-key_\":",
+		},
+		{
+			name:  "Denied annotation key",
+			key:   "denied-key",
 			value: "true",
 			want:  ErrUnprefixedKeysNotAllowed,
 		},
 		{
 			name:  "Invalid annotation value",
 			key:   "feature.node.kubernetes.io/feature",
-			value: "invalid value",
-			want:  "invalid value \"invalid value\": ",
+			value: string(make([]byte, 1100)),
+			want:  "invalid value: too long:",
 		},
 		{
 			name:  "Denied annotation key",
@@ -52,6 +76,49 @@ func TestAnnotation(t *testing.T) {
 	}
 }
 
+func TestAnnotations(t *testing.T) {
+	tests := []struct {
+		name        string
+		annotations map[string]string
+		want        []error
+	}{
+		{
+			name:        "Empty annotations",
+			annotations: map[string]string{},
+			want:        nil,
+		},
+		{
+			name: "Valid annotations",
+			annotations: map[string]string{
+				"feature.node.kubernetes.io/annotation": "true",
+				"vendor.io/annotation":                  "true",
+			},
+			want: nil,
+		},
+		{
+			name: "Invalid annotations",
+			annotations: map[string]string{
+				"invalid-key":              "true",
+				"kubernetes.io/annotation": "true",
+			},
+			want: []error{
+				ErrUnprefixedKeysNotAllowed,
+				ErrNSNotAllowed,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			errs := sortErrors(Annotations(tt.annotations))
+			assert.Equal(t, len(tt.want), len(errs))
+			for i := range errs {
+				assert.ErrorIs(t, errs[i], tt.want[i])
+			}
+		})
+	}
+}
+
 func TestTaint(t *testing.T) {
 	tests := []struct {
 		name  string
@@ -113,6 +180,71 @@ func TestTaint(t *testing.T) {
 	}
 }
 
+func TestTaints(t *testing.T) {
+	tests := []struct {
+		name   string
+		taints []corev1.Taint
+		want   []error
+	}{
+		{
+			name:   "Empty taints",
+			taints: []corev1.Taint{},
+			want:   nil,
+		},
+		{
+			name: "Valid taints",
+			taints: []corev1.Taint{
+				{
+					Key:    "feature.node.kubernetes.io/taint",
+					Value:  "true",
+					Effect: corev1.TaintEffectNoSchedule,
+				},
+				{
+					Key:    "vendor.io/taint",
+					Value:  "true",
+					Effect: corev1.TaintEffectNoExecute,
+				},
+			},
+			want: nil,
+		},
+		{
+			name: "Invalid taints",
+			taints: []corev1.Taint{
+				{
+					Key:    "invalid-key",
+					Value:  "true",
+					Effect: corev1.TaintEffectNoSchedule,
+				},
+				{
+					Key:    "feature.node.kubernetes.io/taint",
+					Value:  "true",
+					Effect: "",
+				},
+				{
+					Key:    "feature.node.kubernetes.io/taint",
+					Value:  "true",
+					Effect: "invalid-effect",
+				},
+			},
+			want: []error{
+				ErrUnprefixedKeysNotAllowed,
+				ErrEmptyTaintEffect,
+				ErrInvalidTaintEffect,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			errs := Taints(tt.taints)
+			assert.Equal(t, len(tt.want), len(errs))
+			for i := range errs {
+				assert.ErrorIs(t, errs[i], tt.want[i])
+			}
+		})
+	}
+}
+
 func TestLabel(t *testing.T) {
 	tests := []struct {
 		name  string
@@ -132,6 +264,12 @@ func TestLabel(t *testing.T) {
 			value: "true",
 			want:  nil,
 		},
+		{
+			name:  "Invalid label key",
+			key:   "invalid-key:",
+			value: "true",
+			want:  "invalid label key \"invalid-key:\": ",
+		},
 		{
 			name:  "Denied label with prefix",
 			key:   "kubernetes.io/label",
@@ -139,8 +277,8 @@ func TestLabel(t *testing.T) {
 			want:  ErrNSNotAllowed,
 		},
 		{
-			name:  "Invalid label key",
-			key:   "invalid-key",
+			name:  "Denied label key unprefixed",
+			key:   "denied-key",
 			value: "true",
 			want:  ErrUnprefixedKeysNotAllowed,
 		},
@@ -170,6 +308,49 @@ func TestLabel(t *testing.T) {
 	}
 }
 
+func TestLabels(t *testing.T) {
+	tests := []struct {
+		name   string
+		labels map[string]string
+		want   []error
+	}{
+		{
+			name:   "Empty labels",
+			labels: map[string]string{},
+			want:   nil,
+		},
+		{
+			name: "Valid labels",
+			labels: map[string]string{
+				"feature.node.kubernetes.io/label": "true",
+				"vendor.io/label":                  "true",
+			},
+			want: nil,
+		},
+		{
+			name: "Invalid labels",
+			labels: map[string]string{
+				"invalid-key":         "true",
+				"kubernetes.io/label": "true",
+			},
+			want: []error{
+				ErrUnprefixedKeysNotAllowed,
+				ErrNSNotAllowed,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := sortErrors(Labels(tt.labels))
+			assert.Equal(t, len(tt.want), len(err))
+			for i := range err {
+				assert.ErrorIs(t, err[i], tt.want[i])
+			}
+		})
+	}
+}
+
 func TestExtendedResource(t *testing.T) {
 	tests := []struct {
 		name  string
@@ -184,8 +365,14 @@ func TestExtendedResource(t *testing.T) {
 			want:  nil,
 		},
 		{
-			name:  "Invalid extended resource key",
-			key:   "invalid-key",
+			name:  "Invalid extended resource name",
+			key:   "invalid-name~",
+			value: "123",
+			want:  "invalid name \"invalid-name~\": ",
+		},
+		{
+			name:  "Denied extended resource key",
+			key:   "denied-key",
 			value: "123",
 			want:  ErrUnprefixedKeysNotAllowed,
 		},
@@ -214,3 +401,211 @@ func TestExtendedResource(t *testing.T) {
 		})
 	}
 }
+
+func TestExtendedResources(t *testing.T) {
+	tests := []struct {
+		name              string
+		extendedResources map[string]string
+		want              []error
+	}{
+		{
+			name:              "Empty extended resources",
+			extendedResources: map[string]string{},
+			want:              nil,
+		},
+		{
+			name: "Valid extended resources",
+			extendedResources: map[string]string{
+				"feature.node.kubernetes.io/extended-resource": "123",
+				"vendor.io/extended-resource":                  "456",
+			},
+			want: nil,
+		},
+		{
+			name: "Invalid extended resources",
+			extendedResources: map[string]string{
+				"invalid-key":                     "456",
+				"kubernetes.io/extended-resource": "123",
+			},
+			want: []error{
+				ErrUnprefixedKeysNotAllowed,
+				ErrNSNotAllowed,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			errs := sortErrors(ExtendedResources(tt.extendedResources))
+			assert.Equal(t, len(tt.want), len(errs))
+			for i := range errs {
+				assert.ErrorIs(t, errs[i], tt.want[i])
+			}
+		})
+	}
+}
+
+func TestMatchFeatures(t *testing.T) {
+	tests := []struct {
+		name           string
+		matchFeature   nfdv1alpha1.FeatureMatcher
+		expectedErrors []error
+	}{
+		{
+			name:           "Empty matchFeature",
+			matchFeature:   nfdv1alpha1.FeatureMatcher{},
+			expectedErrors: nil,
+		},
+		{
+			name: "Valid matchFeature",
+			matchFeature: nfdv1alpha1.FeatureMatcher{
+				{
+					Feature: "domain1.feature1",
+				},
+				{
+					Feature: "domain2.feature2",
+				},
+			},
+			expectedErrors: nil,
+		},
+		{
+			name: "Invalid matchFeature",
+			matchFeature: nfdv1alpha1.FeatureMatcher{
+				{
+					Feature: "invalid-feature",
+				},
+				{
+					Feature: "domain3",
+				},
+				{
+					Feature: "prefix.domain.feature",
+				},
+			},
+			expectedErrors: []error{
+				fmt.Errorf("invalid feature name invalid-feature (not <domain>.<feature>), cannot be used for templating"),
+				fmt.Errorf("invalid feature name domain3 (not <domain>.<feature>), cannot be used for templating"),
+				fmt.Errorf("invalid feature name prefix.domain.feature (not <domain>.<feature>), cannot be used for templating"),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			errors := MatchFeatures(tt.matchFeature)
+			assert.Equal(t, tt.expectedErrors, errors)
+		})
+	}
+}
+
+func TestMatchAny(t *testing.T) {
+	tests := []struct {
+		name           string
+		matchAny       []nfdv1alpha1.MatchAnyElem
+		expectedErrors []error
+	}{
+		{
+			name:           "Empty matchAny",
+			matchAny:       []nfdv1alpha1.MatchAnyElem{},
+			expectedErrors: nil,
+		},
+		{
+			name: "Valid matchAny",
+			matchAny: []nfdv1alpha1.MatchAnyElem{
+				{
+					MatchFeatures: nfdv1alpha1.FeatureMatcher{
+						{
+							Feature: "domain1.feature1",
+						},
+						{
+							Feature: "domain2.feature2",
+						},
+					},
+				},
+				{
+					MatchFeatures: nfdv1alpha1.FeatureMatcher{
+						{
+							Feature: "domain3.feature3",
+						},
+						{
+							Feature: "domain4.feature4",
+						},
+					},
+				},
+			},
+			expectedErrors: nil,
+		},
+		{
+			name: "Invalid matchAny",
+			matchAny: []nfdv1alpha1.MatchAnyElem{
+				{
+					MatchFeatures: nfdv1alpha1.FeatureMatcher{
+						{
+							Feature: "invalid-feature",
+						},
+						{
+							Feature: "domain3",
+						},
+					},
+				},
+				{
+					MatchFeatures: nfdv1alpha1.FeatureMatcher{
+						{
+							Feature: "domain5.feature5",
+						},
+						{
+							Feature: "invalid.domain.feature6",
+						},
+					},
+				},
+			},
+			expectedErrors: []error{
+				fmt.Errorf("invalid feature name invalid-feature (not <domain>.<feature>), cannot be used for templating"),
+				fmt.Errorf("invalid feature name domain3 (not <domain>.<feature>), cannot be used for templating"),
+				fmt.Errorf("invalid feature name invalid.domain.feature6 (not <domain>.<feature>), cannot be used for templating"),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			errors := MatchAny(tt.matchAny)
+			assert.Equal(t, tt.expectedErrors, errors)
+		})
+	}
+}
+
+func TestTemplate(t *testing.T) {
+	tests := []struct {
+		name           string
+		labelsTemplate string
+		want           []error
+	}{
+		{
+			name:           "Valid template",
+			labelsTemplate: "key1=value1,key2=value2",
+			want:           nil,
+		},
+		{
+			name:           "Invalid template",
+			labelsTemplate: "{{.key1=value1,key2=value2}}",
+			want:           []error{fmt.Errorf("invalid template: template: :1: bad character U+003D '='")},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			errs := Template(tt.labelsTemplate)
+			assert.Equal(t, len(tt.want), len(errs))
+			for i := range errs {
+				assert.EqualError(t, errs[i], tt.want[i].Error())
+			}
+		})
+	}
+}
+
+func sortErrors(errs []error) []error {
+	sort.Slice(errs, func(i, j int) bool {
+		return errs[i].Error() < errs[j].Error()
+	})
+	return errs
+}