feat: move nfd to operator
diff --git a/atmosphere/flows.py b/atmosphere/flows.py
index f933700..81c223b 100644
--- a/atmosphere/flows.py
+++ b/atmosphere/flows.py
@@ -19,6 +19,14 @@
 HELM_REPOSITORY_PERCONA = "percona"
 HELM_REPOSITORY_PROMETHEUS_COMMUINTY = "prometheus-community"
 
+CONTROL_PLANE_NODE_SELECTOR = {
+    "openstack-control-plane": "enabled",
+}
+
+NODE_FEATURE_DISCOVERY_VALUES = {
+    "master": {"nodeSelector": CONTROL_PLANE_NODE_SELECTOR}
+}
+
 
 def get_deployment_flow():
     flow = graph_flow.Flow("deploy").add(
@@ -48,6 +56,14 @@
             name=HELM_REPOSITORY_NODE_FEATURE_DISCOVERY,
             url="https://kubernetes-sigs.github.io/node-feature-discovery/charts",
         ),
+        flux.CreateOrUpdateHelmReleaseTask(
+            namespace=NAMESPACE_MONITORING,
+            name="node-feature-discovery",
+            repository=HELM_REPOSITORY_NODE_FEATURE_DISCOVERY,
+            chart="node-feature-discovery",
+            version="0.11.2",
+            values=NODE_FEATURE_DISCOVERY_VALUES,
+        ),
         # openstack
         kubernetes.CreateOrUpdateNamespaceTask(name=NAMESPACE_OPENSTACK),
         flux.CreateOrUpdateHelmRepositoryTask(
diff --git a/atmosphere/tasks/flux.py b/atmosphere/tasks/flux.py
index e9b7f52..51da589 100644
--- a/atmosphere/tasks/flux.py
+++ b/atmosphere/tasks/flux.py
@@ -21,7 +21,7 @@
             requires=set(["namespace", "name", "url"]),
             inject={"name": name, "url": url},
             *args,
-            **kwargs
+            **kwargs,
         )
 
     def generate_object(
@@ -47,3 +47,81 @@
         self, resource: pykube.objects.APIObject, url: str, *args, **kwargs
     ):
         resource.obj["spec"]["url"] = url
+
+
+class HelmRelease(pykube.objects.NamespacedAPIObject):
+    version = "helm.toolkit.fluxcd.io/v2beta1"
+    endpoint = "helmreleases"
+    kind = "HelmRelease"
+
+
+class CreateOrUpdateHelmReleaseTask(kubernetes.CreateOrUpdateKubernetesObjectTask):
+    def __init__(
+        self,
+        namespace: str,
+        name: str,
+        repository: str,
+        chart: str,
+        version: str,
+        values: dict,
+        *args,
+        **kwargs,
+    ):
+        super().__init__(
+            HelmRelease,
+            namespace,
+            name,
+            requires=set(
+                ["namespace", "name", "repository", "chart", "version", "values"]
+            ),
+            rebind={"repository": f"helm-repository-{namespace}-{repository}"},
+            inject={
+                "name": name,
+                "repository": repository,
+                "chart": chart,
+                "version": version,
+                "values": values,
+            },
+            *args,
+            **kwargs,
+        )
+
+    def generate_object(
+        self,
+        namespace: pykube.Namespace,
+        name: str,
+        repository: HelmRepository,
+        chart: str,
+        version: str,
+        values: dict,
+        *args,
+        **kwargs,
+    ) -> HelmRelease:
+        return HelmRelease(
+            self.api,
+            {
+                "apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
+                "kind": "HelmRelease",
+                "metadata": {
+                    "name": name,
+                    "namespace": namespace.name,
+                },
+                "spec": {
+                    "interval": "60s",
+                    "chart": {
+                        "spec": {
+                            "chart": chart,
+                            "version": version,
+                            "sourceRef": {
+                                "kind": "HelmRepository",
+                                "name": repository.name,
+                            },
+                        }
+                    },
+                    "values": values,
+                },
+            },
+        )
+
+    def update_object(self, resource: HelmRelease, values: dict = {}, *args, **kwargs):
+        resource.obj["spec"]["values"] = values
diff --git a/playbooks/openstack.yml b/playbooks/openstack.yml
index 2f76ff9..98dc5eb 100644
--- a/playbooks/openstack.yml
+++ b/playbooks/openstack.yml
@@ -44,10 +44,6 @@
       tags:
         - prometheus-ethtool-exporter
 
-    - role: node_feature_discovery
-      tags:
-        - node-feature-discovery
-
     - role: ipmi_exporter
       tags:
         - ipmi-exporter
diff --git a/roles/node_feature_discovery/meta/main.yml b/roles/node_feature_discovery/meta/main.yml
deleted file mode 100644
index 9bd6201..0000000
--- a/roles/node_feature_discovery/meta/main.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
-#
-# 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.
-
-galaxy_info:
-  author: VEXXHOST, Inc.
-  description: Ansible role for node-feature-discovery
-  license: Apache-2.0
-  min_ansible_version: 5.5.0
-  standalone: false
-  platforms:
-    - name: Ubuntu
-      versions:
-        - focal
-
-dependencies:
-  - role: atmosphere
diff --git a/roles/node_feature_discovery/tasks/main.yml b/roles/node_feature_discovery/tasks/main.yml
deleted file mode 100644
index c613dfd..0000000
--- a/roles/node_feature_discovery/tasks/main.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
-#
-# 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.
-
-- name: Deploy Helm chart
-  kubernetes.core.k8s:
-    state: present
-    definition:
-      - apiVersion: helm.toolkit.fluxcd.io/v2beta1
-        kind: HelmRelease
-        metadata:
-          name: node-feature-discovery
-          namespace: monitoring
-        spec:
-          interval: 60s
-          chart:
-            spec:
-              chart: node-feature-discovery
-              version: 0.10.0
-              sourceRef:
-                kind: HelmRepository
-                name: node-feature-discovery
-          values:
-            image:
-              repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}/node-feature-discovery"
-              tag: 0.10.0
-            master:
-              nodeSelector:
-                openstack-control-plane: enabled