feat(ingress): enable overriding/disabling
diff --git a/atmosphere/flows.py b/atmosphere/flows.py
index 55d490b..c5f5d0d 100644
--- a/atmosphere/flows.py
+++ b/atmosphere/flows.py
@@ -39,6 +39,7 @@
version=constants.HELM_RELEASE_CERT_MANAGER_VERSION,
values=constants.HELM_RELEASE_CERT_MANAGER_VALUES,
),
+ *cert_manager.issuer_tasks_from_config(config.issuer),
# monitoring
v1.ApplyNamespaceTask(name=constants.NAMESPACE_MONITORING),
flux.ApplyHelmRepositoryTask(
@@ -89,19 +90,7 @@
values=constants.HELM_RELEASE_PXC_OPERATOR_VALUES,
),
openstack_helm.ApplyPerconaXtraDBClusterTask(),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_REPOSITORY_INGRESS_NGINX,
- url="https://kubernetes.github.io/ingress-nginx",
- ),
- flux.ApplyHelmReleaseTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_RELEASE_INGRESS_NGINX_NAME,
- repository=constants.HELM_REPOSITORY_INGRESS_NGINX,
- chart=constants.HELM_RELEASE_INGRESS_NGINX_NAME,
- version=constants.HELM_RELEASE_INGRESS_NGINX_VERSION,
- values=constants.HELM_RELEASE_INGRESS_NGINX_VALUES,
- ),
+ *openstack_helm.ingress_nginx_tasks_from_config(config.ingress_nginx),
flux.ApplyHelmRepositoryTask(
namespace=constants.NAMESPACE_OPENSTACK,
name=constants.HELM_REPOSITORY_OPENSTACK_HELM_INFRA,
@@ -143,9 +132,6 @@
),
)
- for t in cert_manager.ApplyIssuerTask.from_config(config.issuer):
- flow.add(t)
-
if config.memcached.enabled:
flow.add(
openstack_helm.ApplyReleaseSecretTask(
diff --git a/atmosphere/models/config.py b/atmosphere/models/config.py
index f5a7fe9..be20536 100644
--- a/atmosphere/models/config.py
+++ b/atmosphere/models/config.py
@@ -77,21 +77,29 @@
TYPE = "self-signed"
+class ChartConfig(base.Model):
+ enabled = types.BooleanType(default=True, required=True)
+ overrides = types.DictType(types.BaseType(), default={})
+
+
class MemcachedImagesConfig(base.Model):
memcached = types.StringType(default="docker.io/library/memcached:1.6.17")
exporter = types.StringType(default="quay.io/prometheus/memcached-exporter:v0.10.0")
-class MemcachedConfig(base.Model):
- enabled = types.BooleanType(default=True)
+class MemcachedChartConfig(ChartConfig):
secret_key = types.StringType(required=True)
images = types.ModelType(MemcachedImagesConfig, default=MemcachedImagesConfig())
- overrides = types.DictType(types.BaseType(), default={})
+
+
+class IngressNginxConfig(ChartConfig):
+ pass
class Config(base.Model):
+ ingress_nginx = types.ModelType(IngressNginxConfig, default=IngressNginxConfig())
memcached = types.ModelType(
- MemcachedConfig, default=MemcachedConfig(), required=True
+ MemcachedChartConfig, default=MemcachedChartConfig(), required=True
)
issuer = types.PolyModelType(
[AcmeIssuerConfig, CaIssuerConfig, SelfSignedIssuerConfig],
diff --git a/atmosphere/tasks/composite/openstack_helm.py b/atmosphere/tasks/composite/openstack_helm.py
index 22d221d..96d0446 100644
--- a/atmosphere/tasks/composite/openstack_helm.py
+++ b/atmosphere/tasks/composite/openstack_helm.py
@@ -49,6 +49,33 @@
)
+def ingress_nginx_tasks_from_config(config: config.IngressNginxConfig):
+ if not config.enabled:
+ return []
+
+ values = mergedeep.merge(
+ {},
+ constants.HELM_RELEASE_INGRESS_NGINX_VALUES,
+ config.overrides,
+ )
+
+ return [
+ flux.ApplyHelmRepositoryTask(
+ namespace=constants.NAMESPACE_OPENSTACK,
+ name=constants.HELM_REPOSITORY_INGRESS_NGINX,
+ url=constants.HELM_REPOSITORY_INGRESS_NGINX_URL,
+ ),
+ flux.ApplyHelmReleaseTask(
+ namespace=constants.NAMESPACE_OPENSTACK,
+ name=constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ repository=constants.HELM_REPOSITORY_INGRESS_NGINX,
+ chart=constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ version=constants.HELM_RELEASE_INGRESS_NGINX_VERSION,
+ values=values,
+ ),
+ ]
+
+
class PerconaXtraDBCluster(pykube.objects.NamespacedAPIObject):
version = "pxc.percona.com/v1-10-0"
endpoint = "perconaxtradbclusters"
diff --git a/atmosphere/tasks/constants.py b/atmosphere/tasks/constants.py
index d816bf8..3073379 100644
--- a/atmosphere/tasks/constants.py
+++ b/atmosphere/tasks/constants.py
@@ -10,7 +10,10 @@
HELM_REPOSITORY_BITNAMI = "bitnami"
HELM_REPOSITORY_CEPH = "ceph"
HELM_REPOSITORY_COREDNS = "coredns"
+
HELM_REPOSITORY_INGRESS_NGINX = "ingress-nginx"
+HELM_REPOSITORY_INGRESS_NGINX_URL = "https://kubernetes.github.io/ingress-nginx"
+
HELM_REPOSITORY_JETSTACK = "jetstack"
HELM_REPOSITORY_NODE_FEATURE_DISCOVERY = "node-feature-discovery"
HELM_REPOSITORY_OPENSTACK_HELM = "openstack-helm"
diff --git a/atmosphere/tasks/kubernetes/cert_manager.py b/atmosphere/tasks/kubernetes/cert_manager.py
index 95f1431..2d2caca 100644
--- a/atmosphere/tasks/kubernetes/cert_manager.py
+++ b/atmosphere/tasks/kubernetes/cert_manager.py
@@ -77,146 +77,146 @@
},
)
- @classmethod
- def from_config(cls, config: config.Issuer) -> list:
- objects = []
- if config.type == "acme":
- spec = {
- "acme": {
- "email": config.email,
- "server": config.server,
- "privateKeySecretRef": {
- "name": "cert-manager-issuer-account-key",
+def issuer_tasks_from_config(config: config.Issuer) -> list:
+ objects = []
+
+ 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",
+ },
},
},
- }
-
- 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_OPENSTACK,
- "cert-manager-issuer-tsig-secret-key",
- data={
- "tsig-secret-key": base64.encode_as_text(
- 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_OPENSTACK,
- "cert-manager-issuer-route53-credentials",
- data={
- "secret-access-key": base64.encode_as_text(
- 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.
+ ]
+ 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_OPENSTACK,
- "cert-manager-issuer-ca",
+ "cert-manager-issuer-tsig-secret-key",
data={
- "tls.crt": base64.encode_as_text(config.certificate),
- "tls.key": base64.encode_as_text(config.private_key),
+ "tsig-secret-key": base64.encode_as_text(
+ config.solver.tsig_secret
+ ),
},
)
)
- 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 += [
- ApplyIssuerTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name="self-signed",
- spec={
- "selfSigned": {},
- },
- ),
- ApplyCertificateTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- 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": "Issuer",
- "name": "self-signed",
+ 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",
+ },
},
},
- ),
+ },
]
-
- spec = {
- "ca": {
- "secretName": "cert-manager-selfsigned-ca",
- }
- }
-
- return objects + [
- ApplyIssuerTask(
- namespace=constants.NAMESPACE_OPENSTACK, name="openstack", spec=spec
+ 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_OPENSTACK,
+ "cert-manager-issuer-route53-credentials",
+ data={
+ "secret-access-key": base64.encode_as_text(
+ 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_OPENSTACK,
+ "cert-manager-issuer-ca",
+ data={
+ "tls.crt": base64.encode_as_text(config.certificate),
+ "tls.key": base64.encode_as_text(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 += [
+ ApplyIssuerTask(
+ namespace=constants.NAMESPACE_OPENSTACK,
+ name="self-signed",
+ spec={
+ "selfSigned": {},
+ },
+ ),
+ ApplyCertificateTask(
+ namespace=constants.NAMESPACE_OPENSTACK,
+ 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": "Issuer",
+ "name": "self-signed",
+ },
+ },
+ ),
]
+
+ spec = {
+ "ca": {
+ "secretName": "cert-manager-selfsigned-ca",
+ }
+ }
+
+ return objects + [
+ ApplyIssuerTask(
+ namespace=constants.NAMESPACE_OPENSTACK, name="openstack", spec=spec
+ )
+ ]
diff --git a/atmosphere/tests/unit/tasks/composite/test_openstack_helm.py b/atmosphere/tests/unit/tasks/composite/test_openstack_helm.py
new file mode 100644
index 0000000..efb161b
--- /dev/null
+++ b/atmosphere/tests/unit/tasks/composite/test_openstack_helm.py
@@ -0,0 +1,129 @@
+import textwrap
+
+import pytest
+
+from atmosphere.models import config
+from atmosphere.tasks import constants
+from atmosphere.tasks.composite import openstack_helm
+
+
+@pytest.mark.parametrize(
+ "cfg_data,expected",
+ [
+ pytest.param(
+ textwrap.dedent(
+ """\
+ """
+ ),
+ [
+ {
+ "apiVersion": "source.toolkit.fluxcd.io/v1beta2",
+ "kind": "HelmRepository",
+ "metadata": {
+ "name": constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ "namespace": constants.NAMESPACE_OPENSTACK,
+ },
+ "spec": {
+ "interval": "1m",
+ "url": constants.HELM_REPOSITORY_INGRESS_NGINX_URL,
+ },
+ },
+ {
+ "apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
+ "kind": "HelmRelease",
+ "metadata": {
+ "name": constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ "namespace": constants.NAMESPACE_OPENSTACK,
+ },
+ "spec": {
+ "chart": {
+ "spec": {
+ "chart": constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ "sourceRef": {
+ "kind": "HelmRepository",
+ "name": constants.HELM_REPOSITORY_INGRESS_NGINX,
+ },
+ "version": constants.HELM_RELEASE_INGRESS_NGINX_VERSION,
+ }
+ },
+ "install": {"disableWait": True},
+ "interval": "60s",
+ "upgrade": {"disableWait": True},
+ "values": constants.HELM_RELEASE_INGRESS_NGINX_VALUES,
+ "valuesFrom": [],
+ },
+ },
+ ],
+ id="default",
+ ),
+ pytest.param(
+ textwrap.dedent(
+ """\
+ [ingress_nginx.overrides]
+ foo = "bar"
+ """
+ ),
+ [
+ {
+ "apiVersion": "source.toolkit.fluxcd.io/v1beta2",
+ "kind": "HelmRepository",
+ "metadata": {
+ "name": constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ "namespace": constants.NAMESPACE_OPENSTACK,
+ },
+ "spec": {
+ "interval": "1m",
+ "url": constants.HELM_REPOSITORY_INGRESS_NGINX_URL,
+ },
+ },
+ {
+ "apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
+ "kind": "HelmRelease",
+ "metadata": {
+ "name": constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ "namespace": constants.NAMESPACE_OPENSTACK,
+ },
+ "spec": {
+ "chart": {
+ "spec": {
+ "chart": constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ "sourceRef": {
+ "kind": "HelmRepository",
+ "name": constants.HELM_REPOSITORY_INGRESS_NGINX,
+ },
+ "version": constants.HELM_RELEASE_INGRESS_NGINX_VERSION,
+ }
+ },
+ "install": {"disableWait": True},
+ "interval": "60s",
+ "upgrade": {"disableWait": True},
+ "values": {
+ **constants.HELM_RELEASE_INGRESS_NGINX_VALUES,
+ "foo": "bar",
+ },
+ "valuesFrom": [],
+ },
+ },
+ ],
+ id="overrides",
+ ),
+ pytest.param(
+ textwrap.dedent(
+ """\
+ [ingress_nginx]
+ enabled = false
+ """
+ ),
+ [],
+ id="disabled",
+ ),
+ ],
+)
+def test_ingress_nginx_tasks_from_config(pykube, cfg_data, expected):
+ cfg = config.Config.from_string(cfg_data, validate=False)
+ cfg.ingress_nginx.validate()
+
+ assert [
+ t.generate_object().obj
+ for t in openstack_helm.ingress_nginx_tasks_from_config(cfg.ingress_nginx)
+ ] == expected
diff --git a/atmosphere/tests/unit/tasks/kubernetes/test_cert_manager.py b/atmosphere/tests/unit/tasks/kubernetes/test_cert_manager.py
index aecd126..4a24f18 100644
--- a/atmosphere/tests/unit/tasks/kubernetes/test_cert_manager.py
+++ b/atmosphere/tests/unit/tasks/kubernetes/test_cert_manager.py
@@ -304,5 +304,5 @@
cfg.issuer.validate()
assert [
t.generate_object().obj
- for t in cert_manager.ApplyIssuerTask.from_config(cfg.issuer)
+ for t in cert_manager.issuer_tasks_from_config(cfg.issuer)
] == expected