chore: moved cluster issuer to ansible
diff --git a/atmosphere/flows.py b/atmosphere/flows.py
deleted file mode 100644
index e34077d..0000000
--- a/atmosphere/flows.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from taskflow import engines
-from taskflow.patterns import graph_flow
-
-from atmosphere import clients
-from atmosphere.operator.api import objects, types
-from atmosphere.tasks import constants
-from atmosphere.tasks.composite import openstack_helm
-from atmosphere.tasks.kubernetes import cert_manager
-
-
-def get_engine(config):
-    api = clients.get_pykube_api()
-
-    objects.Namespace(
-        api=api,
-        metadata=types.ObjectMeta(
-            name=constants.NAMESPACE_MONITORING,
-        ),
-    ).apply()
-
-    if config.kube_prometheus_stack.enabled:
-        objects.HelmRepository(
-            api=api,
-            metadata=types.NamespacedObjectMeta(
-                name=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
-                namespace=config.kube_prometheus_stack.namespace,
-            ),
-            spec=types.HelmRepositorySpec(
-                url=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY_URL,
-            ),
-        ).apply()
-        objects.HelmRelease(
-            api=api,
-            metadata=types.NamespacedObjectMeta(
-                name=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
-                namespace=config.kube_prometheus_stack.namespace,
-            ),
-            spec=types.HelmReleaseSpec(
-                chart=types.HelmChartTemplate(
-                    spec=types.HelmChartTemplateSpec(
-                        chart=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
-                        version=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VERSION,
-                        source_ref=types.CrossNamespaceObjectReference(
-                            kind="HelmRepository",
-                            name=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
-                            namespace=config.kube_prometheus_stack.namespace,
-                        ),
-                    )
-                ),
-                values={
-                    **constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VALUES,
-                    **config.kube_prometheus_stack.overrides,
-                    **{
-                        "alertmanager": {
-                            "config": openstack_helm.generate_alertmanager_config_for_opsgenie(
-                                config.opsgenie
-                            )
-                        }
-                    },
-                },
-            ),
-        ).apply()
-
-    flow = graph_flow.Flow("deploy").add(
-        *cert_manager.issuer_tasks_from_config(config.issuer),
-    )
-    return engines.load(flow)
diff --git a/atmosphere/models/config.py b/atmosphere/models/config.py
index b0f75f5..c63e235 100644
--- a/atmosphere/models/config.py
+++ b/atmosphere/models/config.py
@@ -9,75 +9,6 @@
 CONFIG_FILE = os.environ.get("ATMOSPHERE_CONFIG", "/etc/atmosphere/config.toml")
 
 
-class AcmeIssuerSolverConfig(base.Model):
-    type = types.StringType(
-        choices=("http", "rfc2136", "route53"), default="http", required=True
-    )
-
-    @classmethod
-    def _claim_polymorphic(cls, data):
-        return data.get("type", cls.type.default) == cls.TYPE
-
-
-class HttpAcmeIssuerSolverConfig(AcmeIssuerSolverConfig):
-    TYPE = "http"
-
-
-class Rfc2136AcmeIssuerSolverConfig(AcmeIssuerSolverConfig):
-    TYPE = "rfc2136"
-
-    nameserver = types.StringType(required=True)
-    tsig_algorithm = types.StringType(required=True)
-    tsig_key_name = types.StringType(required=True)
-    tsig_secret = types.StringType(required=True)
-
-
-class Route53AcmeIssuerSolverConfig(AcmeIssuerSolverConfig):
-    TYPE = "route53"
-
-    region = types.StringType(default="global", required=True)
-    hosted_zone_id = types.StringType(required=True)
-    access_key_id = types.StringType(required=True)
-    secret_access_key = types.StringType(required=True)
-
-
-class Issuer(base.Model):
-    type = types.StringType(
-        choices=("acme", "ca", "self-signed"), default="acme", required=True
-    )
-
-    @classmethod
-    def _claim_polymorphic(cls, data):
-        return data.get("type", cls.type.default) == cls.TYPE
-
-
-class AcmeIssuerConfig(Issuer):
-    TYPE = "acme"
-
-    email = types.StringType(required=True)
-    server = types.URLType(default="https://acme-v02.api.letsencrypt.org/directory")
-    solver = types.PolyModelType(
-        [
-            HttpAcmeIssuerSolverConfig,
-            Rfc2136AcmeIssuerSolverConfig,
-            Route53AcmeIssuerSolverConfig,
-        ],
-        default=HttpAcmeIssuerSolverConfig(),
-        required=True,
-    )
-
-
-class CaIssuerConfig(Issuer):
-    TYPE = "ca"
-
-    certificate = types.StringType(required=True)
-    private_key = types.StringType(required=True)
-
-
-class SelfSignedIssuerConfig(Issuer):
-    TYPE = "self-signed"
-
-
 class ChartConfig(base.Model):
     enabled = types.BooleanType(default=True, required=True)
     overrides = types.DictType(types.BaseType(), default={})
@@ -108,11 +39,6 @@
     kube_prometheus_stack = types.ModelType(
         KubePrometheusStackChartConfig, default=KubePrometheusStackChartConfig()
     )
-    issuer = types.PolyModelType(
-        [AcmeIssuerConfig, CaIssuerConfig, SelfSignedIssuerConfig],
-        default=AcmeIssuerConfig(),
-        required=True,
-    )
     opsgenie = types.ModelType(OpsGenieConfig, default=OpsGenieConfig())
 
     @classmethod
diff --git a/atmosphere/operator/controllers/cloud.py b/atmosphere/operator/controllers/cloud.py
index cfe28ca..573edca 100644
--- a/atmosphere/operator/controllers/cloud.py
+++ b/atmosphere/operator/controllers/cloud.py
@@ -1,14 +1,64 @@
 import kopf
 
-from atmosphere import flows
+from atmosphere import clients
 from atmosphere.models import config
-from atmosphere.operator.api import Cloud
+from atmosphere.operator.api import Cloud, objects, types
+from atmosphere.tasks import constants
+from atmosphere.tasks.composite import openstack_helm
 
 
 @kopf.on.resume(Cloud.version, Cloud.kind)
 @kopf.on.create(Cloud.version, Cloud.kind)
 def create_fn(namespace: str, name: str, spec: dict, **_):
-    # TODO(mnaser): Get rid of this flow.
     cfg = config.Config.from_file()
-    engine = flows.get_engine(cfg)
-    engine.run()
+    api = clients.get_pykube_api()
+
+    objects.Namespace(
+        api=api,
+        metadata=types.ObjectMeta(
+            name=constants.NAMESPACE_MONITORING,
+        ),
+    ).apply()
+
+    if cfg.kube_prometheus_stack.enabled:
+        objects.HelmRepository(
+            api=api,
+            metadata=types.NamespacedObjectMeta(
+                name=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
+                namespace=cfg.kube_prometheus_stack.namespace,
+            ),
+            spec=types.HelmRepositorySpec(
+                url=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY_URL,
+            ),
+        ).apply()
+        objects.HelmRelease(
+            api=api,
+            metadata=types.NamespacedObjectMeta(
+                name=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
+                namespace=cfg.kube_prometheus_stack.namespace,
+            ),
+            spec=types.HelmReleaseSpec(
+                chart=types.HelmChartTemplate(
+                    spec=types.HelmChartTemplateSpec(
+                        chart=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
+                        version=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VERSION,
+                        source_ref=types.CrossNamespaceObjectReference(
+                            kind="HelmRepository",
+                            name=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
+                            namespace=cfg.kube_prometheus_stack.namespace,
+                        ),
+                    )
+                ),
+                values={
+                    **constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VALUES,
+                    **cfg.kube_prometheus_stack.overrides,
+                    **{
+                        "alertmanager": {
+                            "config": openstack_helm.generate_alertmanager_config_for_opsgenie(
+                                cfg.opsgenie
+                            )
+                        }
+                    },
+                },
+            ),
+        ).apply()
diff --git a/atmosphere/tasks/kubernetes/__init__.py b/atmosphere/tasks/kubernetes/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/atmosphere/tasks/kubernetes/__init__.py
+++ /dev/null
diff --git a/atmosphere/tasks/kubernetes/base.py b/atmosphere/tasks/kubernetes/base.py
deleted file mode 100644
index 838b6ad..0000000
--- a/atmosphere/tasks/kubernetes/base.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import json
-import re
-
-import pykube
-import structlog
-from taskflow import task
-from tenacity import retry, stop_after_delay, wait_fixed
-
-from atmosphere import clients
-
-CAMEL_CASE_PATTERN = re.compile(r"(?<!^)(?=[A-Z])")
-LOG = structlog.get_logger()
-
-
-class ApplyKubernetesObjectTask(task.Task):
-    def __init__(
-        self, kind: pykube.objects.APIObject, namespace: str, name: str, *args, **kwargs
-    ):
-        self._obj_kind = kind
-        self._obj_namespace = namespace
-        self._obj_name = name
-
-        kwargs["name"] = CAMEL_CASE_PATTERN.sub("-", kind.__name__).lower()
-        if namespace:
-            kwargs["name"] += f"-{namespace}"
-        kwargs["name"] += f"-{name}"
-
-        kwargs.setdefault("provides", set())
-        kwargs["provides"] = kwargs["provides"].union(set([kwargs["name"]]))
-
-        super().__init__(*args, **kwargs)
-
-    @property
-    def api(self):
-        return clients.get_pykube_api()
-
-    @property
-    def logger(self):
-        log = LOG.bind(
-            kind=self._obj_kind.__name__,
-            name=self._obj_name,
-        )
-        if self._obj_namespace:
-            log = log.bind(namespace=self._obj_namespace)
-        if self.requires:
-            log = log.bind(requires=list(self.requires))
-        return log
-
-    def generate_object(self, *args, **kwargs) -> pykube.objects.APIObject:
-        raise NotImplementedError
-
-    def wait_for_resource(self, resource: pykube.objects.APIObject):
-        pass
-
-    @retry(wait=wait_fixed(2), stop=stop_after_delay(120))
-    def execute(self, *args, **kwargs):
-        self.logger.debug("Ensuring resource")
-
-        resource = self.generate_object()
-        resp = resource.api.patch(
-            **resource.api_kwargs(
-                headers={
-                    "Content-Type": "application/apply-patch+yaml",
-                },
-                params={
-                    "fieldManager": "atmosphere-operator",
-                    "force": True,
-                },
-                data=json.dumps(resource.obj),
-            )
-        )
-
-        resource.api.raise_for_status(resp)
-        resource.set_obj(resp.json())
-
-        self.wait_for_resource(resource)
-
-        self.logger.info("Ensured resource")
-
-        return {
-            self.name: resource,
-        }
diff --git a/atmosphere/tasks/kubernetes/cert_manager.py b/atmosphere/tasks/kubernetes/cert_manager.py
deleted file mode 100644
index 2c420a3..0000000
--- a/atmosphere/tasks/kubernetes/cert_manager.py
+++ /dev/null
@@ -1,200 +0,0 @@
-import pykube
-
-from atmosphere.models import config
-from atmosphere.tasks import constants
-from atmosphere.tasks.kubernetes import base, v1
-
-
-class Certificate(pykube.objects.NamespacedAPIObject):
-    version = "cert-manager.io/v1"
-    endpoint = "certificates"
-    kind = "Certificate"
-
-
-class ApplyCertificateTask(base.ApplyKubernetesObjectTask):
-    def __init__(self, namespace: str, name: str, spec: dict):
-        self._spec = spec
-
-        super().__init__(
-            kind=Certificate,
-            namespace=namespace,
-            name=name,
-        )
-
-    def generate_object(self) -> Certificate:
-        return Certificate(
-            self.api,
-            {
-                "apiVersion": self._obj_kind.version,
-                "kind": self._obj_kind.kind,
-                "metadata": {
-                    "name": self._obj_name,
-                    "namespace": self._obj_namespace,
-                },
-                "spec": self._spec,
-            },
-        )
-
-
-class ClusterIssuer(pykube.objects.APIObject):
-    version = "cert-manager.io/v1"
-    endpoint = "clusterissuers"
-    kind = "ClusterIssuer"
-
-
-class ApplyClusterIssuerTask(base.ApplyKubernetesObjectTask):
-    def __init__(self, name: str, spec: dict):
-        self._spec = spec
-
-        super().__init__(
-            kind=ClusterIssuer,
-            namespace=None,
-            name=name,
-        )
-
-    def generate_object(self) -> ClusterIssuer:
-        return ClusterIssuer(
-            self.api,
-            {
-                "apiVersion": self._obj_kind.version,
-                "kind": self._obj_kind.kind,
-                "metadata": {
-                    "name": self._obj_name,
-                },
-                "spec": self._spec,
-            },
-        )
-
-
-def issuer_tasks_from_config(config: config.Issuer) -> list:
-    objects = [
-        ApplyClusterIssuerTask(
-            name="self-signed",
-            spec={
-                "selfSigned": {},
-            },
-        )
-    ]
-
-    if config.type == "acme":
-        spec = {
-            "acme": {
-                "email": config.email,
-                "server": config.server,
-                "privateKeySecretRef": {
-                    "name": "cert-manager-issuer-account-key",
-                },
-            },
-        }
-
-        if config.solver.type == "http":
-            spec["acme"]["solvers"] = [
-                {
-                    "http01": {
-                        "ingress": {
-                            "class": "openstack",
-                        },
-                    },
-                },
-            ]
-        elif config.solver.type == "rfc2136":
-            # NOTE(mnaser): We have to create a secret containing the AWS
-            #               credentials in this case.
-            objects.append(
-                v1.ApplySecretTask(
-                    constants.NAMESPACE_CERT_MANAGER,
-                    "cert-manager-issuer-tsig-secret-key",
-                    data={
-                        "tsig-secret-key": config.solver.tsig_secret,
-                    },
-                )
-            )
-
-            spec["acme"]["solvers"] = [
-                {
-                    "dns01": {
-                        "rfc2136": {
-                            "nameserver": config.solver.nameserver,
-                            "tsigAlgorithm": config.solver.tsig_algorithm,
-                            "tsigKeyName": config.solver.tsig_key_name,
-                            "tsigSecretSecretRef": {
-                                "name": "cert-manager-issuer-tsig-secret-key",
-                                "key": "tsig-secret-key",
-                            },
-                        },
-                    },
-                },
-            ]
-        elif config.solver.type == "route53":
-            # NOTE(mnaser): We have to create a secret containing the AWS
-            #               credentials in this case.
-            objects.append(
-                v1.ApplySecretTask(
-                    constants.NAMESPACE_CERT_MANAGER,
-                    "cert-manager-issuer-route53-credentials",
-                    data={"secret-access-key": config.solver.secret_access_key},
-                )
-            )
-
-            spec["acme"]["solvers"] = [
-                {
-                    "dns01": {
-                        "route53": {
-                            "region": config.solver.region,
-                            "hostedZoneID": config.solver.hosted_zone_id,
-                            "accessKeyID": config.solver.access_key_id,
-                            "secretAccessKeySecretRef": {
-                                "name": "cert-manager-issuer-route53-credentials",
-                                "key": "secret-access-key",
-                            },
-                        },
-                    },
-                },
-            ]
-    elif config.type == "ca":
-        # NOTE(mnaser): We have to create a secret containing the CA
-        #               certificate and key in this case.
-        objects.append(
-            v1.ApplySecretTask(
-                constants.NAMESPACE_CERT_MANAGER,
-                "cert-manager-issuer-ca",
-                data={
-                    "tls.crt": config.certificate,
-                    "tls.key": config.private_key,
-                },
-            )
-        )
-
-        spec = {
-            "ca": {
-                "secretName": "cert-manager-issuer-ca",
-            }
-        }
-    elif config.type == "self-signed":
-        # NOTE(mnaser): We have to setup the self-signed CA in this case
-        objects += [
-            ApplyCertificateTask(
-                namespace=constants.NAMESPACE_CERT_MANAGER,
-                name="self-signed-ca",
-                spec={
-                    "isCA": True,
-                    "commonName": "selfsigned-ca",
-                    "secretName": "cert-manager-selfsigned-ca",
-                    "duration": "86400h",
-                    "renewBefore": "360h",
-                    "privateKey": {"algorithm": "ECDSA", "size": 256},
-                    "issuerRef": {
-                        "kind": "ClusterIssuer",
-                        "name": "self-signed",
-                    },
-                },
-            ),
-        ]
-
-        spec = {
-            "ca": {
-                "secretName": "cert-manager-selfsigned-ca",
-            }
-        }
-
-    return objects + [ApplyClusterIssuerTask(name="atmosphere", spec=spec)]
diff --git a/atmosphere/tasks/kubernetes/v1.py b/atmosphere/tasks/kubernetes/v1.py
deleted file mode 100644
index cd48196..0000000
--- a/atmosphere/tasks/kubernetes/v1.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import pykube
-
-from atmosphere.tasks.kubernetes import base
-
-
-class ApplySecretTask(base.ApplyKubernetesObjectTask):
-    def __init__(self, namespace: str, name: str, data: str):
-        self._data = data
-
-        super().__init__(
-            kind=pykube.Secret,
-            namespace=namespace,
-            name=name,
-        )
-
-    def generate_object(self) -> pykube.Secret:
-        return pykube.Secret(
-            self.api,
-            {
-                "apiVersion": "v1",
-                "kind": "Secret",
-                "metadata": {
-                    "name": self._obj_name,
-                    "namespace": self._obj_namespace,
-                },
-                "stringData": self._data,
-            },
-        )
diff --git a/atmosphere/tests/unit/tasks/kubernetes/test_cert_manager.py b/atmosphere/tests/unit/tasks/kubernetes/test_cert_manager.py
deleted file mode 100644
index 1f03439..0000000
--- a/atmosphere/tests/unit/tasks/kubernetes/test_cert_manager.py
+++ /dev/null
@@ -1,335 +0,0 @@
-import textwrap
-
-import pykube
-import pytest
-
-from atmosphere.models import config
-from atmosphere.tasks import constants
-from atmosphere.tasks.kubernetes import cert_manager
-
-
-@pytest.mark.parametrize(
-    "cfg_data,expected",
-    [
-        pytest.param(
-            textwrap.dedent(
-                """\
-                [issuer]
-                email = "mnaser@vexxhost.com"
-                """
-            ),
-            [
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "self-signed",
-                    },
-                    "spec": {
-                        "selfSigned": {},
-                    },
-                },
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "atmosphere",
-                    },
-                    "spec": {
-                        "acme": {
-                            "email": "mnaser@vexxhost.com",
-                            "server": "https://acme-v02.api.letsencrypt.org/directory",
-                            "privateKeySecretRef": {
-                                "name": "cert-manager-issuer-account-key",
-                            },
-                            "solvers": [
-                                {
-                                    "http01": {
-                                        "ingress": {
-                                            "class": "openstack",
-                                        },
-                                    },
-                                },
-                            ],
-                        },
-                    },
-                },
-            ],
-            id="default",
-        ),
-        pytest.param(
-            textwrap.dedent(
-                """\
-                [issuer]
-                email = "mnaser@vexxhost.com"
-
-                [issuer.solver]
-                type = "rfc2136"
-                nameserver = "1.2.3.4:53"
-                tsig_algorithm = "hmac-sha256"
-                tsig_key_name = "foobar"
-                tsig_secret = "secret123"
-                """
-            ),
-            [
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "self-signed",
-                    },
-                    "spec": {
-                        "selfSigned": {},
-                    },
-                },
-                {
-                    "apiVersion": pykube.Secret.version,
-                    "kind": pykube.Secret.kind,
-                    "metadata": {
-                        "name": "cert-manager-issuer-tsig-secret-key",
-                        "namespace": constants.NAMESPACE_CERT_MANAGER,
-                    },
-                    "stringData": {
-                        "tsig-secret-key": "secret123",
-                    },
-                },
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "atmosphere",
-                    },
-                    "spec": {
-                        "acme": {
-                            "email": "mnaser@vexxhost.com",
-                            "server": "https://acme-v02.api.letsencrypt.org/directory",
-                            "privateKeySecretRef": {
-                                "name": "cert-manager-issuer-account-key",
-                            },
-                            "solvers": [
-                                {
-                                    "dns01": {
-                                        "rfc2136": {
-                                            "nameserver": "1.2.3.4:53",
-                                            "tsigAlgorithm": "hmac-sha256",
-                                            "tsigKeyName": "foobar",
-                                            "tsigSecretSecretRef": {
-                                                "name": "cert-manager-issuer-tsig-secret-key",
-                                                "key": "tsig-secret-key",
-                                            },
-                                        },
-                                    },
-                                },
-                            ],
-                        },
-                    },
-                },
-            ],
-            id="rfc2136",
-        ),
-        pytest.param(
-            textwrap.dedent(
-                """\
-                [issuer]
-                email = "mnaser@vexxhost.com"
-
-                [issuer.solver]
-                type = "route53"
-                hosted_zone_id = "Z3A4X2Y2Y3"
-                access_key_id = "AKIAIOSFODNN7EXAMPLE"
-                secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
-                """
-            ),
-            [
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "self-signed",
-                    },
-                    "spec": {
-                        "selfSigned": {},
-                    },
-                },
-                {
-                    "apiVersion": pykube.Secret.version,
-                    "kind": pykube.Secret.kind,
-                    "metadata": {
-                        "name": "cert-manager-issuer-route53-credentials",
-                        "namespace": constants.NAMESPACE_CERT_MANAGER,
-                    },
-                    "stringData": {
-                        "secret-access-key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
-                    },
-                },
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "atmosphere",
-                    },
-                    "spec": {
-                        "acme": {
-                            "email": "mnaser@vexxhost.com",
-                            "server": "https://acme-v02.api.letsencrypt.org/directory",
-                            "privateKeySecretRef": {
-                                "name": "cert-manager-issuer-account-key",
-                            },
-                            "solvers": [
-                                {
-                                    "dns01": {
-                                        "route53": {
-                                            "region": "global",
-                                            "hostedZoneID": "Z3A4X2Y2Y3",
-                                            "accessKeyID": "AKIAIOSFODNN7EXAMPLE",
-                                            "secretAccessKeySecretRef": {
-                                                "name": "cert-manager-issuer-route53-credentials",
-                                                "key": "secret-access-key",
-                                            },
-                                        },
-                                    },
-                                },
-                            ],
-                        },
-                    },
-                },
-            ],
-            id="route53",
-        ),
-        pytest.param(
-            textwrap.dedent(
-                """\
-                [issuer]
-                type = "ca"
-                certificate = '''
-                -----BEGIN CERTIFICATE-----
-                MIIDBjCCAe4CCQDQ3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-                VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
-                ...
-                -----END CERTIFICATE-----
-                '''
-                private_key = '''
-                -----BEGIN RSA PRIVATE KEY-----
-                MIIEpAIBAAKCAQEAw3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-                VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
-                ...
-                -----END RSA PRIVATE KEY-----
-                '''
-                """
-            ),
-            [
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "self-signed",
-                    },
-                    "spec": {
-                        "selfSigned": {},
-                    },
-                },
-                {
-                    "apiVersion": pykube.Secret.version,
-                    "kind": pykube.Secret.kind,
-                    "metadata": {
-                        "name": "cert-manager-issuer-ca",
-                        "namespace": constants.NAMESPACE_CERT_MANAGER,
-                    },
-                    "stringData": {
-                        "tls.crt": textwrap.dedent(
-                            """\
-                            -----BEGIN CERTIFICATE-----
-                            MIIDBjCCAe4CCQDQ3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-                            VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
-                            ...
-                            -----END CERTIFICATE-----
-                            """
-                        ),
-                        "tls.key": textwrap.dedent(
-                            """\
-                            -----BEGIN RSA PRIVATE KEY-----
-                            MIIEpAIBAAKCAQEAw3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-                            VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
-                            ...
-                            -----END RSA PRIVATE KEY-----
-                            """
-                        ),
-                    },
-                },
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "atmosphere",
-                    },
-                    "spec": {
-                        "ca": {
-                            "secretName": "cert-manager-issuer-ca",
-                        },
-                    },
-                },
-            ],
-            id="ca",
-        ),
-        pytest.param(
-            textwrap.dedent(
-                """\
-                [issuer]
-                type = "self-signed"
-                """
-            ),
-            [
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "self-signed",
-                    },
-                    "spec": {
-                        "selfSigned": {},
-                    },
-                },
-                {
-                    "apiVersion": cert_manager.Certificate.version,
-                    "kind": cert_manager.Certificate.kind,
-                    "metadata": {
-                        "name": "self-signed-ca",
-                        "namespace": constants.NAMESPACE_CERT_MANAGER,
-                    },
-                    "spec": {
-                        "isCA": True,
-                        "commonName": "selfsigned-ca",
-                        "secretName": "cert-manager-selfsigned-ca",
-                        "duration": "86400h",
-                        "renewBefore": "360h",
-                        "privateKey": {"algorithm": "ECDSA", "size": 256},
-                        "issuerRef": {
-                            "kind": "ClusterIssuer",
-                            "name": "self-signed",
-                        },
-                    },
-                },
-                {
-                    "apiVersion": cert_manager.ClusterIssuer.version,
-                    "kind": cert_manager.ClusterIssuer.kind,
-                    "metadata": {
-                        "name": "atmosphere",
-                    },
-                    "spec": {
-                        "ca": {
-                            "secretName": "cert-manager-selfsigned-ca",
-                        },
-                    },
-                },
-            ],
-            id="self-signed",
-        ),
-    ],
-)
-def test_apply_issuer_task_from_config(pykube, cfg_data, expected):
-    cfg = config.Config.from_string(cfg_data, validate=False)
-    cfg.issuer.validate()
-    assert [
-        t.generate_object().obj
-        for t in cert_manager.issuer_tasks_from_config(cfg.issuer)
-    ] == expected
diff --git a/docs/certificates.md b/docs/certificates.md
deleted file mode 100644
index d069b3a..0000000
--- a/docs/certificates.md
+++ /dev/null
@@ -1,114 +0,0 @@
-# Certificates
-
-Atmosphere simplifies all the management of your SSL certificates for all of
-your API endpoints by automatically issuing and renewing certificates for you.
-
-## ACME
-
-Atmosphere uses the [ACME](https://tools.ietf.org/html/rfc8555) protocol by
-default to request certificates from [LetsEncrypt](https://letsencrypt.org/).
-
-This is configured to work out of the box if your APIs are publicly accessible,
-you just need to configure an email address.
-
-```yaml
-atmosphere_issuer_config:
-  email: foo@bar.com
-```
-
-If you're running your own internal ACME server, you can configure Atmosphere to
-point towards it by setting the `server` field.
-
-```yaml
-atmosphere_issuer_config:
-  email: foo@bar.com
-  server: https://acme.example.com
-```
-
-### DNS-01 challenges
-
-Atmosphere uses the `HTTP-01` solver by default, which means that as long as
-your ACME server can reach your API, you don't need to do anything else.
-
-If your ACME server cannot reach your API, you will need to use the DNS-01
-challenges which require you to configure your DNS provider.
-
-#### RFC2136
-
-If you have DNS server that supports RFC2136, you can use it to solve the DNS
-challenges, you can use the following configuration:
-
-```yaml
-atmosphere_issuer_config:
-  email: foo@bar.com
-  solver:
-    type: rfc2136
-    nameserver: <NAMESERVER>:<PORT>
-    tsig_algorithm: <ALGORITHM>
-    tsig_key_name: <NAME>
-    tsig_secret: <SECRET>
-```
-
-#### Route53
-
-If you are using Route53 to host the DNS for your domains, you can use the
-following configuration:
-
-```yaml
-atmosphere_issuer_config:
-  email: foo@bar.com
-  solver:
-    type: route53
-    hosted_zone_id: <HOSTED_ZONE_ID>
-    access_key_id: <AWS_ACCESS_KEY_ID>
-    secret_access_key: <AWS_SECRET_ACCESS_KEY>
-```
-
-!!! note
-
-   You'll need to make sure that your AWS credentials have the correct
-   permissions to update the Route53 zone.
-
-## Using pre-existing CA
-
-If you have an existing CA that you'd like to use with Atmosphere, you can
-simply configure it by including the certificate and private key:
-
-```yaml
-atmosphere_issuer_config:
-  type: ca
-  certificate: |
-    -----BEGIN CERTIFICATE-----
-    MIIDBjCCAe4CCQDQ3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-    VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
-    ...
-    -----END CERTIFICATE-----
-  private_key: |
-    -----BEGIN RSA PRIVATE KEY-----
-    MIIEpAIBAAKCAQEAw3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-    VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
-    ...
-    -----END RSA PRIVATE KEY-----
-```
-
-!!! note
-
-   If your issuer is an intermediate certificate, you will need to ensure that
-   they `certificate` key includes the full chain in the correct order of issuer,
-   intermediate(s) then root.
-
-## Self-signed certificates
-
-If you are in an environment which does not have a trusted certificate authority
-and it does not have access to the internet to be able to use LetsEncrypt, you
-can use self-signed certificates by adding the following to your inventory:
-
-```yaml
-atmosphere_issuer_config:
-  type: self-signed
-```
-
-!!! warning
-
-   Self-signed certificates are not recommended for production environments,
-   they are only recommended for development and testing environments.
diff --git a/molecule/default/group_vars/all/molecule.yml b/molecule/default/group_vars/all/molecule.yml
index 6fecc77..f07d9b3 100644
--- a/molecule/default/group_vars/all/molecule.yml
+++ b/molecule/default/group_vars/all/molecule.yml
@@ -1,7 +1,6 @@
 atmosphere_image: "{{ lookup('file', lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') + '/image') }}"

 

-atmosphere_issuer_config:

-  type: self-signed

+cluster_issuer_type: self-signed

 

 openstack_helm_glance_images:

   - name: cirros

diff --git a/poetry.lock b/poetry.lock
index fd93322..ba01ff2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -106,18 +106,6 @@
 tests_no_zope = ["cloudpickle", "hypothesis", "mypy (>=0.971,<0.990)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
 
 [[package]]
-name = "automaton"
-version = "3.0.1"
-description = "Friendly state machines for python."
-category = "main"
-optional = true
-python-versions = ">=3.8"
-
-[package.dependencies]
-pbr = ">=2.0.0,<2.1.0 || >2.1.0"
-PrettyTable = ">=0.7.2"
-
-[[package]]
 name = "binaryornot"
 version = "0.4.4"
 description = "Ultra-lightweight pure Python package to check if a file is binary or text."
@@ -129,14 +117,6 @@
 chardet = ">=3.0.2"
 
 [[package]]
-name = "cachetools"
-version = "5.3.0"
-description = "Extensible memoizing collections and decorators"
-category = "main"
-optional = true
-python-versions = "~=3.7"
-
-[[package]]
 name = "certifi"
 version = "2022.12.7"
 description = "Python package for providing Mozilla's CA Bundle."
@@ -262,7 +242,7 @@
 version = "2.5.0"
 description = "A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner."
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
@@ -336,14 +316,6 @@
 testing = ["pre-commit"]
 
 [[package]]
-name = "fasteners"
-version = "0.18"
-description = "A python package that provides useful locks"
-category = "main"
-optional = true
-python-versions = ">=3.6"
-
-[[package]]
 name = "flake8"
 version = "5.0.4"
 description = "the modular source code checker: pep8 pyflakes and co"
@@ -380,14 +352,6 @@
 python-versions = ">=3.7"
 
 [[package]]
-name = "futurist"
-version = "2.4.1"
-description = "Useful additions to futures, from the future."
-category = "main"
-optional = true
-python-versions = ">=3.6"
-
-[[package]]
 name = "ghp-import"
 version = "2.1.0"
 description = "Copy your docs directly to the gh-pages branch."
@@ -533,7 +497,7 @@
 name = "jsonschema"
 version = "4.17.3"
 description = "An implementation of JSON Schema validation for Python"
-category = "main"
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 
@@ -737,7 +701,7 @@
 version = "1.0.4"
 description = "MessagePack serializer"
 category = "main"
-optional = true
+optional = false
 python-versions = "*"
 
 [[package]]
@@ -768,7 +732,7 @@
 version = "0.8.0"
 description = "A network address manipulation library for Python"
 category = "main"
-optional = true
+optional = false
 python-versions = "*"
 
 [[package]]
@@ -780,21 +744,6 @@
 python-versions = "*"
 
 [[package]]
-name = "networkx"
-version = "3.0"
-description = "Python package for creating and manipulating graphs and networks"
-category = "main"
-optional = true
-python-versions = ">=3.8"
-
-[package.extras]
-default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"]
-developer = ["mypy (>=0.991)", "pre-commit (>=2.20)"]
-doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.2)", "pydata-sphinx-theme (>=0.11)", "sphinx (==5.2.3)", "sphinx-gallery (>=0.11)", "texext (>=0.6.7)"]
-extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"]
-test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"]
-
-[[package]]
 name = "openstacksdk"
 version = "0.61.0"
 description = "An SDK for building applications to work with OpenStack"
@@ -834,7 +783,7 @@
 version = "5.1.0"
 description = "Oslo i18n library"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
@@ -845,7 +794,7 @@
 version = "5.0.0"
 description = "Oslo Serialization library"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.8"
 
 [package.dependencies]
@@ -859,7 +808,7 @@
 version = "6.1.0"
 description = "Oslo Utility library"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.8"
 
 [package.dependencies]
@@ -901,20 +850,6 @@
 testing = ["pytest", "pytest-benchmark"]
 
 [[package]]
-name = "prettytable"
-version = "3.6.0"
-description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format"
-category = "main"
-optional = true
-python-versions = ">=3.7"
-
-[package.dependencies]
-wcwidth = "*"
-
-[package.extras]
-tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"]
-
-[[package]]
 name = "py"
 version = "1.11.0"
 description = "library with cross-python path, ini-parsing, io, code, log facilities"
@@ -954,17 +889,6 @@
 email = ["email-validator (>=1.0.3)"]
 
 [[package]]
-name = "pydot"
-version = "1.4.2"
-description = "Python interface to Graphviz's Dot"
-category = "main"
-optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
-[package.dependencies]
-pyparsing = ">=2.1.4"
-
-[[package]]
 name = "pyflakes"
 version = "2.5.0"
 description = "passive checker of Python programs"
@@ -1016,7 +940,7 @@
 version = "3.0.9"
 description = "pyparsing module - Classes and methods to define and execute parsing grammars"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.6.8"
 
 [package.extras]
@@ -1026,7 +950,7 @@
 name = "pyrsistent"
 version = "0.19.3"
 description = "Persistent/Functional/Immutable data structures"
-category = "main"
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 
@@ -1146,7 +1070,7 @@
 version = "2022.7.1"
 description = "World timezone definitions, modern and historical"
 category = "main"
-optional = true
+optional = false
 python-versions = "*"
 
 [[package]]
@@ -1284,20 +1208,6 @@
 pbr = ">=2.0.0,<2.1.0 || >2.1.0"
 
 [[package]]
-name = "structlog"
-version = "22.3.0"
-description = "Structured Logging for Python"
-category = "main"
-optional = true
-python-versions = ">=3.7"
-
-[package.extras]
-dev = ["structlog[docs,tests,typing]"]
-docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"]
-tests = ["coverage[toml]", "freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"]
-typing = ["mypy", "rich", "twisted"]
-
-[[package]]
 name = "subprocess-tee"
 version = "0.4.1"
 description = "subprocess-tee"
@@ -1309,47 +1219,6 @@
 test = ["enrich (>=1.2.6)", "molecule (>=3.4.0)", "pytest (>=6.2.5)", "pytest-cov (>=2.12.1)", "pytest-plus (>=0.2)", "pytest-xdist (>=2.3.0)"]
 
 [[package]]
-name = "taskflow"
-version = "5.1.0"
-description = "Taskflow structured state management library."
-category = "main"
-optional = true
-python-versions = ">=3.8"
-
-[package.dependencies]
-automaton = ">=1.9.0"
-cachetools = ">=2.0.0"
-fasteners = ">=0.17.3"
-futurist = ">=1.2.0"
-jsonschema = ">=3.2.0"
-networkx = ">=2.1.0"
-"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1"
-"oslo.utils" = ">=3.33.0"
-pbr = ">=2.0.0,<2.1.0 || >2.1.0"
-pydot = ">=1.2.4"
-stevedore = ">=1.20.0"
-tenacity = ">=6.0.0"
-
-[package.extras]
-database = ["PyMySQL (>=0.7.6)", "SQLAlchemy (>=1.0.10,!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8)", "SQLAlchemy-Utils (>=0.30.11)", "alembic (>=0.8.10)", "psycopg2 (>=2.8.0)"]
-eventlet = ["eventlet (>=0.18.2,!=0.18.3,!=0.20.1,!=0.21.0)"]
-redis = ["redis (>=2.10.0)"]
-test = ["hacking (>=0.10.0,<0.11)", "mock (>=2.0.0)", "oslotest (>=3.2.0)", "pydotplus (>=2.0.2)", "stestr (>=2.0.0)", "testscenarios (>=0.4)", "testtools (>=2.2.0)"]
-workers = ["kombu (>=4.3.0)"]
-zookeeper = ["kazoo (>=2.6.0)", "zake (>=0.1.6)"]
-
-[[package]]
-name = "tenacity"
-version = "8.1.0"
-description = "Retry code until it succeeds"
-category = "main"
-optional = true
-python-versions = ">=3.6"
-
-[package.extras]
-doc = ["reno", "sphinx", "tornado (>=4.5)"]
-
-[[package]]
 name = "text-unidecode"
 version = "1.3"
 description = "The most basic Text::Unidecode port"
@@ -1435,19 +1304,11 @@
 watchmedo = ["PyYAML (>=3.10)"]
 
 [[package]]
-name = "wcwidth"
-version = "0.2.6"
-description = "Measures the displayed width of unicode strings in a terminal"
-category = "main"
-optional = true
-python-versions = "*"
-
-[[package]]
 name = "wrapt"
 version = "1.14.1"
 description = "Module for decorators, wrappers and monkey patching."
 category = "main"
-optional = true
+optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 
 [[package]]
@@ -1463,12 +1324,12 @@
 multidict = ">=4.0"
 
 [extras]
-operator = ["schematics", "pydantic", "pykube-ng", "structlog", "taskflow", "tomli", "jsonnet", "kopf"]
+operator = ["schematics", "pydantic", "pykube-ng", "tomli", "jsonnet", "kopf"]
 
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.10"
-content-hash = "2192157ba411e6a12715305b5496b609422c1b04208dac823c3729d2aab802df"
+content-hash = "034ac062f5dca8bb749b33d8a4cd4d646a41e7c0da24771ea1522041fa183413"
 
 [metadata.files]
 aiohttp = [
@@ -1588,18 +1449,10 @@
     {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
     {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
 ]
-automaton = [
-    {file = "automaton-3.0.1-py3-none-any.whl", hash = "sha256:bb5d2f385accbe3724cbd2c28808d7d69375b09a6c03e8e3ef2980df20929c6e"},
-    {file = "automaton-3.0.1.tar.gz", hash = "sha256:1004a4787c241a62ccab255c414207b93f5fdc8509a022590d05bdeb3807cb76"},
-]
 binaryornot = [
     {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"},
     {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"},
 ]
-cachetools = [
-    {file = "cachetools-5.3.0-py3-none-any.whl", hash = "sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4"},
-    {file = "cachetools-5.3.0.tar.gz", hash = "sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14"},
-]
 certifi = [
     {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
     {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
@@ -1800,10 +1653,6 @@
     {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"},
     {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
 ]
-fasteners = [
-    {file = "fasteners-0.18-py3-none-any.whl", hash = "sha256:1d4caf5f8db57b0e4107d94fd5a1d02510a450dced6ca77d1839064c1bacf20c"},
-    {file = "fasteners-0.18.tar.gz", hash = "sha256:cb7c13ef91e0c7e4fe4af38ecaf6b904ec3f5ce0dda06d34924b6b74b869d953"},
-]
 flake8 = [
     {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
     {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
@@ -1888,10 +1737,6 @@
     {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"},
     {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"},
 ]
-futurist = [
-    {file = "futurist-2.4.1-py3-none-any.whl", hash = "sha256:3ef3a1f63eca3c4f6ebc8f4cff0bb1492241a0df93622e0bf3e6e90ca822e0e0"},
-    {file = "futurist-2.4.1.tar.gz", hash = "sha256:9c1760a877c0fe3260d04b6a6d4352a6d25ac58e483f1d6cd495e33dc3740ff7"},
-]
 ghp-import = [
     {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
     {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
@@ -2209,10 +2054,6 @@
     {file = "netifaces-0.11.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1"},
     {file = "netifaces-0.11.0.tar.gz", hash = "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32"},
 ]
-networkx = [
-    {file = "networkx-3.0-py3-none-any.whl", hash = "sha256:58058d66b1818043527244fab9d41a51fcd7dcc271748015f3c181b8a90c8e2e"},
-    {file = "networkx-3.0.tar.gz", hash = "sha256:9a9992345353618ae98339c2b63d8201c381c2944f38a2ab49cb45a4c667e412"},
-]
 openstacksdk = [
     {file = "openstacksdk-0.61.0-py3-none-any.whl", hash = "sha256:9894d3d510563dcfc50c4755287dbfbf98def1f37caf2cfc15e9d0e1fd5d9a41"},
     {file = "openstacksdk-0.61.0.tar.gz", hash = "sha256:3eed308871230f0c53a8f58b6c5a358b184080c6b2c6bc69ab088eea057aa127"},
@@ -2245,10 +2086,6 @@
     {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
     {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
 ]
-prettytable = [
-    {file = "prettytable-3.6.0-py3-none-any.whl", hash = "sha256:3b767129491767a3a5108e6f305cbaa650f8020a7db5dfe994a2df7ef7bad0fe"},
-    {file = "prettytable-3.6.0.tar.gz", hash = "sha256:2e0026af955b4ea67b22122f310b90eae890738c08cb0458693a49b6221530ac"},
-]
 py = [
     {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
     {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
@@ -2299,10 +2136,6 @@
     {file = "pydantic-1.10.4-py3-none-any.whl", hash = "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774"},
     {file = "pydantic-1.10.4.tar.gz", hash = "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648"},
 ]
-pydot = [
-    {file = "pydot-1.4.2-py2.py3-none-any.whl", hash = "sha256:66c98190c65b8d2e2382a441b4c0edfdb4f4c025ef9cb9874de478fb0793a451"},
-    {file = "pydot-1.4.2.tar.gz", hash = "sha256:248081a39bcb56784deb018977e428605c1c758f10897a339fce1dd728ff007d"},
-]
 pyflakes = [
     {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
     {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
@@ -2560,22 +2393,10 @@
     {file = "stevedore-4.1.1-py3-none-any.whl", hash = "sha256:aa6436565c069b2946fe4ebff07f5041e0c8bf18c7376dd29edf80cf7d524e4e"},
     {file = "stevedore-4.1.1.tar.gz", hash = "sha256:7f8aeb6e3f90f96832c301bff21a7eb5eefbe894c88c506483d355565d88cc1a"},
 ]
-structlog = [
-    {file = "structlog-22.3.0-py3-none-any.whl", hash = "sha256:b403f344f902b220648fa9f286a23c0cc5439a5844d271fec40562dbadbc70ad"},
-    {file = "structlog-22.3.0.tar.gz", hash = "sha256:e7509391f215e4afb88b1b80fa3ea074be57a5a17d794bd436a5c949da023333"},
-]
 subprocess-tee = [
     {file = "subprocess-tee-0.4.1.tar.gz", hash = "sha256:b3c124993f8b88d1eb1c2fde0bc2069787eac720ba88771cba17e8c93324825d"},
     {file = "subprocess_tee-0.4.1-py3-none-any.whl", hash = "sha256:eca56973a1c1237093c2055b2731bcaab784683b83f22c76f26e4c5763402e28"},
 ]
-taskflow = [
-    {file = "taskflow-5.1.0-py3-none-any.whl", hash = "sha256:cf845dd9fe7daea755f4d4211bfe3eac20bc842ac15a858846d1aa9cf14aa066"},
-    {file = "taskflow-5.1.0.tar.gz", hash = "sha256:ec9028486b7e233fb790f30fa07b9e82fc6bd5d56a8cff99f378fd1d567180d0"},
-]
-tenacity = [
-    {file = "tenacity-8.1.0-py3-none-any.whl", hash = "sha256:35525cd47f82830069f0d6b73f7eb83bc5b73ee2fff0437952cedf98b27653ac"},
-    {file = "tenacity-8.1.0.tar.gz", hash = "sha256:e48c437fdf9340f5666b92cd7990e96bc5fc955e1298baf4a907e3972067a445"},
-]
 text-unidecode = [
     {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
     {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
@@ -2666,10 +2487,6 @@
     {file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"},
     {file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"},
 ]
-wcwidth = [
-    {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
-    {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
-]
 wrapt = [
     {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
     {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
diff --git a/pyproject.toml b/pyproject.toml
index dc15848..6948ea2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,13 +13,12 @@
 pydantic = { version = "^1.10.4", optional = true }
 schematics = { version = "^2.1.1", optional = true }
 pykube-ng = { version = "^22.7.0", optional = true }
-structlog = { version = "^22.1.0", optional = true }
-taskflow = { version = "^5.0.0", optional = true }
 tomli = { version = "^2.0.1", optional = true }
 jsonnet = { version = "^0.18.0", optional = true }
 kopf = { version = "^1.36.0", optional = true, extras = ["uvloop"] }
 openstacksdk = "<0.99.0"
 docker-image-py = "^0.1.12"
+"oslo.serialization" = "^5.0.0"
 
 [tool.poetry.extras]
 operator = [
diff --git a/roles/atmosphere/defaults/main.yml b/roles/atmosphere/defaults/main.yml
index 3d09da7..377ff32 100644
--- a/roles/atmosphere/defaults/main.yml
+++ b/roles/atmosphere/defaults/main.yml
@@ -4,7 +4,6 @@
   image_repository: "{{ atmosphere_image_repository | default('') }}"
   kube_prometheus_stack:
     overrides: "{{ kube_prometheus_stack_values | default({}) }}"
-  issuer: "{{ atmosphere_issuer_config }}"
   opsgenie: "{{ atmosphere_opsgenie_config | default({}) }}"
 
 atmosphere_cloud_spec: {}
diff --git a/roles/certificates/README.md b/roles/certificates/README.md
deleted file mode 100644
index 141cc1b..0000000
--- a/roles/certificates/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# `certificates`
-
-!!! warning
-
-   This is a legacy role that is meant to be phased out eventually when there
-   is no need for the control-plane systems to have the CA installed on them.
diff --git a/roles/certificates/tasks/main.yml b/roles/certificates/tasks/main.yml
deleted file mode 100644
index e47b897..0000000
--- a/roles/certificates/tasks/main.yml
+++ /dev/null
@@ -1,40 +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: Bootstrap PKI
-  when:
-    - atmosphere_issuer_config.type is defined
-    - atmosphere_issuer_config.type in ("self-signed", "ca")
-  block:
-    - name: Wait till the secret is created
-      kubernetes.core.k8s_info:
-        api_version: v1
-        kind: Secret
-        name: "{{ (atmosphere_issuer_config.type == 'self-signed') | ternary('cert-manager-selfsigned-ca', 'cert-manager-issuer-ca') }}"
-        namespace: cert-manager
-        wait: true
-        wait_sleep: 1
-        wait_timeout: 600
-      register: _openstack_helm_root_secret
-
-    - name: Copy CA certificate on host
-      ansible.builtin.copy:
-        content: "{{ _openstack_helm_root_secret.resources[0].data['tls.crt'] | b64decode }}"
-        dest: "/usr/local/share/ca-certificates/self-signed-osh-ca.crt"
-        mode: "0644"
-
-    - name: Update CA certificates on host
-      ansible.builtin.command:
-        cmd: update-ca-certificates
-      changed_when: false
diff --git a/roles/cluster_issuer/README.md b/roles/cluster_issuer/README.md
new file mode 100644
index 0000000..fe9259c
--- /dev/null
+++ b/roles/cluster_issuer/README.md
@@ -0,0 +1,107 @@
+# `cluster_issuer`
+
+Atmosphere simplifies all the management of your SSL certificates for all of
+your API endpoints by automatically issuing and renewing certificates for you.
+
+## ACME
+
+Atmosphere uses the [ACME](https://tools.ietf.org/html/rfc8555) protocol by
+default to request certificates from [LetsEncrypt](https://letsencrypt.org/).
+
+This is configured to work out of the box if your APIs are publicly accessible,
+you just need to configure an email address.
+
+```yaml
+cluster_issuer_acme_email: user@example.com
+```
+
+If you're running your own internal ACME server, you can configure Atmosphere to
+point towards it by setting the `cluster_issuer_acme_server` variable.
+
+```yaml
+cluster_issuer_acme_server: https://acme.example.com
+cluster_issuer_acme_email: user@example.com
+```
+
+### DNS-01 challenges
+
+Atmosphere uses the `HTTP-01` solver by default, which means that as long as
+your ACME server can reach your API, you don't need to do anything else.
+
+If your ACME server cannot reach your API, you will need to use the DNS-01
+challenges which require you to configure your DNS provider.
+
+#### RFC2136
+
+If you have DNS server that supports RFC2136, you can use it to solve the DNS
+challenges, you can use the following configuration:
+
+```yaml
+cluster_issuer_acme_email: user@example.com
+cluster_issuer_acme_solver: rfc2136
+cluster_issuer_acme_rfc2136_nameserver: <NAMESERVER>:<PORT>
+cluster_issuer_acme_rfc2136_tsig_algorithm: <ALGORITHM>
+cluster_issuer_acme_rfc2136_tsig_key_name: <KEY_NAME>
+cluster_issuer_acme_rfc2136_tsig_secret_key: <SECRET_KEY>
+```
+
+#### Route53
+
+If you are using Route53 to host the DNS for your domains, you can use the
+following configuration:
+
+```yaml
+cluster_issuer_acme_email: user@example.com
+cluster_issuer_acme_solver: route53
+cluster_issuer_acme_route53_region: <REGION>
+cluster_issuer_acme_route53_hosted_zone_id: <HOSTED_ZONE_ID>
+cluster_issuer_acme_route53_access_key_id: <AWS_ACCESS_KEY_ID>
+cluster_issuer_acme_route53_secret_access_key: <AWS_SECRET_ACCESS_KEY>
+```
+
+!!! note
+
+   You'll need to make sure that your AWS credentials have the correct
+   permissions to update the Route53 zone.
+
+## Using pre-existing CA
+
+If you have an existing CA that you'd like to use with Atmosphere, you can
+simply configure it by including the certificate and private key:
+
+```yaml
+cluster_issuer_type: ca
+cluster_issuer_ca_certificate: |
+  -----BEGIN CERTIFICATE-----
+  MIIDBjCCAe4CCQDQ3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
+  VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
+  ...
+  -----END CERTIFICATE-----
+cluster_issuer_ca_private_key: |
+  -----BEGIN RSA PRIVATE KEY-----
+  MIIEpAIBAAKCAQEAw3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
+  VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
+  ...
+  -----END RSA PRIVATE KEY-----
+```
+
+!!! note
+
+   If your issuer is an intermediate certificate, you will need to ensure that
+   they `certificate` key includes the full chain in the correct order of issuer,
+   intermediate(s) then root.
+
+## Self-signed certificates
+
+If you are in an environment which does not have a trusted certificate authority
+and it does not have access to the internet to be able to use LetsEncrypt, you
+can use self-signed certificates by adding the following to your inventory:
+
+```yaml
+cluster_issuer_type: self-signed
+```
+
+!!! warning
+
+   Self-signed certificates are not recommended for production environments,
+   they are only recommended for development and testing environments.
diff --git a/roles/cluster_issuer/defaults/main.yml b/roles/cluster_issuer/defaults/main.yml
new file mode 100644
index 0000000..b7bcfb9
--- /dev/null
+++ b/roles/cluster_issuer/defaults/main.yml
@@ -0,0 +1,53 @@
+# Copyright (c) 2023 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.
+
+cluster_issuer_name: "{{ atmosphere_ingress_cluster_issuer }}"
+cluster_issuer_type: acme
+
+cluster_issuer_acme_server: https://acme-v02.api.letsencrypt.org/directory
+# cluster_issuer_acme_email:
+cluster_issuer_acme_private_key_secret_name: cert-manager-issuer-account-key
+
+cluster_issuer_acme_solver: http01
+
+cluster_issuer_acme_http01_ingress_class: "{{ atmosphere_ingress_class_name }}"
+
+cluster_issuer_acme_rfc2136_secret_name: cert-manager-issuer-tsig-secret-key
+# cluster_issuer_acme_rfc2136_nameserver: <NAMESERVER>:<PORT>
+# cluster_issuer_acme_rfc2136_tsig_algorithm: <ALGORITHM>
+# cluster_issuer_acme_rfc2136_tsig_key_name: <KEY_NAME>
+# cluster_issuer_acme_rfc2136_tsig_secret_key: <SECRET_KEY>
+
+cluster_issuer_acme_route53_secret_name: cert-manager-issuer-route53-credentials
+# cluster_issuer_acme_route53_region: <REGION>
+# cluster_issuer_acme_route53_hosted_zone_id: <HOSTED_ZONE_ID>
+# cluster_issuer_acme_route53_access_key_id: <AWS_ACCESS_KEY_ID>
+# cluster_issuer_acme_route53_secret_access_key: <AWS_SECRET_ACCESS_KEY>
+
+cluster_issuer_ca_secret_name: cert-manager-issuer-ca
+# cluster_issuer_ca_certificate: |
+#   -----BEGIN CERTIFICATE-----
+#   MIIDBjCCAe4CCQDQ3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
+#   VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
+#   ...
+#   -----END CERTIFICATE-----
+# cluster_issuer_ca_private_key: |
+#   -----BEGIN RSA PRIVATE KEY-----
+#   MIIEpAIBAAKCAQEAw3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
+#   VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
+#   ...
+#   -----END RSA PRIVATE KEY-----
+
+cluster_issuer_self_signed_certificate_name: self-signed-ca
+cluster_issuer_self_signed_secret_name: cert-manager-selfsigned-ca
diff --git a/roles/certificates/meta/main.yml b/roles/cluster_issuer/handlers/main.yml
similarity index 62%
copy from roles/certificates/meta/main.yml
copy to roles/cluster_issuer/handlers/main.yml
index 921f51d..711faa3 100644
--- a/roles/certificates/meta/main.yml
+++ b/roles/cluster_issuer/handlers/main.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2023 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
@@ -12,17 +12,6 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-galaxy_info:
-  author: VEXXHOST, Inc.
-  description: Ansible role for distributing certificates
-  license: Apache-2.0
-  min_ansible_version: 5.5.0
-  standalone: false
-  platforms:
-    - name: Ubuntu
-      versions:
-        - focal
-
-dependencies:
-  - role: defaults
-  - role: atmosphere
+- name: Update CA certificates on host
+  ansible.builtin.command:
+    cmd: update-ca-certificates
diff --git a/roles/certificates/meta/main.yml b/roles/cluster_issuer/meta/main.yml
similarity index 86%
rename from roles/certificates/meta/main.yml
rename to roles/cluster_issuer/meta/main.yml
index 921f51d..517631b 100644
--- a/roles/certificates/meta/main.yml
+++ b/roles/cluster_issuer/meta/main.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2023 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
@@ -14,7 +14,7 @@
 
 galaxy_info:
   author: VEXXHOST, Inc.
-  description: Ansible role for distributing certificates
+  description: Ansible role for creating ClusterIssuer
   license: Apache-2.0
   min_ansible_version: 5.5.0
   standalone: false
@@ -25,4 +25,3 @@
 
 dependencies:
   - role: defaults
-  - role: atmosphere
diff --git a/roles/cluster_issuer/tasks/main.yml b/roles/cluster_issuer/tasks/main.yml
new file mode 100644
index 0000000..5416426
--- /dev/null
+++ b/roles/cluster_issuer/tasks/main.yml
@@ -0,0 +1,49 @@
+# Copyright (c) 2023 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: Create self-signed cluster issuer
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: cert-manager.io/v1
+      kind: ClusterIssuer
+      metadata:
+        name: self-signed
+      spec:
+        selfSigned: {}
+
+- name: Import tasks for ClusterIssuer type
+  ansible.builtin.include_tasks: "type/{{ cluster_issuer_type }}/main.yml"
+
+- name: Bootstrap PKI
+  when: cluster_issuer_type in ("self-signed", "ca")
+  block:
+    - name: Wait till the secret is created
+      kubernetes.core.k8s_info:
+        api_version: v1
+        kind: Secret
+        name: "{{ (cluster_issuer_type == 'self-signed') | ternary(cluster_issuer_self_signed_secret_name, cluster_issuer_ca_secret_name) }}"
+        namespace: cert-manager
+        wait: true
+        wait_sleep: 1
+        wait_timeout: 600
+      register: _cluster_issuer_ca_secret
+
+    - name: Copy CA certificate on host
+      ansible.builtin.copy:
+        content: "{{ _cluster_issuer_ca_secret.resources[0].data['tls.crt'] | b64decode }}"
+        dest: /usr/local/share/ca-certificates/atmosphere.crt
+        mode: 0644
+      notify:
+        - Update CA certificates on host
diff --git a/roles/certificates/meta/main.yml b/roles/cluster_issuer/tasks/type/acme/main.yml
similarity index 62%
copy from roles/certificates/meta/main.yml
copy to roles/cluster_issuer/tasks/type/acme/main.yml
index 921f51d..5adb9f8 100644
--- a/roles/certificates/meta/main.yml
+++ b/roles/cluster_issuer/tasks/type/acme/main.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2023 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
@@ -12,17 +12,5 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-galaxy_info:
-  author: VEXXHOST, Inc.
-  description: Ansible role for distributing certificates
-  license: Apache-2.0
-  min_ansible_version: 5.5.0
-  standalone: false
-  platforms:
-    - name: Ubuntu
-      versions:
-        - focal
-
-dependencies:
-  - role: defaults
-  - role: atmosphere
+- name: Import tasks for solver type
+  ansible.builtin.include_tasks: "solver/{{ cluster_issuer_acme_solver }}.yml"
diff --git a/roles/cluster_issuer/tasks/type/acme/solver/http01.yml b/roles/cluster_issuer/tasks/type/acme/solver/http01.yml
new file mode 100644
index 0000000..363dc64
--- /dev/null
+++ b/roles/cluster_issuer/tasks/type/acme/solver/http01.yml
@@ -0,0 +1,32 @@
+# Copyright (c) 2023 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: Create ClusterIssuer
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: cert-manager.io/v1
+      kind: ClusterIssuer
+      metadata:
+        name: "{{ cluster_issuer_name }}"
+      spec:
+        acme:
+          email: "{{ cluster_issuer_acme_email }}"
+          server: "{{ cluster_issuer_acme_server }}"
+          privateKeySecretRef:
+            name: "{{ cluster_issuer_acme_private_key_secret_name }}"
+          solvers:
+            - http01:
+                ingress:
+                  class: "{{ cluster_issuer_acme_http01_ingress_class }}"
diff --git a/roles/cluster_issuer/tasks/type/acme/solver/rfc2136.yml b/roles/cluster_issuer/tasks/type/acme/solver/rfc2136.yml
new file mode 100644
index 0000000..60306ea
--- /dev/null
+++ b/roles/cluster_issuer/tasks/type/acme/solver/rfc2136.yml
@@ -0,0 +1,46 @@
+# Copyright (c) 2023 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: Create ClusterIssuer
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      - apiVersion: v1
+        kind: Secret
+        metadata:
+          name: "{{ cluster_issuer_acme_rfc2136_secret_name }}"
+          namespace: cert-manager
+        type: Opaque
+        stringData:
+          tsig-secret-key: "{{ cluster_issuer_acme_rfc2136_tsig_secret_key }}"
+
+      - apiVersion: cert-manager.io/v1
+        kind: ClusterIssuer
+        metadata:
+          name: "{{ cluster_issuer_name }}"
+        spec:
+          acme:
+            email: "{{ cluster_issuer_acme_email }}"
+            server: "{{ cluster_issuer_acme_server }}"
+            privateKeySecretRef:
+              name: "{{ cluster_issuer_acme_private_key_secret_name }}"
+            solvers:
+              - dns01:
+                  rfc2136:
+                    nameserver: "{{ cluster_issuer_acme_rfc2136_nameserver }}"
+                    tsigAlgorithm: "{{ cluster_issuer_acme_rfc2136_tsig_algorithm }}"
+                    tsigKeyName: "{{ cluster_issuer_acme_rfc2136_tsig_key_name }}"
+                    tsigSecretSecretRef:
+                      name: "{{ cluster_issuer_acme_rfc2136_secret_name }}"
+                      key: tsig-secret-key
diff --git a/roles/cluster_issuer/tasks/type/acme/solver/route53.yml b/roles/cluster_issuer/tasks/type/acme/solver/route53.yml
new file mode 100644
index 0000000..fa805d6
--- /dev/null
+++ b/roles/cluster_issuer/tasks/type/acme/solver/route53.yml
@@ -0,0 +1,46 @@
+# Copyright (c) 2023 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: Create ClusterIssuer
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      - apiVersion: v1
+        kind: Secret
+        metadata:
+          name: "{{ cluster_issuer_acme_route53_secret_name }}"
+          namespace: cert-manager
+        type: Opaque
+        stringData:
+          secret-access-key: "{{ cluster_issuer_acme_route53_secret_access_key }}"
+
+      - apiVersion: cert-manager.io/v1
+        kind: ClusterIssuer
+        metadata:
+          name: "{{ cluster_issuer_name }}"
+        spec:
+          acme:
+            email: "{{ cluster_issuer_acme_email }}"
+            server: "{{ cluster_issuer_acme_server }}"
+            privateKeySecretRef:
+              name: "{{ cluster_issuer_acme_private_key_secret_name }}"
+            solvers:
+              - dns01:
+                  route53:
+                    region: "{{ cluster_issuer_acme_route53_region }}"
+                    hostedZoneID: "{{ cluster_issuer_acme_route53_hosted_zone_id }}"
+                    accessKeyID: "{{ cluster_issuer_acme_route53_access_key_id }}"
+                    secretAccessKeySecretRef:
+                      name: "{{ cluster_issuer_acme_route53_secret_name }}"
+                      key: secret-access-key
diff --git a/roles/cluster_issuer/tasks/type/ca/main.yml b/roles/cluster_issuer/tasks/type/ca/main.yml
new file mode 100644
index 0000000..dd5394b
--- /dev/null
+++ b/roles/cluster_issuer/tasks/type/ca/main.yml
@@ -0,0 +1,35 @@
+# Copyright (c) 2023 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: Create ClusterIssuer
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      - apiVersion: v1
+        kind: Secret
+        metadata:
+          name: "{{ cluster_issuer_ca_secret_name }}"
+          namespace: cert-manager
+        type: kubernetes.io/tls
+        data:
+          tls.crt: "{{ cluster_issuer_ca_certificate }}"
+          tls.key: "{{ cluster_issuer_ca_private_key }}"
+
+      - apiVersion: cert-manager.io/v1
+        kind: ClusterIssuer
+        metadata:
+          name: "{{ cluster_issuer_name }}"
+        spec:
+          ca:
+            secretName: "{{ cluster_issuer_ca_secret_name }}"
diff --git a/roles/cluster_issuer/tasks/type/self-signed/main.yml b/roles/cluster_issuer/tasks/type/self-signed/main.yml
new file mode 100644
index 0000000..8461003
--- /dev/null
+++ b/roles/cluster_issuer/tasks/type/self-signed/main.yml
@@ -0,0 +1,43 @@
+# Copyright (c) 2023 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: Create ClusterIssuer
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      - apiVersion: cert-manager.io/v1
+        kind: Certificate
+        metadata:
+          name: "{{ cluster_issuer_self_signed_certificate_name }}"
+          namespace: cert-manager
+        spec:
+          isCA: true
+          commonName: selfsigned-ca
+          secretName: "{{ cluster_issuer_self_signed_secret_name }}"
+          duration: 87600h
+          renewBefore: 360h
+          privateKey:
+            algorithm: ECDSA
+            size: 256
+          issuerRef:
+            kind: ClusterIssuer
+            name: self-signed
+
+      - apiVersion: cert-manager.io/v1
+        kind: ClusterIssuer
+        metadata:
+          name: "{{ cluster_issuer_name }}"
+        spec:
+          ca:
+            secretName: "{{ cluster_issuer_self_signed_secret_name }}"
diff --git a/roles/openstack_cli/meta/main.yml b/roles/openstack_cli/meta/main.yml
index 3108e1f..9b0260e 100644
--- a/roles/openstack_cli/meta/main.yml
+++ b/roles/openstack_cli/meta/main.yml
@@ -25,4 +25,3 @@
 
 dependencies:
   - role: defaults
-  - role: certificates
diff --git a/roles/openstacksdk/meta/main.yml b/roles/openstacksdk/meta/main.yml
index 285d65a..eb42ef3 100644
--- a/roles/openstacksdk/meta/main.yml
+++ b/roles/openstacksdk/meta/main.yml
@@ -25,4 +25,3 @@
 
 dependencies:
   - role: defaults
-  - role: certificates