fix: add ansible-based ingress
diff --git a/atmosphere/cmd/operator.py b/atmosphere/cmd/operator.py
index f1e931b..ca8aeb1 100644
--- a/atmosphere/cmd/operator.py
+++ b/atmosphere/cmd/operator.py
@@ -43,34 +43,3 @@
         ),
         spec=types.OpenstackHelmRabbitmqClusterSpec(**spec),
     ).delete_rabbitmq_cluster()
-
-
-@kopf.on.create(constants.API_VERSION_ATMOSPHERE, constants.KIND_OPENSTACK_HELM_INGRESS)
-@kopf.on.resume(constants.API_VERSION_ATMOSPHERE, constants.KIND_OPENSTACK_HELM_INGRESS)
-def create_openstack_helm_ingress(
-    namespace: str, name: str, annotations: dict, labels: dict, spec: dict, **_
-):
-    api = clients.get_pykube_api()
-    objects.OpenstackHelmIngress(
-        api=api,
-        metadata=types.OpenstackHelmIngressObjectMeta(
-            name=name,
-            namespace=namespace,
-            annotations=utils.filter_annotations(annotations),
-            labels=labels,
-        ),
-        spec=types.OpenstackHelmIngressSpec(**spec),
-    ).apply_ingress()
-
-
-@kopf.on.delete(constants.API_VERSION_ATMOSPHERE, constants.KIND_OPENSTACK_HELM_INGRESS)
-def delete_openstack_helm_ingress(namespace: str, name: str, spec: dict, **_):
-    api = clients.get_pykube_api()
-    objects.OpenstackHelmIngress(
-        api=api,
-        metadata=types.OpenstackHelmIngressObjectMeta(
-            name=name,
-            namespace=namespace,
-        ),
-        spec=types.OpenstackHelmIngressSpec(**spec),
-    ).delete_ingress()
diff --git a/atmosphere/operator/api/objects.py b/atmosphere/operator/api/objects.py
index d668b00..0ef37b0 100644
--- a/atmosphere/operator/api/objects.py
+++ b/atmosphere/operator/api/objects.py
@@ -104,130 +104,3 @@
         ).get_or_none(name=f"rabbitmq-{self.metadata.name}")
         if rabbitmq_cluster:
             rabbitmq_cluster.delete()
-
-
-class OpenstackHelmIngress(types.NamespacedKubernetesObject):
-    endpoint: ClassVar[str] = "openstackhelmingresses"
-
-    kind: str = Field(constants.KIND_OPENSTACK_HELM_INGRESS, const=True)
-    metadata: types.OpenstackHelmIngressObjectMeta
-    spec: types.OpenstackHelmIngressSpec
-
-    ENDPOINT_TO_SERVICE_MAPPING: ClassVar[dict] = {
-        types.OpenstackHelmIngressObjectMetaName.cloudformation: types.IngressServiceBackend(
-            name="heat-cfn",
-            port=types.ServiceBackendPort(number=8000),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.clustering: types.IngressServiceBackend(
-            name="senlin-api",
-            port=types.ServiceBackendPort(number=8778),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.compute: types.IngressServiceBackend(
-            name="nova-api",
-            port=types.ServiceBackendPort(number=8774),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.compute_novnc_proxy: types.IngressServiceBackend(
-            name="nova-novncproxy",
-            port=types.ServiceBackendPort(number=6080),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.container_infra: types.IngressServiceBackend(
-            name="magnum-api",
-            port=types.ServiceBackendPort(number=9511),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.container_infra_registry: types.IngressServiceBackend(
-            name="magnum-registry",
-            port=types.ServiceBackendPort(number=5000),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.dashboard: types.IngressServiceBackend(
-            name="horizon-int",
-            port=types.ServiceBackendPort(number=80),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.identity: types.IngressServiceBackend(
-            name="keystone-api",
-            port=types.ServiceBackendPort(number=5000),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.image: types.IngressServiceBackend(
-            name="glance-api",
-            port=types.ServiceBackendPort(number=9292),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.key_manager: types.IngressServiceBackend(
-            name="barbican-api",
-            port=types.ServiceBackendPort(number=9311),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.load_balancer: types.IngressServiceBackend(
-            name="octavia-api",
-            port=types.ServiceBackendPort(number=9876),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.network: types.IngressServiceBackend(
-            name="neutron-server",
-            port=types.ServiceBackendPort(number=9696),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.orchestration: types.IngressServiceBackend(
-            name="heat-api",
-            port=types.ServiceBackendPort(number=8004),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.placement: types.IngressServiceBackend(
-            name="placement-api",
-            port=types.ServiceBackendPort(number=8778),
-        ),
-        types.OpenstackHelmIngressObjectMetaName.volumev3: types.IngressServiceBackend(
-            name="cinder-api",
-            port=types.ServiceBackendPort(number=8776),
-        ),
-    }
-
-    @property
-    def service(self):
-        return self.ENDPOINT_TO_SERVICE_MAPPING[self.metadata.name]
-
-    def apply_ingress(self) -> Ingress:
-        return Ingress(
-            self.api,
-            {
-                "apiVersion": Ingress.version,
-                "kind": Ingress.kind,
-                "metadata": {
-                    "name": self.metadata.name,
-                    "namespace": self.metadata.namespace,
-                    "labels": self.metadata.labels,
-                    "annotations": {
-                        **{
-                            "cert-manager.io/cluster-issuer": self.spec.clusterIssuer,
-                        },
-                        **self.metadata.annotations,
-                    },
-                },
-                "spec": {
-                    "ingressClassName": self.spec.ingressClassName,
-                    "rules": [
-                        {
-                            "host": self.spec.host,
-                            "http": {
-                                "paths": [
-                                    {
-                                        "path": "/",
-                                        "pathType": "Prefix",
-                                        "backend": {
-                                            "service": self.service.dict(),
-                                        },
-                                    },
-                                ],
-                            },
-                        },
-                    ],
-                    "tls": [
-                        {
-                            "secretName": f"{self.service.name}-certs",
-                            "hosts": [self.spec.host],
-                        }
-                    ],
-                },
-            },
-        ).apply()
-
-    def delete_ingress(self) -> None:
-        ingress = Ingress.objects(
-            self.api, namespace=self.metadata.namespace
-        ).get_or_none(name=self.metadata.name)
-        if ingress:
-            ingress.delete()
diff --git a/atmosphere/operator/api/types.py b/atmosphere/operator/api/types.py
index 1a44267..1df4516 100644
--- a/atmosphere/operator/api/types.py
+++ b/atmosphere/operator/api/types.py
@@ -177,31 +177,3 @@
 
 class OpenstackHelmRabbitmqClusterSpec(pydantic.BaseModel):
     image: pydantic.constr(min_length=1)
-
-
-class OpenstackHelmIngressObjectMetaName(str, Enum):
-    cloudformation = "cloudformation"
-    clustering = "clustering"
-    compute = "compute"
-    compute_novnc_proxy = "compute-novnc-proxy"
-    container_infra = "container-infra"
-    container_infra_registry = "container-infra-registry"
-    dashboard = "dashboard"
-    identity = "identity"
-    image = "image"
-    key_manager = "key-manager"
-    load_balancer = "load-balancer"
-    network = "network"
-    orchestration = "orchestration"
-    placement = "placement"
-    volumev3 = "volumev3"
-
-
-class OpenstackHelmIngressObjectMeta(NamespacedObjectMeta):
-    name: OpenstackHelmIngressObjectMetaName
-
-
-class OpenstackHelmIngressSpec(pydantic.BaseModel):
-    clusterIssuer: pydantic.constr(min_length=1) = "atmosphere"
-    ingressClassName: pydantic.constr(min_length=1) = "atmosphere"
-    host: Hostname
diff --git a/atmosphere/operator/constants.py b/atmosphere/operator/constants.py
index 3fd102d..8aa2b71 100644
--- a/atmosphere/operator/constants.py
+++ b/atmosphere/operator/constants.py
@@ -1,7 +1,6 @@
 API_VERSION_ATMOSPHERE = "atmosphere.vexxhost.com/v1alpha1"
 
 KIND_OPENSTACK_HELM_RABBITMQ_CLUSTER = "OpenstackHelmRabbitmqCluster"
-KIND_OPENSTACK_HELM_INGRESS = "OpenstackHelmIngress"
 
 IMAGE_LIST = {
     "alertmanager": "quay.io/prometheus/alertmanager:v0.24.0",
diff --git a/atmosphere/operator/controllers/cloud.py b/atmosphere/operator/controllers/cloud.py
index 0ea5dc2..a5f06ec 100644
--- a/atmosphere/operator/controllers/cloud.py
+++ b/atmosphere/operator/controllers/cloud.py
@@ -71,18 +71,6 @@
                 },
             ),
         )
-        objects.OpenstackHelmIngress(
-            api=api,
-            metadata=types.OpenstackHelmIngressObjectMeta(
-                name="container-infra",
-                namespace=namespace,
-            ),
-            spec=types.OpenstackHelmIngressSpec(
-                clusterIssuer=spec["certManagerClusterIssuer"],
-                ingressClassName=spec["ingressClassName"],
-                host=spec["magnum"]["endpoint"],
-            ),
-        ).apply()
 
         objects.OpenstackHelmRabbitmqCluster(
             api=api,
diff --git a/atmosphere/tests/unit/operator/test_objects.py b/atmosphere/tests/unit/operator/test_objects.py
index a2fbbcd..ad682e7 100644
--- a/atmosphere/tests/unit/operator/test_objects.py
+++ b/atmosphere/tests/unit/operator/test_objects.py
@@ -1,6 +1,5 @@
 import json
 
-import pytest
 import responses
 from hypothesis import given
 from hypothesis import provisional as prov
@@ -407,208 +406,3 @@
 
             instance.delete_rabbitmq_cluster()
             assert len(rsps.calls) == 1
-
-
-class TestOpenstackHelmIngress:
-    @given(st.builds(objects.OpenstackHelmIngress))
-    def test_property(self, instance):
-        assert isinstance(instance, objects.OpenstackHelmIngress)
-        assert isinstance(instance.metadata, types.OpenstackHelmIngressObjectMeta)
-        assert isinstance(instance.spec, types.OpenstackHelmIngressSpec)
-
-    def test_endpont_to_service_mapping_order(self):
-        assert [*objects.OpenstackHelmIngress.ENDPOINT_TO_SERVICE_MAPPING] == sorted(
-            [*objects.OpenstackHelmIngress.ENDPOINT_TO_SERVICE_MAPPING]
-        )
-
-    @pytest.mark.parametrize("name", types.OpenstackHelmIngressObjectMetaName)
-    def test_service(self, name):
-        instance = objects.OpenstackHelmIngress(
-            metadata=types.OpenstackHelmIngressObjectMeta(
-                name=name,
-                namespace="default",
-            ),
-            spec=types.OpenstackHelmIngressSpec(
-                host=f"{name}.example.com",
-            ),
-        )
-
-        assert instance.service is not None
-
-    @pytest.mark.parametrize("name", types.OpenstackHelmIngressObjectMetaName)
-    def test_apply(self, api, requests_mock, name):
-        instance = objects.OpenstackHelmIngress(
-            api=api,
-            metadata=types.OpenstackHelmIngressObjectMeta(
-                name=name,
-                namespace="default",
-                annotations={
-                    "annotate": "this",
-                },
-                labels={
-                    "foo": "bar",
-                },
-            ),
-            spec=types.OpenstackHelmIngressSpec(
-                host=f"{name}.example.com",
-            ),
-        )
-
-        with requests_mock as rsps:
-            rsps.add(
-                responses.PATCH,
-                f"https://localhost:9443/apis/atmosphere.vexxhost.com/v1alpha1/namespaces/default/openstackhelmingresses/{name}?fieldManager=atmosphere-operator&force=True",  # noqa
-                json={},
-            )
-
-            instance.apply()
-
-            assert len(rsps.calls) == 1
-            assert json.loads(rsps.calls[0].request.body) == {
-                "apiVersion": "atmosphere.vexxhost.com/v1alpha1",
-                "kind": "OpenstackHelmIngress",
-                "metadata": {
-                    "name": name,
-                    "namespace": "default",
-                    "annotations": {
-                        "annotate": "this",
-                    },
-                    "labels": {
-                        "foo": "bar",
-                    },
-                },
-                "spec": {
-                    "host": f"{name}.example.com",
-                    "clusterIssuer": "atmosphere",
-                    "ingressClassName": "atmosphere",
-                },
-            }
-
-    @pytest.mark.parametrize("name", types.OpenstackHelmIngressObjectMetaName)
-    def test_apply_ingress(self, api, requests_mock, name):
-        instance = objects.OpenstackHelmIngress(
-            api=api,
-            metadata=types.OpenstackHelmIngressObjectMeta(
-                name=name,
-                namespace="default",
-                annotations={
-                    "annotate": "this",
-                },
-                labels={
-                    "foo": "bar",
-                },
-            ),
-            spec=types.OpenstackHelmIngressSpec(
-                host=f"{name}.example.com",
-            ),
-        )
-
-        with requests_mock as rsps:
-            rsps.add(
-                responses.PATCH,
-                f"https://localhost:9443/apis/networking.k8s.io/v1/namespaces/default/ingresses/{name}?fieldManager=atmosphere-operator&force=True",  # noqa
-                json={},
-            )
-
-            instance.apply_ingress()
-
-            assert len(rsps.calls) == 1
-            assert json.loads(rsps.calls[0].request.body) == {
-                "apiVersion": "networking.k8s.io/v1",
-                "kind": "Ingress",
-                "metadata": {
-                    "name": name,
-                    "namespace": "default",
-                    "labels": {
-                        "foo": "bar",
-                    },
-                    "annotations": {
-                        "annotate": "this",
-                        "cert-manager.io/cluster-issuer": "atmosphere",
-                    },
-                },
-                "spec": {
-                    "ingressClassName": "atmosphere",
-                    "rules": [
-                        {
-                            "host": f"{name}.example.com",
-                            "http": {
-                                "paths": [
-                                    {
-                                        "path": "/",
-                                        "pathType": "Prefix",
-                                        "backend": {
-                                            "service": {
-                                                "name": instance.service.name,
-                                                "port": {
-                                                    "number": instance.service.port.number,
-                                                },
-                                            },
-                                        },
-                                    },
-                                ],
-                            },
-                        },
-                    ],
-                    "tls": [
-                        {
-                            "secretName": f"{instance.service.name}-certs",
-                            "hosts": [f"{name}.example.com"],
-                        }
-                    ],
-                },
-            }
-
-    @pytest.mark.parametrize("name", types.OpenstackHelmIngressObjectMetaName)
-    def test_delete_ingress(self, api, requests_mock, name):
-        instance = objects.OpenstackHelmIngress(
-            api=api,
-            metadata=types.OpenstackHelmIngressObjectMeta(
-                name=name,
-                namespace="default",
-            ),
-            spec=types.OpenstackHelmIngressSpec(
-                host=f"{name}.example.com",
-            ),
-        )
-
-        with requests_mock as rsps:
-            rsps.add(
-                responses.GET,
-                f"https://localhost:9443/apis/networking.k8s.io/v1/namespaces/default/ingresses/{name}",  # noqa
-                json={
-                    "metadata": {
-                        "name": instance.metadata.name,
-                    },
-                },
-            )
-            rsps.add(
-                responses.DELETE,
-                f"https://localhost:9443/apis/networking.k8s.io/v1/namespaces/default/ingresses/{name}",  # noqa
-            )
-
-            instance.delete_ingress()
-            assert len(rsps.calls) == 2
-
-    @pytest.mark.parametrize("name", types.OpenstackHelmIngressObjectMetaName)
-    def test_delete_missing_ingress(self, api, requests_mock, name):
-        instance = objects.OpenstackHelmIngress(
-            api=api,
-            metadata=types.OpenstackHelmIngressObjectMeta(
-                name=name,
-                namespace="default",
-            ),
-            spec=types.OpenstackHelmIngressSpec(
-                host=f"{name}.example.com",
-            ),
-        )
-
-        with requests_mock as rsps:
-            rsps.add(
-                responses.GET,
-                f"https://localhost:9443/apis/networking.k8s.io/v1/namespaces/default/ingresses/{name}",  # noqa
-                status=404,
-            )
-
-            instance.delete_ingress()
-            assert len(rsps.calls) == 1
diff --git a/atmosphere/tests/unit/operator/test_types.py b/atmosphere/tests/unit/operator/test_types.py
index 8acf233..246dc69 100644
--- a/atmosphere/tests/unit/operator/test_types.py
+++ b/atmosphere/tests/unit/operator/test_types.py
@@ -157,26 +157,3 @@
         assert isinstance(instance, types.OpenstackHelmRabbitmqClusterSpec)
         assert isinstance(instance.image, str)
         assert instance.image != ""
-
-
-class TestOpenstackHelmIngressObjectMetaName:
-    def test_name_order(self):
-        assert [*types.OpenstackHelmIngressObjectMetaName] == sorted(
-            [*types.OpenstackHelmIngressObjectMetaName]
-        )
-
-
-class TestOpenstackHelmIngressObjectMeta:
-    @given(st.builds(types.OpenstackHelmIngressObjectMeta))
-    def test_property(self, instance):
-        assert isinstance(instance, types.OpenstackHelmIngressObjectMeta)
-        assert isinstance(instance.name, types.OpenstackHelmIngressObjectMetaName)
-
-
-class TestOpenstackHelmIngressSpec:
-    @given(st.builds(types.OpenstackHelmIngressSpec))
-    def test_property(self, instance):
-        assert isinstance(instance, types.OpenstackHelmIngressSpec)
-        assert instance.clusterIssuer != ""
-        assert instance.ingressClassName != ""
-        assert instance.host != ""
diff --git a/roles/atmosphere/templates/crds.yml b/roles/atmosphere/templates/crds.yml
index db56695..edc7e9f 100644
--- a/roles/atmosphere/templates/crds.yml
+++ b/roles/atmosphere/templates/crds.yml
@@ -28,32 +28,6 @@
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
-  name: openstackhelmingresses.atmosphere.vexxhost.com
-spec:
-  scope: Namespaced
-  group: atmosphere.vexxhost.com
-  names:
-    kind: OpenstackHelmIngress
-    plural: openstackhelmingresses
-    singular: openstackhelmingress
-  versions:
-    - name: v1alpha1
-      served: true
-      storage: true
-      schema:
-        openAPIV3Schema:
-          type: object
-          properties:
-            spec:
-              type: object
-              x-kubernetes-preserve-unknown-fields: true
-            status:
-              type: object
-              x-kubernetes-preserve-unknown-fields: true
----
-apiVersion: apiextensions.k8s.io/v1
-kind: CustomResourceDefinition
-metadata:
   name: clouds.atmosphere.vexxhost.com
 spec:
   scope: Namespaced
diff --git a/roles/openstack_helm_horizon/tasks/main.yml b/roles/openstack_helm_horizon/tasks/main.yml
index 460634a..424ea08 100644
--- a/roles/openstack_helm_horizon/tasks/main.yml
+++ b/roles/openstack_helm_horizon/tasks/main.yml
@@ -66,12 +66,3 @@
     openstack_helm_ingress_service_name: horizon-int
     openstack_helm_ingress_service_port: 80
     openstack_helm_ingress_annotations: "{{ openstack_helm_horizon_ingress_annotations }}"
-    # NOTE: Remove grafana path from horizon ingress till monasca realized
-    # openstack_helm_ingress_paths:
-    #   - path: /grafana
-    #     pathType: Prefix
-    #     backend:
-    #       service:
-    #         name: grafana
-    #         port:
-    #           number: 80
diff --git a/roles/openstack_helm_ingress/defaults/main.yml b/roles/openstack_helm_ingress/defaults/main.yml
index 1ae03fe..9dacdb1 100644
--- a/roles/openstack_helm_ingress/defaults/main.yml
+++ b/roles/openstack_helm_ingress/defaults/main.yml
@@ -1,25 +1,21 @@
----
-# .. vim: foldmarker=[[[,]]]:foldmethod=marker
+# Name of the OpenStack-Helm Ingress to create
+openstack_helm_ingress_name: "{{ openstack_helm_ingress_endpoint | replace('_', '-') }}"
 
-# .. Copyright (C) 2022 VEXXHOST, Inc.
-# .. SPDX-License-Identifier: Apache-2.0
-
-# Default variables
-# =================
-
-# .. contents:: Sections
-#    :local:
-
-
-# .. envvar:: openstack_helm_ingress_annotations [[[
-#
-# Additional annotations to include for Kubernetes ingress
+# List of annotations to apply to the Ingress
 openstack_helm_ingress_annotations: {}
 
-                                                                   # ]]]
-# .. envvar:: openstack_helm_ingress_paths [[[
-#
-# Additional paths to include for Kubernetes ingress
-openstack_helm_ingress_paths: []
+# Hostname to expose for the Ingress
+openstack_helm_ingress_host: "{{ openstack_helm_endpoints[openstack_helm_ingress_endpoint]['host_fqdn_override']['public']['host'] }}"
 
-                                                                   # ]]]
+# Name of the "cert-manager" ClusterIssuer to use for TLS certificates
+openstack_helm_ingress_cluster_issuer: atmosphere
+
+# Name of the Ingress class to use for exposing the service.
+openstack_helm_ingress_class_name: openstack
+
+# Secret containing the TLS certificate to use for the Ingress, overriding this
+# value globally should allow to point towards a secret containing the custom
+# certificate.
+#
+# See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
+openstack_helm_ingress_secret_name: "{{ openstack_helm_ingress_service_name }}-certs"
diff --git a/roles/openstack_helm_ingress/tasks/main.yml b/roles/openstack_helm_ingress/tasks/main.yml
index cf2b287..ae13029 100644
--- a/roles/openstack_helm_ingress/tasks/main.yml
+++ b/roles/openstack_helm_ingress/tasks/main.yml
@@ -12,17 +12,37 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: Create Ingress {{ openstack_helm_ingress_endpoint }}
+- name: Create Ingress {{ openstack_helm_ingress_name }}
   kubernetes.core.k8s:
     state: present
     definition:
-      apiVersion: atmosphere.vexxhost.com/v1alpha1
-      kind: OpenstackHelmIngress
+      apiVersion: v1
+      kind: Ingress
       metadata:
-        name: "{{ openstack_helm_ingress_endpoint | replace('_', '-') }}"
+        name: "{{ openstack_helm_ingress_name }}"
         namespace: openstack
-        annotations: "{{ openstack_helm_ingress_annotations }}"
+        annotations: "{{ _openstack_helm_ingress_annotations | combine(openstack_helm_ingress_annotations, recursive=True) }}"
       spec:
-        clusterIssuer: "{{ openstack_helm_ingress_cluster_issuer | default('atmosphere') }}"
-        ingressClassName: "{{ openstack_helm_ingress_class_name | default('openstack') }}"
-        host: "{{ openstack_helm_endpoints[openstack_helm_ingress_endpoint]['host_fqdn_override']['public']['host'] }}"
+        ingressClassName: "{{ openstack_helm_ingress_class_name }}"
+        rules:
+          - host: "{{ openstack_helm_ingress_host }}"
+            http:
+              paths:
+                - path: /
+                  pathType: Prefix
+                  backend:
+                    service:
+                      name: "{{ openstack_helm_ingress_service_name }}"
+                      port:
+                        number: "{{ openstack_helm_ingress_service_port }}"
+        tls:
+          - secretName: "{{ openstack_helm_ingress_secret_name }}"
+            hosts:
+              - "{{ openstack_helm_ingress_host }}"
+  # NOTE(mnaser): The Atmosphere operator is so fast that the Ingress webhook
+  #               is not up yet by the time we run this for the first time, so
+  #               we retry until we let the operator handle creating the ingress.
+  retries: 60
+  delay: 5
+  register: _result
+  until: _result is not failed
diff --git a/roles/openstack_helm_ingress/vars/main.yml b/roles/openstack_helm_ingress/vars/main.yml
new file mode 100644
index 0000000..a1debf7
--- /dev/null
+++ b/roles/openstack_helm_ingress/vars/main.yml
@@ -0,0 +1,16 @@
+# 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.
+
+_openstack_helm_ingress_annotations:
+  cert-manager.io/cluster-issuer: "{{ openstack_helm_ingress_cluster_issuer }}"
diff --git a/roles/openstack_helm_magnum/defaults/main.yml b/roles/openstack_helm_magnum/defaults/main.yml
index b693bc7..d291f86 100644
--- a/roles/openstack_helm_magnum/defaults/main.yml
+++ b/roles/openstack_helm_magnum/defaults/main.yml
@@ -32,3 +32,6 @@
     container_format: bare
 
                                                                    # ]]]
+
+# List of annotations to apply to the Ingress
+openstack_helm_magnum_ingress_annotations: {}
diff --git a/roles/openstack_helm_magnum/tasks/main.yml b/roles/openstack_helm_magnum/tasks/main.yml
index 0ee4691..69e3819 100644
--- a/roles/openstack_helm_magnum/tasks/main.yml
+++ b/roles/openstack_helm_magnum/tasks/main.yml
@@ -322,6 +322,15 @@
               nodeSelector:
                 openstack-control-plane: enabled
 
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: container_infra
+    openstack_helm_ingress_service_name: magnum-api
+    openstack_helm_ingress_service_port: 9511
+    openstack_helm_ingress_annotations: "{{ openstack_helm_magnum_ingress_annotations }}"
+
 - name: Create magnum registry Ingress
   ansible.builtin.include_role:
     name: openstack_helm_ingress