Add osh ingress (#249)
* feat: add OpenstackHelmIngress
* chore: drop certbuilder deps
* chore: shave more deps
* feat: added openstackhelmrabbitmqclusters
* fix: install cert-manager first
* test: fix integration tests
* test: fix e2e tests
* tests: describe and get all resources
* fix: change default image repo to be none
* fix: solve when no override_registry
* fix: add annotation + labels
* fix: move more reesources to helm
* fix: add more dependencies
* chore: move services out of flows
* chore: build dependencies
* fix: drops deps from ApplyPerconaXtraDBClusterTask
* fix: add wait_for_pxc role to avoid race conditions
* fix: solve rabbitmq for magnum
* fix: clean-up filter_annotations
* chore: increase wait_timeout for secret waiting
diff --git a/.gitignore b/.gitignore
index 548e34a..d40a05a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@
.pytest*
.coverage
.kube
+.hypothesis
diff --git a/atmosphere/cmd/operator.py b/atmosphere/cmd/operator.py
index e8c672f..f1e931b 100644
--- a/atmosphere/cmd/operator.py
+++ b/atmosphere/cmd/operator.py
@@ -1,20 +1,76 @@
-import os
-
import kopf
-from atmosphere import flows
-from atmosphere.models import config
-from atmosphere.operator import controllers # noqa: F401
+from atmosphere import clients
+from atmosphere.operator import constants, controllers, utils # noqa: F401
+from atmosphere.operator.api import objects, types
-@kopf.on.startup()
-def configure(settings: kopf.OperatorSettings, **_):
- settings.admission.server = kopf.WebhookServer(host=os.environ["POD_IP"])
- settings.admission.managed = "auto.atmosphere.vexxhost.com"
+@kopf.on.create(
+ constants.API_VERSION_ATMOSPHERE,
+ constants.KIND_OPENSTACK_HELM_RABBITMQ_CLUSTER,
+)
+@kopf.on.resume(
+ constants.API_VERSION_ATMOSPHERE,
+ constants.KIND_OPENSTACK_HELM_RABBITMQ_CLUSTER,
+)
+def create_openstack_helm_rabbitmq_cluster(
+ namespace: str, name: str, annotations: dict, labels: dict, spec: dict, **_
+):
+ api = clients.get_pykube_api()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=name,
+ namespace=namespace,
+ annotations=utils.filter_annotations(annotations),
+ labels=labels,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(**spec),
+ ).apply_rabbitmq_cluster()
-@kopf.on.startup()
-def startup(**_):
- cfg = config.Config.from_file()
- engine = flows.get_engine(cfg)
- engine.run()
+@kopf.on.delete(
+ constants.API_VERSION_ATMOSPHERE,
+ constants.KIND_OPENSTACK_HELM_RABBITMQ_CLUSTER,
+)
+def delete_openstack_helm_rabbitmq_cluster(namespace: str, name: str, spec: dict, **_):
+ api = clients.get_pykube_api()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=name,
+ namespace=namespace,
+ ),
+ 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/flows.py b/atmosphere/flows.py
index a1be369..6fd57ab 100644
--- a/atmosphere/flows.py
+++ b/atmosphere/flows.py
@@ -1,12 +1,308 @@
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, flux, v1
+from atmosphere.tasks.kubernetes import cert_manager, v1
def get_engine(config):
+ api = clients.get_pykube_api()
+
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_CEPH,
+ namespace=constants.NAMESPACE_KUBE_SYSTEM,
+ ),
+ spec=types.HelmRepositorySpec(
+ url="https://ceph.github.io/csi-charts",
+ ),
+ ).apply()
+
+ if config.ingress_nginx.enabled:
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_INGRESS_NGINX,
+ namespace=config.ingress_nginx.namespace,
+ ),
+ spec=types.HelmRepositorySpec(
+ url=constants.HELM_REPOSITORY_INGRESS_NGINX_URL,
+ ),
+ ).apply()
+ objects.HelmRelease(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ namespace=config.ingress_nginx.namespace,
+ ),
+ spec=types.HelmReleaseSpec(
+ chart=types.HelmChartTemplate(
+ spec=types.HelmChartTemplateSpec(
+ chart=constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ version=constants.HELM_RELEASE_INGRESS_NGINX_VERSION,
+ source_ref=types.CrossNamespaceObjectReference(
+ kind="HelmRepository",
+ name=constants.HELM_REPOSITORY_INGRESS_NGINX,
+ namespace=config.ingress_nginx.namespace,
+ ),
+ )
+ ),
+ values={
+ **constants.HELM_RELEASE_INGRESS_NGINX_VALUES,
+ **config.ingress_nginx.overrides,
+ },
+ ),
+ ).apply()
+
+ # NOTE(mnaser): We're running this first since we do get often timeouts
+ # when waiting for the self-signed certificate authority to
+ # be ready.
+ objects.Namespace(
+ api=api,
+ metadata=types.ObjectMeta(
+ name=constants.NAMESPACE_CERT_MANAGER,
+ ),
+ ).apply()
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_JETSTACK,
+ namespace=constants.NAMESPACE_CERT_MANAGER,
+ ),
+ spec=types.HelmRepositorySpec(
+ url="https://charts.jetstack.io",
+ ),
+ ).apply()
+ objects.HelmRelease(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_RELEASE_CERT_MANAGER_NAME,
+ namespace=constants.NAMESPACE_CERT_MANAGER,
+ ),
+ spec=types.HelmReleaseSpec(
+ chart=types.HelmChartTemplate(
+ spec=types.HelmChartTemplateSpec(
+ chart=constants.HELM_RELEASE_CERT_MANAGER_NAME,
+ version=constants.HELM_RELEASE_CERT_MANAGER_VERSION,
+ source_ref=types.CrossNamespaceObjectReference(
+ kind="HelmRepository",
+ name=constants.HELM_REPOSITORY_JETSTACK,
+ namespace=constants.NAMESPACE_CERT_MANAGER,
+ ),
+ )
+ ),
+ depends_on=[
+ types.NamespacedObjectReference(
+ name=constants.HELM_RELEASE_INGRESS_NGINX_NAME,
+ namespace=config.ingress_nginx.namespace,
+ )
+ ],
+ values=constants.HELM_RELEASE_CERT_MANAGER_VALUES,
+ ),
+ ).apply()
+
+ objects.Namespace(
+ api=api,
+ metadata=types.ObjectMeta(
+ name=constants.NAMESPACE_MONITORING,
+ ),
+ ).apply()
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_NODE_FEATURE_DISCOVERY,
+ namespace=constants.NAMESPACE_MONITORING,
+ ),
+ spec=types.HelmRepositorySpec(
+ url="https://kubernetes-sigs.github.io/node-feature-discovery/charts",
+ ),
+ ).apply()
+ objects.HelmRelease(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="node-feature-discovery",
+ namespace=constants.NAMESPACE_MONITORING,
+ ),
+ spec=types.HelmReleaseSpec(
+ chart=types.HelmChartTemplate(
+ spec=types.HelmChartTemplateSpec(
+ chart="node-feature-discovery",
+ version="0.11.2",
+ source_ref=types.CrossNamespaceObjectReference(
+ kind="HelmRepository",
+ name=constants.HELM_REPOSITORY_NODE_FEATURE_DISCOVERY,
+ namespace=constants.NAMESPACE_MONITORING,
+ ),
+ )
+ ),
+ values=constants.HELM_RELEASE_NODE_FEATURE_DISCOVERY_VALUES,
+ ),
+ ).apply()
+
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_BITNAMI,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ spec=types.HelmRepositorySpec(
+ url="https://charts.bitnami.com/bitnami",
+ ),
+ ).apply()
+ objects.HelmRelease(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_RELEASE_RABBITMQ_OPERATOR_NAME,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ spec=types.HelmReleaseSpec(
+ chart=types.HelmChartTemplate(
+ spec=types.HelmChartTemplateSpec(
+ chart=constants.HELM_RELEASE_RABBITMQ_OPERATOR_NAME,
+ version=constants.HELM_RELEASE_RABBITMQ_OPERATOR_VERSION,
+ source_ref=types.CrossNamespaceObjectReference(
+ kind="HelmRepository",
+ name=constants.HELM_REPOSITORY_BITNAMI,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ )
+ ),
+ depends_on=[
+ types.NamespacedObjectReference(
+ name=constants.HELM_RELEASE_CERT_MANAGER_NAME,
+ namespace=constants.NAMESPACE_CERT_MANAGER,
+ )
+ ],
+ values=constants.HELM_RELEASE_RABBITMQ_OPERATOR_VALUES,
+ ),
+ ).apply()
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_PERCONA,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ spec=types.HelmRepositorySpec(
+ url="https://percona.github.io/percona-helm-charts/",
+ ),
+ ).apply()
+ objects.HelmRelease(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_RELEASE_PXC_OPERATOR_NAME,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ spec=types.HelmReleaseSpec(
+ chart=types.HelmChartTemplate(
+ spec=types.HelmChartTemplateSpec(
+ chart=constants.HELM_RELEASE_PXC_OPERATOR_NAME,
+ version=constants.HELM_RELEASE_PXC_OPERATOR_VERSION,
+ source_ref=types.CrossNamespaceObjectReference(
+ kind="HelmRepository",
+ name=constants.HELM_REPOSITORY_PERCONA,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ )
+ ),
+ depends_on=[
+ types.NamespacedObjectReference(
+ name=constants.HELM_RELEASE_CERT_MANAGER_NAME,
+ namespace=constants.NAMESPACE_CERT_MANAGER,
+ )
+ ],
+ values=constants.HELM_RELEASE_PXC_OPERATOR_VALUES,
+ ),
+ ).apply()
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_OPENSTACK_HELM_INFRA,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ spec=types.HelmRepositorySpec(
+ url="https://tarballs.opendev.org/openstack/openstack-helm-infra/",
+ ),
+ ).apply()
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_COREDNS,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ spec=types.HelmRepositorySpec(url="https://coredns.github.io/helm"),
+ ).apply()
+ objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name=constants.HELM_REPOSITORY_OPENSTACK_HELM,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ spec=types.HelmRepositorySpec(
+ url="https://tarballs.opendev.org/openstack/openstack-helm/",
+ ),
+ ).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,
+ ),
+ )
+ ),
+ depends_on=[
+ types.NamespacedObjectReference(
+ name=constants.HELM_RELEASE_RABBITMQ_OPERATOR_NAME,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ types.NamespacedObjectReference(
+ name=constants.HELM_RELEASE_PXC_OPERATOR_NAME,
+ namespace=constants.NAMESPACE_OPENSTACK,
+ ),
+ types.NamespacedObjectReference(
+ name="node-feature-discovery",
+ namespace=constants.NAMESPACE_MONITORING,
+ ),
+ ],
+ 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()
+
return engines.load(
get_deployment_flow(config),
executor="greenthreaded",
@@ -15,126 +311,12 @@
)
+# TODO(mnaser): Move this into the Cloud CRD
def get_deployment_flow(config):
flow = graph_flow.Flow("deploy").add(
- # kube-system
- v1.ApplyNamespaceTask(name=constants.NAMESPACE_KUBE_SYSTEM),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_KUBE_SYSTEM,
- name=constants.HELM_REPOSITORY_CEPH,
- url="https://ceph.github.io/csi-charts",
- ),
# cert-manager
- v1.ApplyNamespaceTask(name=constants.NAMESPACE_CERT_MANAGER),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_CERT_MANAGER,
- name=constants.HELM_REPOSITORY_JETSTACK,
- url="https://charts.jetstack.io",
- ),
- flux.ApplyHelmReleaseTask(
- namespace=constants.NAMESPACE_CERT_MANAGER,
- name=constants.HELM_RELEASE_CERT_MANAGER_NAME,
- repository=constants.HELM_REPOSITORY_JETSTACK,
- chart=constants.HELM_RELEASE_CERT_MANAGER_NAME,
- 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),
- *openstack_helm.kube_prometheus_stack_tasks_from_config(
- config.kube_prometheus_stack,
- opsgenie=config.opsgenie,
- ),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_MONITORING,
- name=constants.HELM_REPOSITORY_NODE_FEATURE_DISCOVERY,
- url="https://kubernetes-sigs.github.io/node-feature-discovery/charts",
- ),
- flux.ApplyHelmReleaseTask(
- namespace=constants.NAMESPACE_MONITORING,
- name="node-feature-discovery",
- repository=constants.HELM_REPOSITORY_NODE_FEATURE_DISCOVERY,
- chart="node-feature-discovery",
- version="0.11.2",
- values=constants.HELM_RELEASE_NODE_FEATURE_DISCOVERY_VALUES,
- ),
- # openstack
- v1.ApplyNamespaceTask(name=constants.NAMESPACE_OPENSTACK),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_REPOSITORY_BITNAMI,
- url="https://charts.bitnami.com/bitnami",
- ),
- flux.ApplyHelmReleaseTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_RELEASE_RABBITMQ_OPERATOR_NAME,
- repository=constants.HELM_REPOSITORY_BITNAMI,
- chart=constants.HELM_RELEASE_RABBITMQ_OPERATOR_NAME,
- version=constants.HELM_RELEASE_RABBITMQ_OPERATOR_VERSION,
- values=constants.HELM_RELEASE_RABBITMQ_OPERATOR_VALUES,
- requires=constants.HELM_RELEASE_RABBITMQ_OPERATOR_REQUIRES,
- ),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_REPOSITORY_PERCONA,
- url="https://percona.github.io/percona-helm-charts/",
- ),
- flux.ApplyHelmReleaseTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_RELEASE_PXC_OPERATOR_NAME,
- repository=constants.HELM_REPOSITORY_PERCONA,
- chart=constants.HELM_RELEASE_PXC_OPERATOR_NAME,
- version=constants.HELM_RELEASE_PXC_OPERATOR_VERSION,
- values=constants.HELM_RELEASE_PXC_OPERATOR_VALUES,
- ),
openstack_helm.ApplyPerconaXtraDBClusterTask(),
- *openstack_helm.ingress_nginx_tasks_from_config(config.ingress_nginx),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_REPOSITORY_OPENSTACK_HELM_INFRA,
- url="https://tarballs.opendev.org/openstack/openstack-helm-infra/",
- ),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_REPOSITORY_COREDNS,
- url="https://coredns.github.io/helm",
- ),
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_OPENSTACK,
- name=constants.HELM_REPOSITORY_OPENSTACK_HELM,
- url="https://tarballs.opendev.org/openstack/openstack-helm/",
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_KEYSTONE_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_BARBICAN_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_GLANCE_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_CINDER_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_NEUTRON_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_NOVA_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_OCTAVIA_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_SENLIN_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_DESIGNATE_NAME,
- ),
- openstack_helm.ApplyRabbitmqClusterTask(
- name=constants.HELM_RELEASE_HEAT_NAME,
- ),
)
if config.memcached.enabled:
diff --git a/atmosphere/operator/api/mixins.py b/atmosphere/operator/api/mixins.py
new file mode 100644
index 0000000..dad93f7
--- /dev/null
+++ b/atmosphere/operator/api/mixins.py
@@ -0,0 +1,30 @@
+import json
+
+import kopf
+import requests
+
+
+class ServerSideApplyMixin:
+ def apply(self):
+ resp = self.api.patch(
+ **self.api_kwargs(
+ headers={
+ "Content-Type": "application/apply-patch+yaml",
+ },
+ params={
+ "fieldManager": "atmosphere-operator",
+ "force": True,
+ },
+ data=json.dumps(self.obj),
+ )
+ )
+
+ try:
+ self.api.raise_for_status(resp)
+ except requests.exceptions.HTTPError:
+ if resp.status_code == 404:
+ raise kopf.TemporaryError("CRD is not yet installed", delay=1)
+ raise
+
+ self.set_obj(resp.json())
+ return self
diff --git a/atmosphere/operator/api/objects.py b/atmosphere/operator/api/objects.py
new file mode 100644
index 0000000..d668b00
--- /dev/null
+++ b/atmosphere/operator/api/objects.py
@@ -0,0 +1,233 @@
+from typing import ClassVar
+
+import pykube
+from pydantic import Field
+
+from atmosphere.operator import constants
+from atmosphere.operator.api import mixins, types
+
+# Kubernetes API
+
+
+class Namespace(types.KubernetesObject):
+ endpoint: ClassVar[str] = "namespaces"
+
+ version: str = Field("v1", alias="apiVersion", const=True)
+ kind: str = Field("Namespace", const=True)
+
+
+class Ingress(pykube.objects.Ingress, mixins.ServerSideApplyMixin):
+ pass
+
+
+class HelmRepository(types.NamespacedKubernetesObject):
+ endpoint: ClassVar[str] = "helmrepositories"
+
+ version: str = Field(
+ "source.toolkit.fluxcd.io/v1beta2", alias="apiVersion", const=True
+ )
+ kind: str = Field("HelmRepository", const=True)
+ spec: types.HelmRepositorySpec
+
+
+class HelmRelease(types.NamespacedKubernetesObject):
+ endpoint: ClassVar[str] = "helmreleases"
+
+ version: str = Field(
+ "helm.toolkit.fluxcd.io/v2beta1", alias="apiVersion", const=True
+ )
+ kind: str = Field("HelmRelease", const=True)
+ spec: types.HelmReleaseSpec
+
+
+class RabbitmqCluster(pykube.objects.NamespacedAPIObject, mixins.ServerSideApplyMixin):
+ version = "rabbitmq.com/v1beta1"
+ endpoint = "rabbitmqclusters"
+ kind = "RabbitmqCluster"
+
+
+# Atmosphere
+
+
+class OpenstackHelmRabbitmqCluster(types.NamespacedKubernetesObject):
+ endpoint: ClassVar[str] = "openstackhelmrabbitmqclusters"
+
+ kind: str = Field(constants.KIND_OPENSTACK_HELM_RABBITMQ_CLUSTER, const=True)
+ spec: types.OpenstackHelmRabbitmqClusterSpec
+
+ def apply_rabbitmq_cluster(self):
+ return RabbitmqCluster(
+ self.api,
+ {
+ "apiVersion": RabbitmqCluster.version,
+ "kind": RabbitmqCluster.kind,
+ "metadata": {
+ "name": f"rabbitmq-{self.metadata.name}",
+ "namespace": self.metadata.namespace,
+ "annotations": self.metadata.annotations,
+ "labels": self.metadata.labels,
+ },
+ "spec": {
+ "image": self.spec.image,
+ "affinity": {
+ "nodeAffinity": {
+ "requiredDuringSchedulingIgnoredDuringExecution": {
+ "nodeSelectorTerms": [
+ {
+ "matchExpressions": [
+ {
+ "key": "openstack-control-plane",
+ "operator": "In",
+ "values": ["enabled"],
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "rabbitmq": {
+ "additionalConfig": "vm_memory_high_watermark.relative = 0.9\n"
+ },
+ "resources": {
+ "requests": {"cpu": "500m", "memory": "1Gi"},
+ "limits": {"cpu": "1", "memory": "2Gi"},
+ },
+ "terminationGracePeriodSeconds": 15,
+ },
+ },
+ ).apply()
+
+ def delete_rabbitmq_cluster(self):
+ rabbitmq_cluster = RabbitmqCluster.objects(
+ self.api, namespace=self.metadata.namespace
+ ).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
new file mode 100644
index 0000000..8e99769
--- /dev/null
+++ b/atmosphere/operator/api/types.py
@@ -0,0 +1,202 @@
+from enum import Enum
+from typing import Any
+
+import pydantic
+import pykube
+import validators.domain
+
+from atmosphere.operator import constants
+from atmosphere.operator.api import mixins
+
+# Generic
+
+
+class Hostname(str):
+ @classmethod
+ def __get_validators__(cls):
+ yield cls.validate
+
+ @classmethod
+ def __modify_schema__(cls, field_schema: dict[str, Any]) -> None:
+ field_schema.update(
+ examples=["example.com"],
+ )
+
+ @classmethod
+ def validate(cls, v):
+ if validators.domain(v):
+ return cls(v)
+
+ def __repr__(self):
+ return f"Hostname({super().__repr__()})"
+
+
+# Kubernetes API
+
+
+class ObjectMeta(pydantic.BaseModel):
+ name: pydantic.constr(min_length=1)
+ annotations: dict[str, str] = {}
+ labels: dict[str, str] = {}
+
+
+class NamespacedObjectMeta(ObjectMeta):
+ namespace: pydantic.constr(min_length=1)
+
+
+class KubernetesObject(pydantic.BaseModel, mixins.ServerSideApplyMixin):
+ api: pykube.http.HTTPClient = None
+
+ version: str = pydantic.Field(
+ constants.API_VERSION_ATMOSPHERE, alias="apiVersion", const=True
+ )
+ metadata: ObjectMeta
+
+ class Config:
+ allow_population_by_field_name = True
+ arbitrary_types_allowed = True
+ fields = {"api": {"exclude": True}}
+
+ @property
+ def obj(self) -> dict:
+ return self.dict(by_alias=True)
+
+ def set_obj(self, *_):
+ pass
+
+ @property
+ def namespace(self) -> str:
+ return None
+
+ @property
+ def name(self) -> str:
+ return self.metadata.name
+
+ @property
+ def base(self) -> None:
+ return None
+
+ def api_kwargs(self, **kwargs):
+ return pykube.objects.APIObject.api_kwargs(self, **kwargs)
+
+
+class NamespacedKubernetesObject(KubernetesObject):
+ metadata: NamespacedObjectMeta
+
+ @property
+ def namespace(self) -> str:
+ return self.metadata.namespace
+
+
+class ServiceBackendPort(pydantic.BaseModel):
+ number: pydantic.conint(ge=1, le=65535)
+
+
+class IngressServiceBackend(pydantic.BaseModel):
+ name: pydantic.constr(min_length=1)
+ port: ServiceBackendPort
+
+
+class NamespacedObjectReference(pydantic.BaseModel):
+ name: pydantic.constr(min_length=1)
+ namespace: pydantic.constr(min_length=1) = None
+
+
+class CrossNamespaceObjectReference(NamespacedObjectReference):
+ kind: pydantic.constr(min_length=1)
+
+
+class HelmRepositorySpec(pydantic.BaseModel):
+ url: pydantic.HttpUrl
+ interval: str = "60s"
+
+
+class HelmChartTemplateSpec(pydantic.BaseModel):
+ chart: pydantic.constr(min_length=1)
+ version: pydantic.constr(min_length=1) = None
+ source_ref: CrossNamespaceObjectReference = pydantic.Field(alias="sourceRef")
+
+ class Config:
+ allow_population_by_field_name = True
+
+
+class HelmChartTemplate(pydantic.BaseModel):
+ spec: HelmChartTemplateSpec
+
+
+class HelmReleaseActionSpecCRDsPolicy(str, Enum):
+ SKIP = "Skip"
+ CREATE = "Create"
+ CREATE_REPLACE = "CreateReplace"
+
+
+class HelmReleaseActionSpec(pydantic.BaseModel):
+ crds: HelmReleaseActionSpecCRDsPolicy = (
+ HelmReleaseActionSpecCRDsPolicy.CREATE_REPLACE
+ )
+ disable_wait: bool = pydantic.Field(default=True, alias="disableWait")
+
+ class Config:
+ allow_population_by_field_name = True
+
+
+class HelmReleaseValuesReference(pydantic.BaseModel):
+ kind: pydantic.constr(min_length=1)
+ name: pydantic.constr(min_length=1)
+ values_key: str = pydantic.Field(default=None, alias="valuesKey")
+ target_path: str = pydantic.Field(default=None, alias="targetPath")
+
+ class Config:
+ allow_population_by_field_name = True
+
+
+class HelmReleaseSpec(pydantic.BaseModel):
+ chart: HelmChartTemplate
+ interval: str = "60s"
+ depends_on: list[NamespacedObjectReference] = pydantic.Field(
+ default=[], alias="dependsOn"
+ )
+ install: HelmReleaseActionSpec = HelmReleaseActionSpec()
+ upgrade: HelmReleaseActionSpec = HelmReleaseActionSpec()
+ values: dict = {}
+ values_from: list[HelmReleaseValuesReference] = pydantic.Field(
+ default=[], alias="valuesFrom"
+ )
+
+ class Config:
+ allow_population_by_field_name = True
+
+
+# Atmosphere
+
+
+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 f487040..3fd102d 100644
--- a/atmosphere/operator/constants.py
+++ b/atmosphere/operator/constants.py
@@ -1,3 +1,8 @@
+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",
"atmosphere": "quay.io/vexxhost/atmosphere:0.13.0", # x-release-please-version
diff --git a/atmosphere/operator/controllers/cloud.py b/atmosphere/operator/controllers/cloud.py
index 39c836f..0ea5dc2 100644
--- a/atmosphere/operator/controllers/cloud.py
+++ b/atmosphere/operator/controllers/cloud.py
@@ -5,25 +5,42 @@
from taskflow.listeners import logging as logging_listener
from taskflow.patterns import graph_flow
-from atmosphere.operator import tasks
-from atmosphere.operator.api import Cloud
+from atmosphere import clients, flows
+from atmosphere.models import config
+from atmosphere.operator import tasks, utils
+from atmosphere.operator.api import Cloud, objects, types
@kopf.on.resume(Cloud.version, Cloud.kind)
@kopf.on.create(Cloud.version, Cloud.kind)
def create_fn(namespace: str, name: str, spec: dict, **_):
+ api = clients.get_pykube_api()
+
+ # TODO(mnaser): Get rid of this flow.
+ cfg = config.Config.from_file()
+ engine = flows.get_engine(cfg)
+ engine.run()
+
flow = graph_flow.Flow("deploy").add(
- tasks.BuildApiClient(),
tasks.GenerateImageTagsConfigMap(provides="image_tags"),
tasks.GenerateSecrets(provides="secrets"),
)
if spec["magnum"].get("enabled", True):
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="magnum",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
flow.add(
tasks.InstallClusterApiTask(),
- tasks.ApplyRabbitmqClusterTask(
- inject={"chart_name": "magnum"}, provides="magnum_rabbitmq"
- ),
tasks.GetChartValues(
inject={
"helm_repository": "openstack-helm",
@@ -35,11 +52,10 @@
),
tasks.GenerateReleaseValues(
inject={"chart_name": "magnum"},
- rebind={"rabbitmq": "magnum_rabbitmq"},
provides="magnum_release_values",
),
tasks.GenerateMagnumChartValuesFrom(
- rebind={"rabbitmq": "magnum_rabbitmq"},
+ inject={"chart_name": "magnum"},
provides="magnum_values_from",
),
tasks.ApplyHelmReleaseTask(
@@ -54,18 +70,145 @@
"values_from": "magnum_values_from",
},
),
- tasks.ApplyIngressTask(
- inject={"endpoint": "container_infra"},
- rebind={
- "chart_values": "magnum_chart_values",
- "release_values": "magnum_release_values",
- },
- ),
)
+ 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,
+ metadata=types.NamespacedObjectMeta(
+ name="keystone",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="barbican",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="glance",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="cinder",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="neutron",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="nova",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="octavia",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="senlin",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="designate",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
+ objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="heat",
+ namespace=namespace,
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image=utils.get_image_ref(
+ "rabbitmq_server", override_registry=spec["imageRepository"]
+ ).string()
+ ),
+ ).apply()
engine = engines.load(
flow,
store={
+ "api": api,
"namespace": namespace,
"name": name,
"spec": spec,
diff --git a/atmosphere/operator/tasks.py b/atmosphere/operator/tasks.py
index 01710b3..68fcc50 100644
--- a/atmosphere/operator/tasks.py
+++ b/atmosphere/operator/tasks.py
@@ -12,24 +12,12 @@
from taskflow import task
from tenacity import retry, retry_if_result, stop_after_delay, wait_fixed
-from atmosphere import clients
from atmosphere.operator import constants, utils
LOG = logging.getLogger(__name__)
-class BuildApiClient(task.Task):
- default_provides = "api"
-
- def execute(self) -> pykube.HTTPClient:
- return clients.get_pykube_api()
-
-
class ApplyKubernetesObjectTask(task.Task):
- @property
- def api(self):
- return clients.get_pykube_api()
-
def generate_object(self, *args, **kwargs) -> pykube.objects.APIObject:
raise NotImplementedError
@@ -90,61 +78,6 @@
)
-class RabbitmqCluster(pykube.objects.NamespacedAPIObject):
- version = "rabbitmq.com/v1beta1"
- endpoint = "rabbitmqclusters"
- kind = "RabbitmqCluster"
-
-
-class ApplyRabbitmqClusterTask(ApplyKubernetesObjectTask):
- def execute(
- self, api: pykube.HTTPClient, namespace: str, chart_name: str, spec: dict
- ) -> dict:
- resource = RabbitmqCluster(
- api,
- {
- "apiVersion": RabbitmqCluster.version,
- "kind": RabbitmqCluster.kind,
- "metadata": {
- "name": f"rabbitmq-{chart_name}",
- "namespace": namespace,
- },
- "spec": {
- "image": utils.get_image_ref(
- "rabbitmq_server", override_registry=spec["imageRepository"]
- ).string(),
- "affinity": {
- "nodeAffinity": {
- "requiredDuringSchedulingIgnoredDuringExecution": {
- "nodeSelectorTerms": [
- {
- "matchExpressions": [
- {
- "key": "openstack-control-plane",
- "operator": "In",
- "values": ["enabled"],
- }
- ]
- }
- ]
- }
- }
- },
- "rabbitmq": {
- "additionalConfig": "vm_memory_high_watermark.relative = 0.9\n"
- },
- "resources": {
- "requests": {"cpu": "500m", "memory": "1Gi"},
- "limits": {"cpu": "1", "memory": "2Gi"},
- },
- "terminationGracePeriodSeconds": 15,
- },
- },
- )
-
- return self._apply(resource)
-
-
class HelmRelease(pykube.objects.NamespacedAPIObject):
version = "helm.toolkit.fluxcd.io/v2beta1"
endpoint = "helmreleases"
@@ -293,7 +226,7 @@
class GenerateReleaseValues(task.Task):
- def _generate_base(self, rabbitmq: RabbitmqCluster, spec: dict) -> dict:
+ def _generate_base(self, rabbitmq: str, spec: dict) -> dict:
return {
"endpoints": {
"identity": {
@@ -314,7 +247,7 @@
"statefulset": None,
"hosts": {
# TODO(mnaser): handle scenario when those don't exist
- "default": rabbitmq.name,
+ "default": rabbitmq,
},
},
},
@@ -410,10 +343,10 @@
},
}
- def execute(self, chart_name: str, rabbitmq: RabbitmqCluster, spec: dict) -> dict:
+ def execute(self, chart_name: str, spec: dict) -> dict:
return mergedeep.merge(
{},
- self._generate_base(rabbitmq, spec),
+ self._generate_base(f"rabbitmq-{chart_name}", spec),
getattr(self, f"_generate_{chart_name}")(spec),
spec[chart_name].get("overrides", {}),
)
@@ -422,9 +355,9 @@
class GenerateMagnumChartValuesFrom(task.Task):
def execute(
self,
+ chart_name: str,
image_tags: pykube.ConfigMap,
secrets: pykube.Secret,
- rabbitmq: RabbitmqCluster,
) -> dict:
return [
{
@@ -475,13 +408,13 @@
},
{
"kind": pykube.Secret.kind,
- "name": f"{rabbitmq.name}-default-user",
+ "name": f"rabbitmq-{chart_name}-default-user",
"targetPath": "endpoints.oslo_messaging.auth.admin.username",
"valuesKey": "username",
},
{
"kind": pykube.Secret.kind,
- "name": f"{rabbitmq.name}-default-user",
+ "name": f"rabbitmq-{chart_name}-default-user",
"targetPath": "endpoints.oslo_messaging.auth.admin.password",
"valuesKey": "password",
},
@@ -494,67 +427,6 @@
]
-class ApplyIngressTask(ApplyKubernetesObjectTask):
- def execute(
- self,
- api: pykube.HTTPClient,
- namespace: str,
- endpoint: str,
- spec: dict,
- chart_values: dict,
- release_values: dict,
- ) -> pykube.Ingress:
- host = release_values["endpoints"][endpoint]["host_fqdn_override"]["public"][
- "host"
- ]
- service_name = chart_values["endpoints"][endpoint]["hosts"]["default"]
- service_port = chart_values["endpoints"][endpoint]["port"]["api"]["default"]
-
- resource = pykube.Ingress(
- api,
- {
- "apiVersion": pykube.Ingress.version,
- "kind": pykube.Ingress.kind,
- "metadata": {
- "name": endpoint.replace("_", "-"),
- "namespace": namespace,
- "annotations": {
- "cert-manager.io/cluster-issuer": spec[
- "certManagerClusterIssuer"
- ],
- },
- },
- "spec": {
- "ingressClassName": spec["ingressClassName"],
- "rules": [
- {
- "host": host,
- "http": {
- "paths": [
- {
- "path": "/",
- "pathType": "Prefix",
- "backend": {
- "service": {
- "name": service_name,
- "port": {
- "number": service_port,
- },
- },
- },
- },
- ],
- },
- },
- ],
- "tls": [{"secretName": f"{service_name}-certs", "hosts": [host]}],
- },
- },
- )
-
- return self._apply(resource)
-
-
class GenerateOpenStackHelmEndpoints(task.Task):
SKIPPED_ENDPOINTS = (
"cluster_domain_suffix",
diff --git a/atmosphere/operator/utils.py b/atmosphere/operator/utils.py
index fa8bf63..57cfecc 100644
--- a/atmosphere/operator/utils.py
+++ b/atmosphere/operator/utils.py
@@ -7,7 +7,7 @@
image_name: str, override_registry: str = None
) -> reference.Reference:
ref = reference.Reference.parse(constants.IMAGE_LIST[image_name])
- if override_registry is None:
+ if not override_registry:
return ref
# NOTE(mnaser): We re-write the name of a few images to make sense of them
@@ -23,3 +23,11 @@
ref = reference.Reference.parse(ref.string())
return ref
+
+
+def filter_annotations(annotations: dict) -> dict:
+ return {
+ key: value
+ for key, value in annotations.items()
+ if key != "kopf.zalando.org/last-handled-configuration"
+ }
diff --git a/atmosphere/tasks/composite/openstack_helm.py b/atmosphere/tasks/composite/openstack_helm.py
index 37d40dc..acd9b89 100644
--- a/atmosphere/tasks/composite/openstack_helm.py
+++ b/atmosphere/tasks/composite/openstack_helm.py
@@ -133,67 +133,6 @@
}
-def kube_prometheus_stack_tasks_from_config(
- config: config.KubePrometheusStackChartConfig, opsgenie: config.OpsGenieConfig
-):
- if not config.enabled:
- return []
-
- values = mergedeep.merge(
- {},
- constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VALUES,
- config.overrides,
- )
-
- if opsgenie.enabled:
- values["alertmanager"]["config"] = generate_alertmanager_config_for_opsgenie(
- opsgenie
- )
-
- return [
- flux.ApplyHelmRepositoryTask(
- namespace=constants.NAMESPACE_MONITORING,
- name=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
- url=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY_URL,
- ),
- flux.ApplyHelmReleaseTask(
- namespace=config.namespace,
- name=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
- repository=constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
- chart=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
- version=constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VERSION,
- values=values,
- ),
- ]
-
-
-def ingress_nginx_tasks_from_config(config: config.IngressNginxChartConfig):
- if not config.enabled:
- return []
-
- values = mergedeep.merge(
- {},
- constants.HELM_RELEASE_INGRESS_NGINX_VALUES,
- config.overrides,
- )
-
- return [
- flux.ApplyHelmRepositoryTask(
- namespace=config.namespace,
- name=constants.HELM_REPOSITORY_INGRESS_NGINX,
- url=constants.HELM_REPOSITORY_INGRESS_NGINX_URL,
- ),
- flux.ApplyHelmReleaseTask(
- namespace=config.namespace,
- 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"
@@ -206,9 +145,6 @@
kind=PerconaXtraDBCluster,
namespace=constants.NAMESPACE_OPENSTACK,
name="percona-xtradb",
- requires=[
- f"helm-release-{constants.NAMESPACE_OPENSTACK}-{constants.HELM_RELEASE_PXC_OPERATOR_NAME}",
- ],
)
def generate_object(self) -> PerconaXtraDBCluster:
@@ -276,64 +212,3 @@
},
},
)
-
-
-class RabbitmqCluster(pykube.objects.NamespacedAPIObject):
- version = "rabbitmq.com/v1beta1"
- endpoint = "rabbitmqclusters"
- kind = "RabbitmqCluster"
-
-
-class ApplyRabbitmqClusterTask(base.ApplyKubernetesObjectTask):
- def __init__(self, name: str):
- super().__init__(
- kind=RabbitmqCluster,
- namespace=constants.NAMESPACE_OPENSTACK,
- name=name,
- requires=[
- f"helm-release-{constants.NAMESPACE_OPENSTACK}-{constants.HELM_RELEASE_RABBITMQ_OPERATOR_NAME}",
- ],
- )
-
- def generate_object(self) -> RabbitmqCluster:
- return RabbitmqCluster(
- self.api,
- {
- "apiVersion": self._obj_kind.version,
- "kind": self._obj_kind.kind,
- "metadata": {
- "name": f"rabbitmq-{self._obj_name}",
- "namespace": self._obj_namespace,
- },
- "spec": {
- "image": utils.get_image_ref_using_legacy_image_repository(
- "rabbitmq_server"
- ).string(),
- "affinity": {
- "nodeAffinity": {
- "requiredDuringSchedulingIgnoredDuringExecution": {
- "nodeSelectorTerms": [
- {
- "matchExpressions": [
- {
- "key": "openstack-control-plane",
- "operator": "In",
- "values": ["enabled"],
- }
- ]
- }
- ]
- }
- }
- },
- "rabbitmq": {
- "additionalConfig": "vm_memory_high_watermark.relative = 0.9\n"
- },
- "resources": {
- "requests": {"cpu": "500m", "memory": "1Gi"},
- "limits": {"cpu": "1", "memory": "2Gi"},
- },
- "terminationGracePeriodSeconds": 15,
- },
- },
- )
diff --git a/atmosphere/tasks/constants.py b/atmosphere/tasks/constants.py
index 00b1931..5e99700 100644
--- a/atmosphere/tasks/constants.py
+++ b/atmosphere/tasks/constants.py
@@ -593,11 +593,6 @@
},
"useCertManager": True,
}
-HELM_RELEASE_RABBITMQ_OPERATOR_REQUIRES = set(
- [
- f"helm-release-{NAMESPACE_CERT_MANAGER}-{HELM_RELEASE_CERT_MANAGER_NAME}",
- ]
-)
HELM_RELEASE_PXC_OPERATOR_NAME = "pxc-operator"
HELM_RELEASE_PXC_OPERATOR_VERSION = "1.10.0"
diff --git a/atmosphere/tasks/kubernetes/base.py b/atmosphere/tasks/kubernetes/base.py
index 7159310..b8f5bae 100644
--- a/atmosphere/tasks/kubernetes/base.py
+++ b/atmosphere/tasks/kubernetes/base.py
@@ -3,6 +3,7 @@
import pykube
from taskflow import task
+from tenacity import retry, stop_after_delay, wait_fixed
from atmosphere import clients, logger
@@ -23,12 +24,6 @@
kwargs["name"] += f"-{namespace}"
kwargs["name"] += f"-{name}"
- if namespace:
- # kwargs.setdefault("requires", [])
- # kwargs["requires"] += [f"namespace-{namespace}"]
- kwargs.setdefault("rebind", {})
- kwargs["rebind"]["namespace"] = f"namespace-{namespace}"
-
kwargs.setdefault("provides", set())
kwargs["provides"] = kwargs["provides"].union(set([kwargs["name"]]))
@@ -56,6 +51,7 @@
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")
diff --git a/atmosphere/tasks/kubernetes/cert_manager.py b/atmosphere/tasks/kubernetes/cert_manager.py
index 61a741d..2c420a3 100644
--- a/atmosphere/tasks/kubernetes/cert_manager.py
+++ b/atmosphere/tasks/kubernetes/cert_manager.py
@@ -19,11 +19,6 @@
kind=Certificate,
namespace=namespace,
name=name,
- requires=set(
- [
- f"helm-release-{constants.NAMESPACE_CERT_MANAGER}-{constants.HELM_RELEASE_CERT_MANAGER_NAME}",
- ]
- ),
)
def generate_object(self) -> Certificate:
@@ -55,11 +50,6 @@
kind=ClusterIssuer,
namespace=None,
name=name,
- requires=set(
- [
- f"helm-release-{constants.NAMESPACE_CERT_MANAGER}-{constants.HELM_RELEASE_CERT_MANAGER_NAME}",
- ]
- ),
)
def generate_object(self) -> ClusterIssuer:
diff --git a/atmosphere/tasks/kubernetes/flux.py b/atmosphere/tasks/kubernetes/flux.py
index 77d6f22..3294897 100644
--- a/atmosphere/tasks/kubernetes/flux.py
+++ b/atmosphere/tasks/kubernetes/flux.py
@@ -8,41 +8,6 @@
LOG = logger.get_logger()
-class HelmRepository(pykube.objects.NamespacedAPIObject):
- version = "source.toolkit.fluxcd.io/v1beta2"
- endpoint = "helmrepositories"
- kind = "HelmRepository"
-
-
-class ApplyHelmRepositoryTask(base.ApplyKubernetesObjectTask):
- def __init__(self, namespace: str, name: str, url: str):
- self._url = url
-
- super().__init__(
- kind=HelmRepository,
- namespace=namespace,
- name=name,
- requires=set(["namespace"]),
- )
-
- def generate_object(self) -> HelmRepository:
- return HelmRepository(
- self.api,
- {
- "apiVersion": self._obj_kind.version,
- "kind": self._obj_kind.kind,
- "metadata": {
- "name": self._obj_name,
- "namespace": self._obj_namespace,
- },
- "spec": {
- "interval": "1m",
- "url": self._url,
- },
- },
- )
-
-
class HelmRelease(pykube.objects.NamespacedAPIObject):
version = "helm.toolkit.fluxcd.io/v2beta1"
endpoint = "helmreleases"
@@ -68,14 +33,10 @@
self._values = values
self._values_from = values_from
- kwargs.setdefault("requires", set())
- kwargs["requires"] = kwargs["requires"].union(set(["repository"]))
-
super().__init__(
kind=HelmRelease,
namespace=namespace,
name=name,
- rebind={"repository": f"helm-repository-{namespace}-{repository}"},
*args,
**kwargs,
)
diff --git a/atmosphere/tasks/kubernetes/v1.py b/atmosphere/tasks/kubernetes/v1.py
index 771bbe9..fc1a881 100644
--- a/atmosphere/tasks/kubernetes/v1.py
+++ b/atmosphere/tasks/kubernetes/v1.py
@@ -6,47 +6,6 @@
LOG = logger.get_logger()
-class ApplyNamespaceTask(base.ApplyKubernetesObjectTask):
- def __init__(self, name: str):
- super().__init__(kind=pykube.Namespace, namespace=None, name=name)
-
- def generate_object(self) -> pykube.Namespace:
- return pykube.Namespace(
- self.api,
- {
- "apiVersion": self._obj_kind.version,
- "kind": self._obj_kind.kind,
- "metadata": {"name": self._obj_name},
- },
- )
-
-
-class ApplyConfigMapTask(base.ApplyKubernetesObjectTask):
- def __init__(self, namespace: str, name: str, data: str):
- self._data = data
-
- super().__init__(
- kind=pykube.ConfigMap,
- namespace=namespace,
- name=name,
- requires=set(["namespace"]),
- )
-
- def generate_object(self) -> pykube.ConfigMap:
- return pykube.ConfigMap(
- self.api,
- {
- "apiVersion": self._obj_kind.version,
- "kind": self._obj_kind.kind,
- "metadata": {
- "name": self._obj_name,
- "namespace": self._obj_namespace,
- },
- "data": self._data,
- },
- )
-
-
class ApplyServiceTask(base.ApplyKubernetesObjectTask):
def __init__(self, namespace: str, name: str, labels: dict, spec: dict):
self._labels = labels
@@ -56,7 +15,6 @@
kind=pykube.Service,
namespace=namespace,
name=name,
- requires=set(["namespace"]),
)
def generate_object(self) -> pykube.Service:
@@ -83,7 +41,6 @@
kind=pykube.Secret,
namespace=namespace,
name=name,
- requires=set(["namespace"]),
)
def generate_object(self) -> pykube.Secret:
@@ -99,40 +56,3 @@
"stringData": self._data,
},
)
-
-
-class ApplyIngressTask(base.ApplyKubernetesObjectTask):
- def __init__(
- self,
- namespace: str,
- name: str,
- spec: dict,
- annotations: dict = {},
- labels: dict = {},
- ):
- self._annotations = annotations
- self._labels = labels
- self._spec = spec
-
- super().__init__(
- kind=pykube.Ingress,
- namespace=namespace,
- name=name,
- requires=set(["namespace"]),
- )
-
- def generate_object(self) -> pykube.Ingress:
- return pykube.Ingress(
- self.api,
- {
- "apiVersion": self._obj_kind.version,
- "kind": self._obj_kind.kind,
- "metadata": {
- "name": self._obj_name,
- "namespace": self._obj_namespace,
- "annotations": self._annotations,
- "labels": self._labels,
- },
- "spec": self._spec,
- },
- )
diff --git a/atmosphere/tests/conftest.py b/atmosphere/tests/conftest.py
index c830701..7092894 100644
--- a/atmosphere/tests/conftest.py
+++ b/atmosphere/tests/conftest.py
@@ -3,6 +3,7 @@
import pytest
import requests
+import responses
from atmosphere.models import config
@@ -19,7 +20,45 @@
@pytest.fixture
+def kubeconfig(tmpdir):
+ kubeconfig = tmpdir.join("kubeconfig")
+ kubeconfig.write(
+ """
+apiVersion: v1
+clusters:
+- cluster: {server: 'https://localhost:9443'}
+ name: test
+contexts:
+- context: {cluster: test, user: test}
+ name: test
+current-context: test
+kind: Config
+preferences: {}
+users:
+- name: test
+ user: {token: testtoken}
+ """
+ )
+ return kubeconfig
+
+
+@pytest.fixture
+def requests_mock():
+ return responses.RequestsMock(target="pykube.http.KubernetesHTTPAdapter._do_send")
+
+
+@pytest.fixture
+def api(kubeconfig):
+ import pykube
+
+ config = pykube.KubeConfig.from_file(str(kubeconfig))
+ return pykube.HTTPClient(config)
+
+
+@pytest.fixture
def pykube(mocker):
+ # TODO(mnaser): We should get rid of this fixture and rename the other one
+ # to pykube.
mocked_api = mocker.MagicMock()
mocker.patch("atmosphere.clients.get_pykube_api", return_value=mocked_api)
return mocked_api
diff --git a/atmosphere/tests/e2e/test_operator.py b/atmosphere/tests/e2e/test_operator.py
index cf1bd48..922ecad 100644
--- a/atmosphere/tests/e2e/test_operator.py
+++ b/atmosphere/tests/e2e/test_operator.py
@@ -67,7 +67,7 @@
wait=wait_fixed(1),
):
with attempt:
- assert "Initial authentication has finished." in pod.logs()
+ assert "kind=Secret name=atmosphere-memcached" in pod.logs()
for secret_name in ["atmosphere-config", "atmosphere-memcached"]:
secret = pykube.Secret.objects(
diff --git a/atmosphere/tests/unit/operator/test_objects.py b/atmosphere/tests/unit/operator/test_objects.py
new file mode 100644
index 0000000..017166e
--- /dev/null
+++ b/atmosphere/tests/unit/operator/test_objects.py
@@ -0,0 +1,608 @@
+import json
+
+import pytest
+import responses
+from hypothesis import given
+from hypothesis import provisional as prov
+from hypothesis import strategies as st
+
+from atmosphere.operator.api import objects, types
+
+
+class TestNamespace:
+ @given(st.builds(objects.Namespace))
+ def test_property(self, instance):
+ assert isinstance(instance, objects.Namespace)
+ assert isinstance(instance.metadata, types.ObjectMeta)
+
+ def test_apply(self, api, requests_mock):
+ instance = objects.Namespace(
+ api=api,
+ metadata=types.ObjectMeta(
+ name="openstack",
+ annotations={
+ "annotate": "this",
+ },
+ labels={
+ "foo": "bar",
+ },
+ ),
+ )
+
+ with requests_mock as rsps:
+ rsps.add(
+ responses.PATCH,
+ "https://localhost:9443/api/v1/namespaces/openstack?fieldManager=atmosphere-operator&force=True",
+ json={},
+ )
+
+ instance.apply()
+
+ assert len(rsps.calls) == 1
+ assert json.loads(rsps.calls[0].request.body) == {
+ "apiVersion": "v1",
+ "kind": "Namespace",
+ "metadata": {
+ "name": "openstack",
+ "annotations": {
+ "annotate": "this",
+ },
+ "labels": {
+ "foo": "bar",
+ },
+ },
+ }
+
+
+class TestHelmRepository:
+ @given(
+ st.builds(
+ objects.HelmRepository,
+ spec=st.builds(types.HelmRepositorySpec, url=prov.urls()),
+ )
+ )
+ def test_property(self, instance):
+ assert isinstance(instance, objects.HelmRepository)
+ assert isinstance(instance.spec, types.HelmRepositorySpec)
+
+ def test_apply(self, api, requests_mock):
+ instance = objects.HelmRepository(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="openstack-helm",
+ namespace="openstack",
+ annotations={
+ "annotate": "this",
+ },
+ labels={
+ "foo": "bar",
+ },
+ ),
+ spec=types.HelmRepositorySpec(
+ url="https://tarballs.opendev.org/openstack/openstack-helm/",
+ ),
+ )
+
+ with requests_mock as rsps:
+ rsps.add(
+ responses.PATCH,
+ "https://localhost:9443/apis/source.toolkit.fluxcd.io/v1beta2/namespaces/openstack/helmrepositories/openstack-helm?fieldManager=atmosphere-operator&force=True", # noqa E501
+ json={},
+ )
+
+ instance.apply()
+
+ assert len(rsps.calls) == 1
+ assert json.loads(rsps.calls[0].request.body) == {
+ "apiVersion": "source.toolkit.fluxcd.io/v1beta2",
+ "kind": "HelmRepository",
+ "metadata": {
+ "name": "openstack-helm",
+ "namespace": "openstack",
+ "annotations": {
+ "annotate": "this",
+ },
+ "labels": {
+ "foo": "bar",
+ },
+ },
+ "spec": {
+ "interval": "60s",
+ "url": "https://tarballs.opendev.org/openstack/openstack-helm/",
+ },
+ }
+
+
+class TestHelmRelease:
+ @given(st.builds(objects.HelmRelease))
+ def test_property(self, instance):
+ assert isinstance(instance, objects.HelmRelease)
+ assert isinstance(instance.spec, types.HelmReleaseSpec)
+
+ def test_apply(self, api, requests_mock):
+ instance = objects.HelmRelease(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="neutron",
+ namespace="openstack",
+ annotations={
+ "annotate": "this",
+ },
+ labels={
+ "foo": "bar",
+ },
+ ),
+ spec=types.HelmReleaseSpec(
+ chart=types.HelmChartTemplate(
+ spec=types.HelmChartTemplateSpec(
+ chart="neutron",
+ version="0.1.0",
+ source_ref=types.CrossNamespaceObjectReference(
+ kind="HelmRepository",
+ name="openstack-helm",
+ namespace="openstack",
+ ),
+ )
+ ),
+ values={
+ "foo": "bar",
+ },
+ values_from=[
+ types.HelmReleaseValuesReference(
+ kind="Secret",
+ name="rabbitmq-neutron-default-user",
+ values_key="username",
+ target_path="rabbitmq.username",
+ )
+ ],
+ ),
+ )
+
+ with requests_mock as rsps:
+ rsps.add(
+ responses.PATCH,
+ "https://localhost:9443/apis/helm.toolkit.fluxcd.io/v2beta1/namespaces/openstack/helmreleases/neutron?fieldManager=atmosphere-operator&force=True", # noqa E501
+ json={},
+ )
+
+ instance.apply()
+
+ assert len(rsps.calls) == 1
+ assert json.loads(rsps.calls[0].request.body) == {
+ "apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
+ "kind": "HelmRelease",
+ "metadata": {
+ "name": instance.metadata.name,
+ "namespace": instance.metadata.namespace,
+ "labels": instance.metadata.labels,
+ "annotations": instance.metadata.annotations,
+ },
+ "spec": {
+ "chart": {
+ "spec": {
+ "chart": "neutron",
+ "version": "0.1.0",
+ "sourceRef": {
+ "kind": "HelmRepository",
+ "name": "openstack-helm",
+ "namespace": "openstack",
+ },
+ },
+ },
+ "dependsOn": [],
+ "install": {
+ "crds": "CreateReplace",
+ "disableWait": True,
+ },
+ "interval": "60s",
+ "upgrade": {
+ "crds": "CreateReplace",
+ "disableWait": True,
+ },
+ "values": {
+ "foo": "bar",
+ },
+ "valuesFrom": [
+ {
+ "kind": "Secret",
+ "name": "rabbitmq-neutron-default-user",
+ "valuesKey": "username",
+ "targetPath": "rabbitmq.username",
+ },
+ ],
+ },
+ }
+
+
+class TestOpenstackHelmRabbitmqCluster:
+ @given(st.builds(objects.OpenstackHelmRabbitmqCluster))
+ def test_property(self, instance):
+ assert isinstance(instance, objects.OpenstackHelmRabbitmqCluster)
+ assert isinstance(instance.metadata, types.NamespacedObjectMeta)
+ assert isinstance(instance.spec, types.OpenstackHelmRabbitmqClusterSpec)
+
+ def test_apply(self, api, requests_mock):
+ instance = objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="neutron",
+ namespace="default",
+ annotations={
+ "annotate": "this",
+ },
+ labels={
+ "foo": "bar",
+ },
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image="rabbitmq:3.8.9",
+ ),
+ )
+
+ with requests_mock as rsps:
+ rsps.add(
+ responses.PATCH,
+ "https://localhost:9443/apis/atmosphere.vexxhost.com/v1alpha1/namespaces/default/openstackhelmrabbitmqclusters/neutron?fieldManager=atmosphere-operator&force=True", # noqa E501
+ json={},
+ )
+
+ instance.apply()
+
+ assert len(rsps.calls) == 1
+ assert json.loads(rsps.calls[0].request.body) == {
+ "apiVersion": "atmosphere.vexxhost.com/v1alpha1",
+ "kind": "OpenstackHelmRabbitmqCluster",
+ "metadata": {
+ "name": "neutron",
+ "namespace": "default",
+ "labels": {
+ "foo": "bar",
+ },
+ "annotations": {
+ "annotate": "this",
+ },
+ },
+ "spec": {
+ "image": "rabbitmq:3.8.9",
+ },
+ }
+
+ def test_apply_rabbitmq_cluster(self, api, requests_mock):
+ instance = objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="neutron",
+ namespace="default",
+ annotations={
+ "annotate": "this",
+ },
+ labels={
+ "foo": "bar",
+ },
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image="rabbitmq:3.8.9",
+ ),
+ )
+
+ with requests_mock as rsps:
+ rsps.add(
+ responses.PATCH,
+ "https://localhost:9443/apis/rabbitmq.com/v1beta1/namespaces/default/rabbitmqclusters/rabbitmq-neutron?fieldManager=atmosphere-operator&force=True", # noqa E501
+ json={},
+ )
+
+ instance.apply_rabbitmq_cluster()
+
+ assert len(rsps.calls) == 1
+ assert json.loads(rsps.calls[0].request.body) == {
+ "apiVersion": "rabbitmq.com/v1beta1",
+ "kind": "RabbitmqCluster",
+ "metadata": {
+ "name": "rabbitmq-neutron",
+ "namespace": "default",
+ "annotations": {
+ "annotate": "this",
+ },
+ "labels": {
+ "foo": "bar",
+ },
+ },
+ "spec": {
+ "image": "rabbitmq:3.8.9",
+ "affinity": {
+ "nodeAffinity": {
+ "requiredDuringSchedulingIgnoredDuringExecution": {
+ "nodeSelectorTerms": [
+ {
+ "matchExpressions": [
+ {
+ "key": "openstack-control-plane",
+ "operator": "In",
+ "values": ["enabled"],
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "rabbitmq": {
+ "additionalConfig": "vm_memory_high_watermark.relative = 0.9\n"
+ },
+ "resources": {
+ "requests": {"cpu": "500m", "memory": "1Gi"},
+ "limits": {"cpu": "1", "memory": "2Gi"},
+ },
+ "terminationGracePeriodSeconds": 15,
+ },
+ }
+
+ def test_delete_rabbitmq_cluster(self, api, requests_mock):
+ instance = objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="neutron",
+ namespace="default",
+ annotations={
+ "annotate": "this",
+ },
+ labels={
+ "foo": "bar",
+ },
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image="rabbitmq:3.8.9",
+ ),
+ )
+
+ with requests_mock as rsps:
+ rsps.add(
+ responses.GET,
+ "https://localhost:9443/apis/rabbitmq.com/v1beta1/namespaces/default/rabbitmqclusters/rabbitmq-neutron",
+ json={
+ "metadata": {
+ "name": "rabbitmq-neutron",
+ },
+ },
+ )
+ rsps.add(
+ responses.DELETE,
+ "https://localhost:9443/apis/rabbitmq.com/v1beta1/namespaces/default/rabbitmqclusters/rabbitmq-neutron",
+ )
+
+ instance.delete_rabbitmq_cluster()
+ assert len(rsps.calls) == 2
+
+ def test_delete_missing_rabbitmq_cluster(self, api, requests_mock):
+ instance = objects.OpenstackHelmRabbitmqCluster(
+ api=api,
+ metadata=types.NamespacedObjectMeta(
+ name="neutron",
+ namespace="default",
+ annotations={
+ "annotate": "this",
+ },
+ labels={
+ "foo": "bar",
+ },
+ ),
+ spec=types.OpenstackHelmRabbitmqClusterSpec(
+ image="rabbitmq:3.8.9",
+ ),
+ )
+
+ with requests_mock as rsps:
+ rsps.add(
+ responses.GET,
+ "https://localhost:9443/apis/rabbitmq.com/v1beta1/namespaces/default/rabbitmqclusters/rabbitmq-neutron",
+ status=404,
+ )
+
+ 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
new file mode 100644
index 0000000..446b31a
--- /dev/null
+++ b/atmosphere/tests/unit/operator/test_types.py
@@ -0,0 +1,174 @@
+import pydantic
+from hypothesis import given
+from hypothesis import provisional as prov
+from hypothesis import strategies as st
+
+from atmosphere.operator.api import types
+
+
+class TestHostname:
+ def test_modify_schema(self):
+ class FakeObj(pydantic.BaseModel):
+ hostname: types.Hostname
+
+ assert FakeObj.schema().get("properties").get("hostname").get("examples") == [
+ "example.com"
+ ]
+
+ def test_repr(self):
+ assert repr(types.Hostname("example.com")) == "Hostname('example.com')"
+
+
+class TestObjectMeta:
+ @given(st.builds(types.ObjectMeta))
+ def test_property(self, instance):
+ assert isinstance(instance, types.ObjectMeta)
+ assert isinstance(instance.name, str)
+ assert instance.name != ""
+
+
+class TestNamespacedObjectMeta:
+ @given(st.builds(types.NamespacedObjectMeta))
+ def test_property(self, instance):
+ assert isinstance(instance, types.NamespacedObjectMeta)
+ assert isinstance(instance.name, str)
+ assert instance.name != ""
+ assert isinstance(instance.namespace, str)
+ assert instance.namespace != ""
+
+
+class TestKubernetesObject:
+ @given(st.builds(types.KubernetesObject))
+ def test_property(self, instance):
+ assert isinstance(instance, types.KubernetesObject)
+ assert isinstance(instance.metadata, types.ObjectMeta)
+
+
+class TestNamespacedKubernetesObject:
+ @given(st.builds(types.NamespacedKubernetesObject))
+ def test_property(self, instance):
+ assert isinstance(instance, types.NamespacedKubernetesObject)
+ assert isinstance(instance.metadata, types.NamespacedObjectMeta)
+
+
+class TestServiceBackendPort:
+ @given(st.builds(types.ServiceBackendPort))
+ def test_property(self, instance):
+ assert isinstance(instance, types.ServiceBackendPort)
+ assert isinstance(instance.number, int)
+ assert 1 <= instance.number <= 65535
+
+
+class TestIngressServiceBackend:
+ @given(st.builds(types.IngressServiceBackend))
+ def test_property(self, instance):
+ assert isinstance(instance, types.IngressServiceBackend)
+ assert isinstance(instance.name, str)
+ assert instance.name != ""
+ assert isinstance(instance.port, types.ServiceBackendPort)
+
+
+class TestCrossNamespaceObjectReference:
+ @given(st.builds(types.CrossNamespaceObjectReference))
+ def test_property(self, instance):
+ assert isinstance(instance, types.CrossNamespaceObjectReference)
+ assert isinstance(instance.kind, str)
+ assert instance.kind != ""
+ assert isinstance(instance.name, str)
+ assert instance.name != ""
+ assert isinstance(instance.namespace, str) or instance.namespace is None
+ assert instance.namespace != ""
+
+
+class TestHelmRepositorySpec:
+ @given(st.builds(types.HelmRepositorySpec, url=prov.urls()))
+ def test_property(self, instance):
+ assert isinstance(instance, types.HelmRepositorySpec)
+ assert isinstance(instance.url, str)
+ assert instance.url != ""
+ assert isinstance(instance.interval, str)
+ assert instance.interval != ""
+
+
+class TestHelmChartTemplateSpec:
+ @given(st.builds(types.HelmChartTemplateSpec))
+ def test_property(self, instance):
+ assert isinstance(instance, types.HelmChartTemplateSpec)
+ assert isinstance(instance.chart, str)
+ assert instance.chart != ""
+ assert isinstance(instance.version, str) or instance.version is None
+ assert instance.version != ""
+
+
+class TestHelmChartTemplate:
+ @given(st.builds(types.HelmChartTemplate))
+ def test_property(self, instance):
+ assert isinstance(instance, types.HelmChartTemplate)
+ assert isinstance(instance.spec, types.HelmChartTemplateSpec)
+
+
+class TestHelmReleaseActionSpec:
+ @given(st.builds(types.HelmReleaseActionSpec))
+ def test_property(self, instance):
+ assert isinstance(instance, types.HelmReleaseActionSpec)
+ assert isinstance(instance.crds, types.HelmReleaseActionSpecCRDsPolicy)
+ assert isinstance(instance.disable_wait, bool)
+ assert instance.disable_wait in [True, False]
+
+
+class TestHelmReleaseValuesReference:
+ @given(st.builds(types.HelmReleaseValuesReference))
+ def test_property(self, instance):
+ assert isinstance(instance, types.HelmReleaseValuesReference)
+ assert isinstance(instance.kind, str)
+ assert instance.kind != ""
+ assert isinstance(instance.name, str)
+ assert instance.name != ""
+ assert isinstance(instance.values_key, str) or instance.values_key is None
+ assert instance.values_key != ""
+ assert isinstance(instance.target_path, str) or instance.target_path is None
+ assert instance.target_path != ""
+
+
+class TestHelmReleaseSpec:
+ @given(st.builds(types.HelmReleaseSpec))
+ def test_property(self, instance):
+ assert isinstance(instance, types.HelmReleaseSpec)
+ assert isinstance(instance.interval, str)
+ assert instance.interval != ""
+ assert isinstance(instance.chart, types.HelmChartTemplate)
+ assert isinstance(instance.install, types.HelmReleaseActionSpec)
+ assert isinstance(instance.upgrade, types.HelmReleaseActionSpec)
+ assert isinstance(instance.values, dict)
+ assert isinstance(instance.values_from, list)
+
+
+class TestOpenstackHelmRabbitmqClusterSpec:
+ @given(st.builds(types.OpenstackHelmRabbitmqClusterSpec))
+ def test_property(self, instance):
+ 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/atmosphere/tests/unit/tasks/composite/test_openstack_helm.py b/atmosphere/tests/unit/tasks/composite/test_openstack_helm.py
deleted file mode 100644
index f94d970..0000000
--- a/atmosphere/tests/unit/tasks/composite/test_openstack_helm.py
+++ /dev/null
@@ -1,317 +0,0 @@
-import textwrap
-
-import mergedeep
-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_REPOSITORY_PROMETHEUS_COMMUINTY,
- "namespace": constants.NAMESPACE_MONITORING,
- },
- "spec": {
- "interval": "1m",
- "url": constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY_URL,
- },
- },
- {
- "apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
- "kind": "HelmRelease",
- "metadata": {
- "name": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
- "namespace": constants.NAMESPACE_MONITORING,
- },
- "spec": {
- "chart": {
- "spec": {
- "chart": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
- "sourceRef": {
- "kind": "HelmRepository",
- "name": constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
- },
- "version": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VERSION,
- }
- },
- "install": {"crds": "CreateReplace", "disableWait": True},
- "interval": "60s",
- "upgrade": {"crds": "CreateReplace", "disableWait": True},
- "values": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VALUES,
- "valuesFrom": [],
- },
- },
- ],
- id="default",
- ),
- pytest.param(
- textwrap.dedent(
- """\
- [kube_prometheus_stack.overrides]
- foo = "bar"
- """
- ),
- [
- {
- "apiVersion": "source.toolkit.fluxcd.io/v1beta2",
- "kind": "HelmRepository",
- "metadata": {
- "name": constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
- "namespace": constants.NAMESPACE_MONITORING,
- },
- "spec": {
- "interval": "1m",
- "url": constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY_URL,
- },
- },
- {
- "apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
- "kind": "HelmRelease",
- "metadata": {
- "name": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
- "namespace": constants.NAMESPACE_MONITORING,
- },
- "spec": {
- "chart": {
- "spec": {
- "chart": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
- "sourceRef": {
- "kind": "HelmRepository",
- "name": constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
- },
- "version": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VERSION,
- }
- },
- "install": {"crds": "CreateReplace", "disableWait": True},
- "interval": "60s",
- "upgrade": {"crds": "CreateReplace", "disableWait": True},
- "values": mergedeep.merge(
- {},
- constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VALUES,
- {"foo": "bar"},
- ),
- "valuesFrom": [],
- },
- },
- ],
- id="overrides",
- ),
- pytest.param(
- textwrap.dedent(
- """\
- [opsgenie]
- enabled = true
- api_key = "foobar"
- heartbeat = "prod"
- """
- ),
- [
- {
- "apiVersion": "source.toolkit.fluxcd.io/v1beta2",
- "kind": "HelmRepository",
- "metadata": {
- "name": constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
- "namespace": constants.NAMESPACE_MONITORING,
- },
- "spec": {
- "interval": "1m",
- "url": constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY_URL,
- },
- },
- {
- "apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
- "kind": "HelmRelease",
- "metadata": {
- "name": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
- "namespace": constants.NAMESPACE_MONITORING,
- },
- "spec": {
- "chart": {
- "spec": {
- "chart": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_NAME,
- "sourceRef": {
- "kind": "HelmRepository",
- "name": constants.HELM_REPOSITORY_PROMETHEUS_COMMUINTY,
- },
- "version": constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VERSION,
- }
- },
- "install": {"crds": "CreateReplace", "disableWait": True},
- "interval": "60s",
- "upgrade": {"crds": "CreateReplace", "disableWait": True},
- "values": mergedeep.merge(
- {},
- constants.HELM_RELEASE_KUBE_PROMETHEUS_STACK_VALUES,
- {
- "alertmanager": {
- "config": openstack_helm.generate_alertmanager_config_for_opsgenie(
- config.OpsGenieConfig(
- {"api_key": "foobar", "heartbeat": "prod"}
- )
- )
- }
- },
- ),
- "valuesFrom": [],
- },
- },
- ],
- id="opsgenie",
- ),
- pytest.param(
- textwrap.dedent(
- """\
- [kube_prometheus_stack]
- enabled = false
- """
- ),
- [],
- id="disabled",
- ),
- ],
-)
-def test_kube_prometheus_stack_tasks_from_config(pykube, cfg_data, expected):
- cfg = config.Config.from_string(cfg_data, validate=False)
- cfg.kube_prometheus_stack.validate()
-
- assert [
- t.generate_object().obj
- for t in openstack_helm.kube_prometheus_stack_tasks_from_config(
- cfg.kube_prometheus_stack, opsgenie=cfg.opsgenie
- )
- ] == expected
-
-
-@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": {"crds": "CreateReplace", "disableWait": True},
- "interval": "60s",
- "upgrade": {"crds": "CreateReplace", "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": {"crds": "CreateReplace", "disableWait": True},
- "interval": "60s",
- "upgrade": {"crds": "CreateReplace", "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/molecule/default/destroy.yml b/molecule/default/destroy.yml
index 71c277d..b1c3516 100644
--- a/molecule/default/destroy.yml
+++ b/molecule/default/destroy.yml
@@ -25,6 +25,80 @@
when:
- ansible_host is not defined
+ - name: Describe all cluster-scoped objects
+ become: true
+ shell: |-
+ set -e
+ export OBJECT_TYPE=node,clusterrole,clusterrolebinding,storageclass,namespace
+ export PARALLELISM_FACTOR=4
+ function list_objects () {
+ printf ${OBJECT_TYPE} | xargs -d ',' -I {} -P1 -n1 bash -c 'echo "$@"' _ {}
+ }
+ export -f list_objects
+ function name_objects () {
+ export OBJECT=$1
+ kubectl get ${OBJECT} -o name | xargs -L1 -I {} -P1 -n1 bash -c 'echo "${OBJECT} ${1#*/}"' _ {}
+ }
+ export -f name_objects
+ function get_objects () {
+ input=($1)
+ export OBJECT=${input[0]}
+ export NAME=${input[1]#*/}
+ echo "${OBJECT}/${NAME}"
+ DIR="{{ logs_dir }}/objects/cluster/${OBJECT}"
+ mkdir -p ${DIR}
+ kubectl get ${OBJECT} ${NAME} -o yaml > "${DIR}/${NAME}.yaml"
+ kubectl describe ${OBJECT} ${NAME} > "${DIR}/${NAME}.txt"
+ }
+ export -f get_objects
+ list_objects | \
+ xargs -r -n 1 -P ${PARALLELISM_FACTOR} -I {} bash -c 'name_objects "$@"' _ {} | \
+ xargs -r -n 1 -P ${PARALLELISM_FACTOR} -I {} bash -c 'get_objects "$@"' _ {}
+ args:
+ executable: /bin/bash
+ ignore_errors: True
+
+ - name: Describe all namespace-scoped objects
+ become: true
+ shell: |-
+ set -e
+ export OBJECT_TYPE=configmaps,cronjobs,daemonsets,deployment,endpoints,ingresses,jobs,networkpolicies,pods,podsecuritypolicies,persistentvolumeclaims,rolebindings,roles,secrets,serviceaccounts,services,statefulsets
+ export PARALLELISM_FACTOR=4
+ function get_namespaces () {
+ kubectl get namespaces -o name | awk -F '/' '{ print $NF }'
+ }
+ function list_namespaced_objects () {
+ export NAMESPACE=$1
+ printf ${OBJECT_TYPE} | xargs -d ',' -I {} -P1 -n1 bash -c 'echo "${NAMESPACE} $@"' _ {}
+ }
+ export -f list_namespaced_objects
+ function name_objects () {
+ input=($1)
+ export NAMESPACE=${input[0]}
+ export OBJECT=${input[1]}
+ kubectl get -n ${NAMESPACE} ${OBJECT} -o name | xargs -L1 -I {} -P1 -n1 bash -c 'echo "${NAMESPACE} ${OBJECT} $@"' _ {}
+ }
+ export -f name_objects
+ function get_objects () {
+ input=($1)
+ export NAMESPACE=${input[0]}
+ export OBJECT=${input[1]}
+ export NAME=${input[2]#*/}
+ echo "${NAMESPACE}/${OBJECT}/${NAME}"
+ DIR="{{ logs_dir }}/objects/namespaced/${NAMESPACE}/${OBJECT}"
+ mkdir -p ${DIR}
+ kubectl get -n ${NAMESPACE} ${OBJECT} ${NAME} -o yaml > "${DIR}/${NAME}.yaml"
+ kubectl describe -n ${NAMESPACE} ${OBJECT} ${NAME} > "${DIR}/${NAME}.txt"
+ }
+ export -f get_objects
+ get_namespaces | \
+ xargs -r -n 1 -P ${PARALLELISM_FACTOR} -I {} bash -c 'list_namespaced_objects "$@"' _ {} | \
+ xargs -r -n 1 -P ${PARALLELISM_FACTOR} -I {} bash -c 'name_objects "$@"' _ {} | \
+ xargs -r -n 1 -P ${PARALLELISM_FACTOR} -I {} bash -c 'get_objects "$@"' _ {}
+ args:
+ executable: /bin/bash
+ ignore_errors: True
+
- name: Retrieve all container logs, current and previous (if they exist)
become: true
shell: |-
diff --git a/poetry.lock b/poetry.lock
index 77caa2b..4d982f6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -82,14 +82,6 @@
python-dateutil = ">=2.7.0"
[[package]]
-name = "asn1crypto"
-version = "1.5.1"
-description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP"
-category = "main"
-optional = true
-python-versions = "*"
-
-[[package]]
name = "async-timeout"
version = "4.0.2"
description = "Timeout context manager for asyncio programs"
@@ -145,18 +137,6 @@
python-versions = "~=3.7"
[[package]]
-name = "certbuilder"
-version = "0.14.2"
-description = "Creates and signs X.509 certificates"
-category = "main"
-optional = true
-python-versions = "*"
-
-[package.dependencies]
-asn1crypto = ">=0.18.1"
-oscrypto = ">=0.16.1"
-
-[[package]]
name = "certifi"
version = "2022.12.7"
description = "Python package for providing Mozilla's CA Bundle."
@@ -271,7 +251,7 @@
[[package]]
name = "cryptography"
-version = "38.0.4"
+version = "39.0.0"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
@@ -281,9 +261,9 @@
cffi = ">=1.12"
[package.extras]
-docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"]
docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
-pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
+pep8test = ["black", "ruff"]
sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
@@ -463,6 +443,35 @@
test = ["faulthandler", "objgraph", "psutil"]
[[package]]
+name = "hypothesis"
+version = "6.61.0"
+description = "A library for property-based testing"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+attrs = ">=19.2.0"
+exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+sortedcontainers = ">=2.1.0,<3.0.0"
+
+[package.extras]
+all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=1.0)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2022.7)"]
+cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"]
+codemods = ["libcst (>=0.3.16)"]
+dateutil = ["python-dateutil (>=1.4)"]
+django = ["django (>=3.2)"]
+dpcontracts = ["dpcontracts (>=0.4)"]
+ghostwriter = ["black (>=19.10b0)"]
+lark = ["lark (>=0.10.1)"]
+numpy = ["numpy (>=1.9.0)"]
+pandas = ["pandas (>=1.0)"]
+pytest = ["pytest (>=4.6)"]
+pytz = ["pytz (>=2014.1)"]
+redis = ["redis (>=3.0.0)"]
+zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.7)"]
+
+[[package]]
name = "idna"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
@@ -848,17 +857,6 @@
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
[[package]]
-name = "oscrypto"
-version = "1.3.0"
-description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD."
-category = "main"
-optional = true
-python-versions = "*"
-
-[package.dependencies]
-asn1crypto = ">=1.5.1"
-
-[[package]]
name = "oslo.concurrency"
version = "5.0.1"
description = "Oslo Concurrency library"
@@ -1033,14 +1031,14 @@
[[package]]
name = "pydantic"
-version = "1.10.2"
+version = "1.10.4"
description = "Data validation and settings management using python type hints"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
-typing-extensions = ">=4.1.0"
+typing-extensions = ">=4.2.0"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
@@ -1067,7 +1065,7 @@
[[package]]
name = "Pygments"
-version = "2.13.0"
+version = "2.14.0"
description = "Pygments is a syntax highlighting package written in Python."
category = "dev"
optional = false
@@ -1125,7 +1123,7 @@
[[package]]
name = "pyrsistent"
-version = "0.19.2"
+version = "0.19.3"
description = "Persistent/Functional/Immutable data structures"
category = "main"
optional = false
@@ -1315,6 +1313,23 @@
test = ["commentjson", "packaging", "pytest"]
[[package]]
+name = "responses"
+version = "0.22.0"
+description = "A utility library for mocking out the `requests` Python library."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+requests = ">=2.22.0,<3.0"
+toml = "*"
+types-toml = "*"
+urllib3 = ">=1.25.10"
+
+[package.extras]
+tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "types-requests"]
+
+[[package]]
name = "rfc3986"
version = "2.0.0"
description = "Validating URI References per RFC 3986"
@@ -1327,11 +1342,11 @@
[[package]]
name = "rich"
-version = "12.6.0"
+version = "13.0.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "dev"
optional = false
-python-versions = ">=3.6.3,<4.0.0"
+python-versions = ">=3.7.0"
[package.dependencies]
commonmark = ">=0.9.0,<0.10.0"
@@ -1357,6 +1372,14 @@
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
name = "stevedore"
version = "4.1.1"
description = "Manage dynamic plugins for Python applications"
@@ -1442,6 +1465,14 @@
python-versions = "*"
[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
@@ -1492,6 +1523,14 @@
test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
[[package]]
+name = "types-toml"
+version = "0.10.8.1"
+description = "Typing stubs for toml"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
name = "typing-extensions"
version = "4.4.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
@@ -1526,8 +1565,22 @@
test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"]
[[package]]
+name = "validators"
+version = "0.20.0"
+description = "Python Data Validation for Humans™."
+category = "main"
+optional = true
+python-versions = ">=3.4"
+
+[package.dependencies]
+decorator = ">=3.4.0"
+
+[package.extras]
+test = ["flake8 (>=2.4.0)", "isort (>=4.2.2)", "pytest (>=2.2.3)"]
+
+[[package]]
name = "watchdog"
-version = "2.2.0"
+version = "2.2.1"
description = "Filesystem events monitoring"
category = "dev"
optional = false
@@ -1565,12 +1618,12 @@
multidict = ">=4.0"
[extras]
-operator = ["schematics", "pykube-ng", "structlog", "mergedeep", "taskflow", "eventlet", "tomli", "jsonnet", "kopf", "openstacksdk", "certbuilder"]
+operator = ["schematics", "pydantic", "pykube-ng", "structlog", "mergedeep", "taskflow", "eventlet", "tomli", "jsonnet", "kopf", "openstacksdk", "validators"]
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
-content-hash = "566e542d5ec662462d50aa2cf5317f0ee1cfe447d87330a756aac85c18069505"
+content-hash = "505316358da7d5be5cb22c3f8e87eb1fd96183738c8baad35d615eda1be7594c"
[metadata.files]
aiohttp = [
@@ -1682,10 +1735,6 @@
{file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"},
{file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"},
]
-asn1crypto = [
- {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"},
- {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"},
-]
async-timeout = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
@@ -1706,10 +1755,6 @@
{file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"},
{file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"},
]
-certbuilder = [
- {file = "certbuilder-0.14.2-py2.py3-none-any.whl", hash = "sha256:feed83d15b20c149debc1b73a7eb74b8e8041c78d3a8deb989e21905b4394a30"},
- {file = "certbuilder-0.14.2.tar.gz", hash = "sha256:56a8aee8ed31a211678647797dfdcdc85ec25d5d1bb1515e44ebae45cce363f9"},
-]
certifi = [
{file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
{file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
@@ -1862,32 +1907,29 @@
{file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"},
]
cryptography = [
- {file = "cryptography-38.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70"},
- {file = "cryptography-38.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb"},
- {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d"},
- {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1"},
- {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8"},
- {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db"},
- {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b"},
- {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c"},
- {file = "cryptography-38.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00"},
- {file = "cryptography-38.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0"},
- {file = "cryptography-38.0.4-cp36-abi3-win32.whl", hash = "sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744"},
- {file = "cryptography-38.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d"},
- {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353"},
- {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7"},
- {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd"},
- {file = "cryptography-38.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2"},
- {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b"},
- {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6"},
- {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876"},
- {file = "cryptography-38.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b"},
- {file = "cryptography-38.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285"},
- {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b"},
- {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083"},
- {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee"},
- {file = "cryptography-38.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9"},
- {file = "cryptography-38.0.4.tar.gz", hash = "sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290"},
+ {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288"},
+ {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72"},
+ {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9"},
+ {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f"},
+ {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b"},
+ {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c"},
+ {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96"},
+ {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717"},
+ {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df"},
+ {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1"},
+ {file = "cryptography-39.0.0-cp36-abi3-win32.whl", hash = "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de"},
+ {file = "cryptography-39.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e"},
+ {file = "cryptography-39.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb"},
+ {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f"},
+ {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458"},
+ {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190"},
+ {file = "cryptography-39.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773"},
+ {file = "cryptography-39.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856"},
+ {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f"},
+ {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce"},
+ {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8"},
+ {file = "cryptography-39.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39"},
+ {file = "cryptography-39.0.0.tar.gz", hash = "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf"},
]
debtcollector = [
{file = "debtcollector-2.5.0-py3-none-any.whl", hash = "sha256:1393a527d2c72f143ffa6a629e9c33face6642634eece475b48cab7b04ba61f3"},
@@ -2055,7 +2097,6 @@
{file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b0ff9878333823226d270417f24f4d06f235cb3e54d1103b71ea537a6a86ce"},
{file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be9e0fb2ada7e5124f5282d6381903183ecc73ea019568d6d63d33f25b2a9000"},
{file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b493db84d124805865adc587532ebad30efa68f79ad68f11b336e0a51ec86c2"},
- {file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0459d94f73265744fee4c2d5ec44c6f34aa8a31017e6e9de770f7bcf29710be9"},
{file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a20d33124935d27b80e6fdacbd34205732660e0a1d35d8b10b3328179a2b51a1"},
{file = "greenlet-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:ea688d11707d30e212e0110a1aac7f7f3f542a259235d396f88be68b649e47d1"},
{file = "greenlet-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:afe07421c969e259e9403c3bb658968702bc3b78ec0b6fde3ae1e73440529c23"},
@@ -2064,7 +2105,6 @@
{file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:659f167f419a4609bc0516fb18ea69ed39dbb25594934bd2dd4d0401660e8a1e"},
{file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:356e4519d4dfa766d50ecc498544b44c0249b6de66426041d7f8b751de4d6b48"},
{file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:811e1d37d60b47cb8126e0a929b58c046251f28117cb16fcd371eed61f66b764"},
- {file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d38ffd0e81ba8ef347d2be0772e899c289b59ff150ebbbbe05dc61b1246eb4e0"},
{file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0109af1138afbfb8ae647e31a2b1ab030f58b21dd8528c27beaeb0093b7938a9"},
{file = "greenlet-2.0.1-cp38-cp38-win32.whl", hash = "sha256:88c8d517e78acdf7df8a2134a3c4b964415b575d2840a2746ddb1cc6175f8608"},
{file = "greenlet-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:d6ee1aa7ab36475035eb48c01efae87d37936a8173fc4d7b10bb02c2d75dd8f6"},
@@ -2073,12 +2113,15 @@
{file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:505138d4fa69462447a562a7c2ef723c6025ba12ac04478bc1ce2fcc279a2db5"},
{file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce1e90dd302f45716a7715517c6aa0468af0bf38e814ad4eab58e88fc09f7f7"},
{file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e9744c657d896c7b580455e739899e492a4a452e2dd4d2b3e459f6b244a638d"},
- {file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:662e8f7cad915ba75d8017b3e601afc01ef20deeeabf281bd00369de196d7726"},
{file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:41b825d65f31e394b523c84db84f9383a2f7eefc13d987f308f4663794d2687e"},
{file = "greenlet-2.0.1-cp39-cp39-win32.whl", hash = "sha256:db38f80540083ea33bdab614a9d28bcec4b54daa5aff1668d7827a9fc769ae0a"},
{file = "greenlet-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6"},
{file = "greenlet-2.0.1.tar.gz", hash = "sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67"},
]
+hypothesis = [
+ {file = "hypothesis-6.61.0-py3-none-any.whl", hash = "sha256:7bb22d22e35db99d5724bbf5bdc686b46add94a0f228bf1be249c47ec46b9c7f"},
+ {file = "hypothesis-6.61.0.tar.gz", hash = "sha256:fbf7da30aea839d88898f74bcc027f0f997060498a8a7605880688c8a2166215"},
+]
idna = [
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
@@ -2386,10 +2429,6 @@
{file = "os-service-types-1.7.0.tar.gz", hash = "sha256:31800299a82239363995b91f1ebf9106ac7758542a1e4ef6dc737a5932878c6c"},
{file = "os_service_types-1.7.0-py2.py3-none-any.whl", hash = "sha256:0505c72205690910077fb72b88f2a1f07533c8d39f2fe75b29583481764965d6"},
]
-oscrypto = [
- {file = "oscrypto-1.3.0-py2.py3-none-any.whl", hash = "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085"},
- {file = "oscrypto-1.3.0.tar.gz", hash = "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4"},
-]
"oslo.concurrency" = [
{file = "oslo.concurrency-5.0.1-py3-none-any.whl", hash = "sha256:7a6b6a1aa28e65a7cbfc9e04ecc9cdcaf7f7a2ad6cdf37014b3694b627280f5f"},
{file = "oslo.concurrency-5.0.1.tar.gz", hash = "sha256:0dfbf36095f4637ffbb65e5c241f4c25851abd3b728dddad9f07396302a72544"},
@@ -2443,42 +2482,42 @@
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pydantic = [
- {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
- {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"},
- {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"},
- {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"},
- {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"},
- {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"},
- {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"},
- {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"},
- {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"},
- {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"},
- {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"},
- {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"},
- {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"},
- {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"},
- {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"},
- {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
- {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
- {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"},
- {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"},
- {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"},
- {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"},
- {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"},
- {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"},
- {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"},
- {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
- {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"},
- {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"},
- {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"},
- {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"},
- {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"},
- {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"},
- {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"},
- {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"},
- {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"},
- {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
- {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
+ {file = "pydantic-1.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854"},
+ {file = "pydantic-1.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817"},
+ {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c"},
+ {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e"},
+ {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85"},
+ {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6"},
+ {file = "pydantic-1.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d"},
+ {file = "pydantic-1.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53"},
+ {file = "pydantic-1.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3"},
+ {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a"},
+ {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"},
+ {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72"},
+ {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857"},
+ {file = "pydantic-1.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3"},
+ {file = "pydantic-1.10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00"},
+ {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978"},
+ {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e"},
+ {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa"},
+ {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8"},
+ {file = "pydantic-1.10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903"},
+ {file = "pydantic-1.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423"},
+ {file = "pydantic-1.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f"},
+ {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06"},
+ {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15"},
+ {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c"},
+ {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416"},
+ {file = "pydantic-1.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6"},
+ {file = "pydantic-1.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d"},
+ {file = "pydantic-1.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f"},
+ {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024"},
+ {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c"},
+ {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28"},
+ {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f"},
+ {file = "pydantic-1.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4"},
+ {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"},
@@ -2489,8 +2528,8 @@
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
]
Pygments = [
- {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
- {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
+ {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
+ {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
]
pyinotify = [
{file = "pyinotify-0.9.6.tar.gz", hash = "sha256:9c998a5d7606ca835065cdabc013ae6c66eb9ea76a00a1e3bc6e0cfe2b4f71f4"},
@@ -2508,28 +2547,33 @@
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pyrsistent = [
- {file = "pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"},
- {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"},
- {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed"},
- {file = "pyrsistent-0.19.2-cp310-cp310-win32.whl", hash = "sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41"},
- {file = "pyrsistent-0.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425"},
- {file = "pyrsistent-0.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712"},
- {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78"},
- {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6"},
- {file = "pyrsistent-0.19.2-cp37-cp37m-win32.whl", hash = "sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2"},
- {file = "pyrsistent-0.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73"},
- {file = "pyrsistent-0.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308"},
- {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584"},
- {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb"},
- {file = "pyrsistent-0.19.2-cp38-cp38-win32.whl", hash = "sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a"},
- {file = "pyrsistent-0.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab"},
- {file = "pyrsistent-0.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770"},
- {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95"},
- {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e"},
- {file = "pyrsistent-0.19.2-cp39-cp39-win32.whl", hash = "sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b"},
- {file = "pyrsistent-0.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291"},
- {file = "pyrsistent-0.19.2-py3-none-any.whl", hash = "sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0"},
- {file = "pyrsistent-0.19.2.tar.gz", hash = "sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2"},
+ {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"},
+ {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"},
+ {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"},
+ {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"},
+ {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"},
+ {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"},
+ {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"},
+ {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"},
+ {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"},
+ {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"},
+ {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"},
+ {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"},
+ {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"},
+ {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"},
+ {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"},
+ {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"},
+ {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"},
+ {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"},
+ {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"},
+ {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"},
+ {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"},
+ {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"},
+ {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"},
+ {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"},
+ {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"},
+ {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"},
+ {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"},
]
pytest = [
{file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
@@ -2715,13 +2759,17 @@
{file = "resolvelib-0.8.1-py2.py3-none-any.whl", hash = "sha256:d9b7907f055c3b3a2cfc56c914ffd940122915826ff5fb5b1de0c99778f4de98"},
{file = "resolvelib-0.8.1.tar.gz", hash = "sha256:c6ea56732e9fb6fca1b2acc2ccc68a0b6b8c566d8f3e78e0443310ede61dbd37"},
]
+responses = [
+ {file = "responses-0.22.0-py3-none-any.whl", hash = "sha256:dcf294d204d14c436fddcc74caefdbc5764795a40ff4e6a7740ed8ddbf3294be"},
+ {file = "responses-0.22.0.tar.gz", hash = "sha256:396acb2a13d25297789a5866b4881cf4e46ffd49cc26c43ab1117f40b973102e"},
+]
rfc3986 = [
{file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"},
{file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"},
]
rich = [
- {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
- {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"},
+ {file = "rich-13.0.0-py3-none-any.whl", hash = "sha256:12b1d77ee7edf251b741531323f0d990f5f570a4e7c054d0bfb59fb7981ad977"},
+ {file = "rich-13.0.0.tar.gz", hash = "sha256:3aa9eba7219b8c575c6494446a59f702552efe1aa261e7eeb95548fa586e1950"},
]
schematics = [
{file = "schematics-2.1.1-py2.py3-none-any.whl", hash = "sha256:be2d451bfb86789975e5ec0864aec569b63cea9010f0d24cbbd992a4e564c647"},
@@ -2731,6 +2779,10 @@
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
+sortedcontainers = [
+ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
+ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
+]
stevedore = [
{file = "stevedore-4.1.1-py3-none-any.whl", hash = "sha256:aa6436565c069b2946fe4ebff07f5041e0c8bf18c7376dd29edf80cf7d524e4e"},
{file = "stevedore-4.1.1.tar.gz", hash = "sha256:7f8aeb6e3f90f96832c301bff21a7eb5eefbe894c88c506483d355565d88cc1a"},
@@ -2755,6 +2807,10 @@
{file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
]
+toml = [
+ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
@@ -2771,6 +2827,10 @@
{file = "typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"},
{file = "typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"},
]
+types-toml = [
+ {file = "types-toml-0.10.8.1.tar.gz", hash = "sha256:171bdb3163d79a520560f24ba916a9fc9bff81659c5448a9fea89240923722be"},
+ {file = "types_toml-0.10.8.1-py3-none-any.whl", hash = "sha256:b7b5c4977f96ab7b5ac06d8a6590d17c0bf252a96efc03b109c2711fb3e0eafd"},
+]
typing-extensions = [
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
@@ -2811,35 +2871,38 @@
{file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"},
{file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"},
]
+validators = [
+ {file = "validators-0.20.0.tar.gz", hash = "sha256:24148ce4e64100a2d5e267233e23e7afeb55316b47d30faae7eb6e7292bc226a"},
+]
watchdog = [
- {file = "watchdog-2.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed91c3ccfc23398e7aa9715abf679d5c163394b8cad994f34f156d57a7c163dc"},
- {file = "watchdog-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:76a2743402b794629a955d96ea2e240bd0e903aa26e02e93cd2d57b33900962b"},
- {file = "watchdog-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:920a4bda7daa47545c3201a3292e99300ba81ca26b7569575bd086c865889090"},
- {file = "watchdog-2.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ceaa9268d81205876bedb1069f9feab3eccddd4b90d9a45d06a0df592a04cae9"},
- {file = "watchdog-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1893d425ef4fb4f129ee8ef72226836619c2950dd0559bba022b0818c63a7b60"},
- {file = "watchdog-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e99c1713e4436d2563f5828c8910e5ff25abd6ce999e75f15c15d81d41980b6"},
- {file = "watchdog-2.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a5bd9e8656d07cae89ac464ee4bcb6f1b9cecbedc3bf1334683bed3d5afd39ba"},
- {file = "watchdog-2.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a048865c828389cb06c0bebf8a883cec3ae58ad3e366bcc38c61d8455a3138f"},
- {file = "watchdog-2.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e722755d995035dd32177a9c633d158f2ec604f2a358b545bba5bed53ab25bca"},
- {file = "watchdog-2.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af4b5c7ba60206759a1d99811b5938ca666ea9562a1052b410637bb96ff97512"},
- {file = "watchdog-2.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:619d63fa5be69f89ff3a93e165e602c08ed8da402ca42b99cd59a8ec115673e1"},
- {file = "watchdog-2.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f2b0665c57358ce9786f06f5475bc083fea9d81ecc0efa4733fd0c320940a37"},
- {file = "watchdog-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:441024df19253bb108d3a8a5de7a186003d68564084576fecf7333a441271ef7"},
- {file = "watchdog-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a410dd4d0adcc86b4c71d1317ba2ea2c92babaf5b83321e4bde2514525544d5"},
- {file = "watchdog-2.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28704c71afdb79c3f215c90231e41c52b056ea880b6be6cee035c6149d658ed1"},
- {file = "watchdog-2.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ac0bd7c206bb6df78ef9e8ad27cc1346f2b41b1fef610395607319cdab89bc1"},
- {file = "watchdog-2.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27e49268735b3c27310883012ab3bd86ea0a96dcab90fe3feb682472e30c90f3"},
- {file = "watchdog-2.2.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:2af1a29fd14fc0a87fb6ed762d3e1ae5694dcde22372eebba50e9e5be47af03c"},
- {file = "watchdog-2.2.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:c7bd98813d34bfa9b464cf8122e7d4bec0a5a427399094d2c17dd5f70d59bc61"},
- {file = "watchdog-2.2.0-py3-none-manylinux2014_i686.whl", hash = "sha256:56fb3f40fc3deecf6e518303c7533f5e2a722e377b12507f6de891583f1b48aa"},
- {file = "watchdog-2.2.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:74535e955359d79d126885e642d3683616e6d9ab3aae0e7dcccd043bd5a3ff4f"},
- {file = "watchdog-2.2.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cf05e6ff677b9655c6e9511d02e9cc55e730c4e430b7a54af9c28912294605a4"},
- {file = "watchdog-2.2.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:d6ae890798a3560688b441ef086bb66e87af6b400a92749a18b856a134fc0318"},
- {file = "watchdog-2.2.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5aed2a700a18c194c39c266900d41f3db0c1ebe6b8a0834b9995c835d2ca66e"},
- {file = "watchdog-2.2.0-py3-none-win32.whl", hash = "sha256:d0fb5f2b513556c2abb578c1066f5f467d729f2eb689bc2db0739daf81c6bb7e"},
- {file = "watchdog-2.2.0-py3-none-win_amd64.whl", hash = "sha256:1f8eca9d294a4f194ce9df0d97d19b5598f310950d3ac3dd6e8d25ae456d4c8a"},
- {file = "watchdog-2.2.0-py3-none-win_ia64.whl", hash = "sha256:ad0150536469fa4b693531e497ffe220d5b6cd76ad2eda474a5e641ee204bbb6"},
- {file = "watchdog-2.2.0.tar.gz", hash = "sha256:83cf8bc60d9c613b66a4c018051873d6273d9e45d040eed06d6a96241bd8ec01"},
+ {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"},
+ {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"},
+ {file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"},
+ {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1"},
+ {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3"},
+ {file = "watchdog-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3"},
+ {file = "watchdog-2.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7"},
+ {file = "watchdog-2.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d"},
+ {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae"},
+ {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9"},
+ {file = "watchdog-2.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"},
+ {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8"},
+ {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640"},
+ {file = "watchdog-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e"},
+ {file = "watchdog-2.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd"},
+ {file = "watchdog-2.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e"},
+ {file = "watchdog-2.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8"},
+ {file = "watchdog-2.2.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344"},
+ {file = "watchdog-2.2.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce"},
+ {file = "watchdog-2.2.1-py3-none-manylinux2014_i686.whl", hash = "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c"},
+ {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646"},
+ {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b"},
+ {file = "watchdog-2.2.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c"},
+ {file = "watchdog-2.2.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e"},
+ {file = "watchdog-2.2.1-py3-none-win32.whl", hash = "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73"},
+ {file = "watchdog-2.2.1-py3-none-win_amd64.whl", hash = "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e"},
+ {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.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
diff --git a/pyproject.toml b/pyproject.toml
index dbec531..71fa6b6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,6 +12,7 @@
[tool.poetry.dependencies]
python = "^3.10"
click = "^8.1.3"
+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 }
@@ -22,7 +23,7 @@
jsonnet = { version = "^0.18.0", optional = true }
kopf = { version = "^1.36.0", optional = true, extras = ["uvloop"] }
openstacksdk = { version = "<0.99.0", optional = true }
-certbuilder = { version = "^0.14.2", optional = true }
+validators = { version = "^0.20.0", optional = true }
"oslo.log" = "^5.0.2"
"oslo.config" = "^9.0.0"
"oslo.concurrency" = "^5.0.1"
@@ -31,10 +32,9 @@
[tool.poetry.extras]
operator = [
"schematics",
+ "pydantic",
"pykube-ng",
"structlog",
- "rich",
- "better-exceptions",
"mergedeep",
"taskflow",
"eventlet",
@@ -42,23 +42,25 @@
"jsonnet",
"kopf",
"openstacksdk",
- "certbuilder",
+ "validators",
]
[tool.poetry.group.dev.dependencies]
ansible-core = "^2.13.4"
flake8 = "^5.0.4"
flake8-isort = "^4.2.0"
+hypothesis = "6.61.0"
Jinja2 = "^3.1.2"
-molecule = "^4.0.1"
+jinja2-ansible-filters = "^1.3.2"
jmespath = "^1.0.1"
+molecule = "^4.0.1"
pytest = "^7.1.3"
pytest-cov = "^3.0.0"
pytest-kind = "^22.9.0"
pytest-mock = "^3.8.2"
python-on-whales = "^0.52.0"
tomli-w = "^1.0.0"
-jinja2-ansible-filters = "^1.3.2"
+responses = "^0.22.0"
[tool.poetry.group.docs.dependencies]
mkdocs-material = "^8.5.7"
diff --git a/roles/atmosphere/templates/crds.yml b/roles/atmosphere/templates/crds.yml
index 2a31263..db56695 100644
--- a/roles/atmosphere/templates/crds.yml
+++ b/roles/atmosphere/templates/crds.yml
@@ -2,6 +2,58 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
+ name: openstackhelmrabbitmqclusters.atmosphere.vexxhost.com
+spec:
+ scope: Namespaced
+ group: atmosphere.vexxhost.com
+ names:
+ kind: OpenstackHelmRabbitmqCluster
+ plural: openstackhelmrabbitmqclusters
+ singular: openstackhelmrabbitmqcluster
+ 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: 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/atmosphere/templates/deployment.yml b/roles/atmosphere/templates/deployment.yml
index 64066dc..3242dd0 100644
--- a/roles/atmosphere/templates/deployment.yml
+++ b/roles/atmosphere/templates/deployment.yml
@@ -19,11 +19,6 @@
containers:
- name: operator
image: "{{ atmosphere_image }}"
- env:
- - name: POD_IP
- valueFrom:
- fieldRef:
- fieldPath: status.podIP
volumeMounts:
- name: config
mountPath: /etc/atmosphere
diff --git a/roles/atmosphere/vars/main.yml b/roles/atmosphere/vars/main.yml
index 8801f39..31b9832 100644
--- a/roles/atmosphere/vars/main.yml
+++ b/roles/atmosphere/vars/main.yml
@@ -1,5 +1,5 @@
_atmosphere_cloud_spec:
- imageRepository: "{{ atmosphere_image_repository | default('quay.io/vexxhost') }}"
+ imageRepository: "{{ atmosphere_image_repository | default(None) }}"
ingressClassName: "{{ openstack_helm_ingress_class_name | default('openstack') }}"
certManagerClusterIssuer: "{{ openstack_helm_ingress_cluster_issuer | default('atmosphere') }}"
regionName: "{{ openstack_helm_endpoints_region_name }}"
diff --git a/roles/certificates/tasks/main.yml b/roles/certificates/tasks/main.yml
index 4cdb916..e47b897 100644
--- a/roles/certificates/tasks/main.yml
+++ b/roles/certificates/tasks/main.yml
@@ -25,7 +25,7 @@
namespace: cert-manager
wait: true
wait_sleep: 1
- wait_timeout: 300
+ wait_timeout: 600
register: _openstack_helm_root_secret
- name: Copy CA certificate on host
diff --git a/roles/openstack_helm_barbican/meta/main.yml b/roles/openstack_helm_barbican/meta/main.yml
index f479c17..3f8b4a4 100644
--- a/roles/openstack_helm_barbican/meta/main.yml
+++ b/roles/openstack_helm_barbican/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: atmosphere
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_cinder/meta/main.yml b/roles/openstack_helm_cinder/meta/main.yml
index e44991a..83f86ce 100644
--- a/roles/openstack_helm_cinder/meta/main.yml
+++ b/roles/openstack_helm_cinder/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: atmosphere
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_designate/meta/main.yml b/roles/openstack_helm_designate/meta/main.yml
index c0d03fc..469958a 100644
--- a/roles/openstack_helm_designate/meta/main.yml
+++ b/roles/openstack_helm_designate/meta/main.yml
@@ -24,3 +24,4 @@
dependencies:
- role: atmosphere
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_glance/meta/main.yml b/roles/openstack_helm_glance/meta/main.yml
index d286883..baf7a6d 100644
--- a/roles/openstack_helm_glance/meta/main.yml
+++ b/roles/openstack_helm_glance/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: openstacksdk
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_heat/meta/main.yml b/roles/openstack_helm_heat/meta/main.yml
index 3a7140b..57e2e1c 100644
--- a/roles/openstack_helm_heat/meta/main.yml
+++ b/roles/openstack_helm_heat/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: atmosphere
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_horizon/meta/main.yml b/roles/openstack_helm_horizon/meta/main.yml
index f101c63..f0271e3 100644
--- a/roles/openstack_helm_horizon/meta/main.yml
+++ b/roles/openstack_helm_horizon/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: atmosphere
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_ingress/tasks/main.yml b/roles/openstack_helm_ingress/tasks/main.yml
index 8ba0a52..cf2b287 100644
--- a/roles/openstack_helm_ingress/tasks/main.yml
+++ b/roles/openstack_helm_ingress/tasks/main.yml
@@ -16,26 +16,13 @@
kubernetes.core.k8s:
state: present
definition:
- apiVersion: v1
- kind: Ingress
+ apiVersion: atmosphere.vexxhost.com/v1alpha1
+ kind: OpenstackHelmIngress
metadata:
name: "{{ openstack_helm_ingress_endpoint | replace('_', '-') }}"
namespace: openstack
- annotations: "{{ _openstack_helm_ingress_annotations | combine(openstack_helm_ingress_annotations, recursive=True) }}"
+ annotations: "{{ openstack_helm_ingress_annotations }}"
spec:
+ clusterIssuer: "{{ openstack_helm_ingress_cluster_issuer | default('atmosphere') }}"
ingressClassName: "{{ openstack_helm_ingress_class_name | default('openstack') }}"
- rules:
- - host: "{{ openstack_helm_endpoints[openstack_helm_ingress_endpoint]['host_fqdn_override']['public']['host'] }}"
- http:
- paths: "{{ _openstack_helm_ingress_paths }}"
- tls:
- - secretName: "{{ openstack_helm_ingress_secret_name | default(openstack_helm_ingress_service_name + '-certs') }}"
- hosts:
- - "{{ openstack_helm_endpoints[openstack_helm_ingress_endpoint]['host_fqdn_override']['public']['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
+ host: "{{ openstack_helm_endpoints[openstack_helm_ingress_endpoint]['host_fqdn_override']['public']['host'] }}"
diff --git a/roles/openstack_helm_ingress/vars/main.yml b/roles/openstack_helm_ingress/vars/main.yml
deleted file mode 100644
index 0af5c42..0000000
--- a/roles/openstack_helm_ingress/vars/main.yml
+++ /dev/null
@@ -1,26 +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.
-
-_openstack_helm_ingress_annotations:
- cert-manager.io/cluster-issuer: "{{ openstack_helm_ingress_cluster_issuer | default('atmosphere') }}"
-
-_openstack_helm_ingress_paths: "{{ openstack_helm_ingress_paths + __openstack_helm_ingress_paths }}"
-__openstack_helm_ingress_paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: "{{ openstack_helm_ingress_service_name }}"
- port:
- number: "{{ openstack_helm_ingress_service_port }}"
diff --git a/roles/openstack_helm_keystone/meta/main.yml b/roles/openstack_helm_keystone/meta/main.yml
index 4929cc5..0fcab44 100644
--- a/roles/openstack_helm_keystone/meta/main.yml
+++ b/roles/openstack_helm_keystone/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: atmosphere
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_magnum/meta/main.yml b/roles/openstack_helm_magnum/meta/main.yml
index f2e80be..9b7fe68 100644
--- a/roles/openstack_helm_magnum/meta/main.yml
+++ b/roles/openstack_helm_magnum/meta/main.yml
@@ -25,5 +25,6 @@
dependencies:
- role: openstacksdk
- role: openstack_cli
+ - role: wait_for_pxc
- role: openstack_helm_barbican
- role: openstack_helm_octavia
diff --git a/roles/openstack_helm_neutron/meta/main.yml b/roles/openstack_helm_neutron/meta/main.yml
index ba41c29..b56ee77 100644
--- a/roles/openstack_helm_neutron/meta/main.yml
+++ b/roles/openstack_helm_neutron/meta/main.yml
@@ -26,3 +26,4 @@
dependencies:
- role: atmosphere
- role: openstacksdk
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_nova/meta/main.yml b/roles/openstack_helm_nova/meta/main.yml
index ad12692..2812822 100644
--- a/roles/openstack_helm_nova/meta/main.yml
+++ b/roles/openstack_helm_nova/meta/main.yml
@@ -26,3 +26,4 @@
dependencies:
- role: atmosphere
- role: openstacksdk
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_octavia/meta/main.yml b/roles/openstack_helm_octavia/meta/main.yml
index 181e9ad..96b62d3 100644
--- a/roles/openstack_helm_octavia/meta/main.yml
+++ b/roles/openstack_helm_octavia/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: openstacksdk
- role: openstack_cli
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_placement/meta/main.yml b/roles/openstack_helm_placement/meta/main.yml
index c83011a..37a83d9 100644
--- a/roles/openstack_helm_placement/meta/main.yml
+++ b/roles/openstack_helm_placement/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: atmosphere
+ - role: wait_for_pxc
diff --git a/roles/openstack_helm_senlin/meta/main.yml b/roles/openstack_helm_senlin/meta/main.yml
index 4c25fbe..73308ad 100644
--- a/roles/openstack_helm_senlin/meta/main.yml
+++ b/roles/openstack_helm_senlin/meta/main.yml
@@ -25,3 +25,4 @@
dependencies:
- role: atmosphere
+ - role: wait_for_pxc
diff --git a/roles/wait_for_pxc/README.md b/roles/wait_for_pxc/README.md
new file mode 100644
index 0000000..918f313
--- /dev/null
+++ b/roles/wait_for_pxc/README.md
@@ -0,0 +1,4 @@
+# wait_for_pxc
+
+This is a meta-role which should be used as a dependency for now to allow the
+Ansible roles to wait for the PXC cluster to be ready before proceeding.
diff --git a/roles/wait_for_pxc/tasks/main.yml b/roles/wait_for_pxc/tasks/main.yml
new file mode 100644
index 0000000..5fa3637
--- /dev/null
+++ b/roles/wait_for_pxc/tasks/main.yml
@@ -0,0 +1,12 @@
+- name: Wait until Percona XtraDB Cluster is ready
+ kubernetes.core.k8s_info:
+ api_version: pxc.percona.com/v1
+ kind: PerconaXtraDBCluster
+ name: percona-xtradb
+ namespace: openstack
+ wait_sleep: 1
+ wait_timeout: 600
+ wait: true
+ wait_condition:
+ type: ready
+ status: true