chore: add more tests + add config
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..8675f7e
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+# syntax=docker/dockerfile-upstream:master-labs
+
+FROM python:3.10-slim AS poetry
+RUN --mount=type=cache,target=/root/.cache <<EOF
+ pip install poetry
+EOF
+
+FROM poetry AS builder
+ADD . /app
+WORKDIR /app
+ENV POETRY_VIRTUALENVS_IN_PROJECT=true
+RUN poetry install --only main --no-interaction
+
+FROM python:3.10-slim AS runtime
+ENV PATH="/app/.venv/bin:$PATH"
+COPY --from=builder --link /app /app
+CMD ["atmosphere-operator"]
diff --git a/atmosphere/clients.py b/atmosphere/clients.py
new file mode 100644
index 0000000..ef36537
--- /dev/null
+++ b/atmosphere/clients.py
@@ -0,0 +1,5 @@
+import pykube
+
+
+def get_pykube_api(timeout=None) -> pykube.HTTPClient:
+ return pykube.HTTPClient(pykube.KubeConfig.from_env(), timeout=timeout)
diff --git a/atmosphere/cmd/__init__.py b/atmosphere/cmd/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/atmosphere/cmd/__init__.py
diff --git a/atmosphere/cmd/operator.py b/atmosphere/cmd/operator.py
new file mode 100644
index 0000000..c9b3ab0
--- /dev/null
+++ b/atmosphere/cmd/operator.py
@@ -0,0 +1,5 @@
+from atmosphere import deploy
+
+
+def main():
+ deploy.run()
diff --git a/atmosphere/config/__init__.py b/atmosphere/config/__init__.py
new file mode 100644
index 0000000..98bf87e
--- /dev/null
+++ b/atmosphere/config/__init__.py
@@ -0,0 +1,23 @@
+import os
+import sys
+
+import confspirator
+from confspirator import groups
+
+from atmosphere.config import images, memcached
+
+_root_config = groups.ConfigGroup("atmosphere")
+_root_config.register_child_config(images.config_group)
+_root_config.register_child_config(memcached.config_group)
+
+CONFIG_FILE = os.environ.get('ATMOSPHERE_CONFIG', '/etc/atmosphere/config.toml')
+
+
+def _load_config():
+ if "pytest" in sys.modules:
+ return confspirator.load_dict(_root_config, {}, test_mode=True)
+
+ return confspirator.load_file(_root_config, CONFIG_FILE)
+
+
+CONF = _load_config()
diff --git a/atmosphere/config/images.py b/atmosphere/config/images.py
new file mode 100644
index 0000000..2520187
--- /dev/null
+++ b/atmosphere/config/images.py
@@ -0,0 +1,20 @@
+from confspirator import fields, groups
+
+config_group = groups.ConfigGroup("images")
+
+config_group.register_child_config(
+ fields.StrConfig(
+ "memcached",
+ required=True,
+ default="docker.io/library/memcached:1.6.17",
+ help_text="Memcached image",
+ ),
+)
+config_group.register_child_config(
+ fields.StrConfig(
+ "memcached_exporter",
+ required=True,
+ default="quay.io/prometheus/memcached-exporter:v0.10.0",
+ help_text="Prometheus Memcached exporter image",
+ ),
+)
diff --git a/atmosphere/config/memcached.py b/atmosphere/config/memcached.py
new file mode 100644
index 0000000..44a466c
--- /dev/null
+++ b/atmosphere/config/memcached.py
@@ -0,0 +1,22 @@
+import uuid
+
+from confspirator import fields, groups
+
+config_group = groups.ConfigGroup("memcached")
+
+config_group.register_child_config(
+ fields.BoolConfig(
+ "enabled",
+ default=True,
+ required=True,
+ help_text="Enable Atmosphere-managed Memcached",
+ ),
+)
+config_group.register_child_config(
+ fields.StrConfig(
+ "secret_key",
+ required=True,
+ help_text="Encryption secret key",
+ test_default=uuid.uuid4().hex,
+ ),
+)
diff --git a/atmosphere/deploy.py b/atmosphere/deploy.py
index be76d36..2564976 100644
--- a/atmosphere/deploy.py
+++ b/atmosphere/deploy.py
@@ -1,6 +1,11 @@
+from atmosphere import clients
+from atmosphere.config import CONF
from atmosphere.models.openstack_helm import values
-def run(api, config):
- if config.memcached:
- values.Values.for_chart("memcached", config).apply(api)
+def run(api=None):
+ if not api:
+ api = clients.get_pykube_api()
+
+ if CONF.memcached.enabled:
+ values.Values.for_chart("memcached").apply(api)
diff --git a/atmosphere/models/conf.py b/atmosphere/models/conf.py
deleted file mode 100644
index 73d4f6f..0000000
--- a/atmosphere/models/conf.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import tomli
-from schematics import types
-
-from atmosphere.models import base
-
-
-class ServiceConfig(base.Model):
- enabled = types.BooleanType(default=True)
- overrides = types.DictType(types.BaseType)
-
-
-class MemcachedImagesConfig(base.Model):
- memcached = types.StringType(default="quay.io/vexxhost/memcached:1.6.9")
- prometheus_memcached_exporter = types.StringType(
- default="quay.io/vexxhost/memcached-exporter:v0.9.0-1"
- )
-
-
-class MemcachedServiceConfig(ServiceConfig):
- images = types.ModelType(MemcachedImagesConfig, default=MemcachedImagesConfig())
- secret_key = types.StringType(required=True)
-
-
-class AtmosphereConfig(base.Model):
- memcached = types.ModelType(
- MemcachedServiceConfig, default=MemcachedServiceConfig()
- )
-
-
-def from_toml(data):
- cfg = AtmosphereConfig(tomli.loads(data), validate=True)
- cfg.validate()
- return cfg
-
-
-def from_file(path):
- with open(path) as f:
- return from_toml(f.read())
diff --git a/atmosphere/models/openstack_helm/endpoints.py b/atmosphere/models/openstack_helm/endpoints.py
index 82519a6..bfad161 100644
--- a/atmosphere/models/openstack_helm/endpoints.py
+++ b/atmosphere/models/openstack_helm/endpoints.py
@@ -1,5 +1,6 @@
from schematics import types
+from atmosphere.config import CONF
from atmosphere.models import base
@@ -19,12 +20,12 @@
auth = types.ModelType(OsloCacheEndpointAuth)
@classmethod
- def for_chart(cls, chart, config):
+ def for_chart(cls, chart):
return cls(
{
"auth": OsloCacheEndpointAuth(
{
- "memcache_secret_key": config.memcached.secret_key,
+ "memcache_secret_key": CONF.memcached.secret_key,
}
)
}
@@ -49,7 +50,7 @@
hosts = types.ModelType(OsloDbEndpointHosts, default=OsloDbEndpointHosts())
@classmethod
- def for_chart(cls, chart, config):
+ def for_chart(cls, chart):
pass
@@ -67,13 +68,11 @@
}
@classmethod
- def for_chart(cls, chart, config):
+ def for_chart(cls, chart):
endpoint = cls()
for endpoint_name in cls.ENDPOINTS[chart]:
- endpoint[endpoint_name] = cls.MAPPINGS[endpoint_name].for_chart(
- chart, config
- )
+ endpoint[endpoint_name] = cls.MAPPINGS[endpoint_name].for_chart(chart)
endpoint.validate()
return endpoint
diff --git a/atmosphere/models/openstack_helm/images.py b/atmosphere/models/openstack_helm/images.py
index f807395..32b03dd 100644
--- a/atmosphere/models/openstack_helm/images.py
+++ b/atmosphere/models/openstack_helm/images.py
@@ -1,5 +1,6 @@
from schematics import types
+from atmosphere.config import CONF
from atmosphere.models import base
@@ -12,11 +13,11 @@
prometheus_memcached_exporter = types.StringType(required=True)
@classmethod
- def for_chart(cls, chart, config):
+ def for_chart(cls, chart):
return cls(
{
- "memcached": config.memcached.images.memcached,
- "prometheus_memcached_exporter": config.memcached.images.prometheus_memcached_exporter,
+ "memcached": CONF.images.memcached,
+ "prometheus_memcached_exporter": CONF.images.memcached_exporter,
}
)
@@ -30,9 +31,9 @@
}
@classmethod
- def for_chart(cls, chart, config):
+ def for_chart(cls, chart):
return cls(
{
- "tags": cls.MAPPINGS[chart].for_chart(chart, config),
+ "tags": cls.MAPPINGS[chart].for_chart(chart),
}
)
diff --git a/atmosphere/models/openstack_helm/monitoring.py b/atmosphere/models/openstack_helm/monitoring.py
index c508f3c..e67b656 100644
--- a/atmosphere/models/openstack_helm/monitoring.py
+++ b/atmosphere/models/openstack_helm/monitoring.py
@@ -11,7 +11,7 @@
prometheus = types.ModelType(PrometheusMonitoring)
@classmethod
- def for_chart(cls, chart, config):
+ def for_chart(cls, chart):
if chart == "memcached":
return Monitoring(
{
diff --git a/atmosphere/models/openstack_helm/values.py b/atmosphere/models/openstack_helm/values.py
index 7bd81da..ded2cfd 100644
--- a/atmosphere/models/openstack_helm/values.py
+++ b/atmosphere/models/openstack_helm/values.py
@@ -20,13 +20,13 @@
roles = {"default": blacklist("chart")}
@classmethod
- def for_chart(cls, chart, config):
+ def for_chart(cls, chart):
return cls(
{
"chart": chart,
- "endpoints": endpoints.Endpoints.for_chart(chart, config),
- "images": images.Images.for_chart(chart, config),
- "monitoring": monitoring.Monitoring.for_chart(chart, config),
+ "endpoints": endpoints.Endpoints.for_chart(chart),
+ "images": images.Images.for_chart(chart),
+ "monitoring": monitoring.Monitoring.for_chart(chart),
}
)
@@ -47,11 +47,13 @@
def apply(self, api):
resource = self.secret()
- secret = pykube.Secret(api, resource)
+ secret = pykube.Secret(api, self.secret())
if not secret.exists():
secret.create()
+ secret.reload()
+
if secret.obj["data"] != resource["data"]:
secret.obj["data"] = resource["data"]
secret.update()
diff --git a/atmosphere/operator.py b/atmosphere/operator.py
deleted file mode 100644
index 553d53f..0000000
--- a/atmosphere/operator.py
+++ /dev/null
@@ -1,6 +0,0 @@
-import kopf
-
-
-@kopf.on.event("secret", field="metadata.name", value="atmosphere-config")
-def secret_event_handler(body, **kwargs):
- print(body)
diff --git a/atmosphere/shell.py b/atmosphere/shell.py
deleted file mode 100644
index 35ee45e..0000000
--- a/atmosphere/shell.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import click
-import pykube
-
-from atmosphere import deploy
-from atmosphere.models import conf
-
-
-@click.command()
-@click.option("--config", help="Path to Atmosphere config file", required=True)
-def run(config):
- config = conf.from_file(config)
-
- kube_config = pykube.KubeConfig.from_env()
- api = pykube.HTTPClient(kube_config)
-
- deploy.run(api, config)
-
-
-if __name__ == "__main__":
- run()
diff --git a/atmosphere/tests/integration/test_kind.py b/atmosphere/tests/integration/test_kind.py
index 305af36..1181cc7 100644
--- a/atmosphere/tests/integration/test_kind.py
+++ b/atmosphere/tests/integration/test_kind.py
@@ -1,7 +1,8 @@
+import confspirator
import pykube
from atmosphere import deploy
-from atmosphere.models import conf
+from atmosphere.config import CONF
from atmosphere.models.openstack_helm import values
@@ -12,35 +13,25 @@
def test_deployment(kind_cluster, tmp_path):
kind_cluster.kubectl("create", "namespace", "openstack")
- initial_path = tmp_path / "config-initial.toml"
- initial_path.write_text(
- """
- [memcached]
- secret_key = "secret"
- """
- )
-
- config = conf.from_file(initial_path)
- deploy.run(kind_cluster.api, config)
+ deploy.run(api=kind_cluster.api)
initial_memcache_secret = pykube.Secret(
- kind_cluster.api, values.Values.for_chart("memcached", config).secret()
+ kind_cluster.api, values.Values.for_chart("memcached").secret()
)
assert initial_memcache_secret.exists()
- updated_path = tmp_path / "config-updated.toml"
- updated_path.write_text(
- """
- [memcached]
- secret_key = "not-secret"
- """
- )
-
- config = conf.from_file(updated_path)
- deploy.run(kind_cluster.api, config)
+ with confspirator.modify_conf(
+ CONF,
+ {
+ "atmosphere.memcached.secret_key": [
+ {"operation": "override", "value": "not-secret"}
+ ],
+ },
+ ):
+ deploy.run(api=kind_cluster.api)
updated_memcache_secret = pykube.Secret(
- kind_cluster.api, values.Values.for_chart("memcached", config).secret()
+ kind_cluster.api, values.Values.for_chart("memcached").secret()
)
assert updated_memcache_secret.exists()
diff --git a/atmosphere/tests/unit/models/openstack_helm/test_endpoints.py b/atmosphere/tests/unit/models/openstack_helm/test_endpoints.py
index 488bc7e..17044b5 100644
--- a/atmosphere/tests/unit/models/openstack_helm/test_endpoints.py
+++ b/atmosphere/tests/unit/models/openstack_helm/test_endpoints.py
@@ -1,27 +1,14 @@
-import pytest
-from schematics import exceptions
-
-from atmosphere.models import conf
+from atmosphere.config import CONF
from atmosphere.models.openstack_helm import endpoints as osh_endpoints
def test_endpoint_for_chart_memcached():
- data = conf.AtmosphereConfig.get_mock_object()
- data.memcached.secret_key = "foobar"
- endpoints = osh_endpoints.Endpoints.for_chart("memcached", data)
+ endpoints = osh_endpoints.Endpoints.for_chart("memcached")
assert {
"oslo_cache": {
"auth": {
- "memcache_secret_key": "foobar",
+ "memcache_secret_key": CONF.memcached.secret_key,
}
}
} == endpoints.to_primitive()
-
-
-def test_endpoint_for_chart_memcached_with_no_secret_key():
- data = conf.AtmosphereConfig.get_mock_object()
- data.memcached.secret_key = None
-
- with pytest.raises(exceptions.DataError):
- osh_endpoints.Endpoints.for_chart("memcached", data)
diff --git a/atmosphere/tests/unit/models/openstack_helm/test_images.py b/atmosphere/tests/unit/models/openstack_helm/test_images.py
index 3f315d3..556c6aa 100644
--- a/atmosphere/tests/unit/models/openstack_helm/test_images.py
+++ b/atmosphere/tests/unit/models/openstack_helm/test_images.py
@@ -1,31 +1,31 @@
-from atmosphere.models import conf
+import confspirator
+
+from atmosphere.config import CONF
from atmosphere.models.openstack_helm import images as osh_images
def test_images_for_chart_memcached_with_defaults():
- data = conf.AtmosphereConfig.get_mock_object()
- images = osh_images.Images.for_chart("memcached", data)
-
assert {
"pull_policy": "Always",
"tags": {
- "memcached": data.memcached.images.memcached,
- "prometheus_memcached_exporter": data.memcached.images.prometheus_memcached_exporter,
+ "memcached": CONF.images.memcached,
+ "prometheus_memcached_exporter": CONF.images.memcached_exporter,
},
- }, images.to_primitive()
+ } == osh_images.Images.for_chart("memcached").to_primitive()
+@confspirator.modify_conf(
+ CONF,
+ {
+ "atmosphere.images.memcached": [{"operation": "override", "value": "foo"}],
+ "atmosphere.images.memcached_exporter": [{"operation": "override", "value": "bar"}],
+ },
+)
def test_images_for_chart_memcached_with_overrides():
- data = conf.AtmosphereConfig.get_mock_object()
- data.memcached.images.memcached = "foo"
- data.memcached.images.prometheus_memcached_exporter = "bar"
-
- images = osh_images.Images.for_chart("memcached", data)
-
assert {
"pull_policy": "Always",
"tags": {
"memcached": "foo",
"prometheus_memcached_exporter": "bar",
},
- }, images.to_primitive()
+ } == osh_images.Images.for_chart("memcached").to_primitive()
diff --git a/atmosphere/tests/unit/models/openstack_helm/test_values.py b/atmosphere/tests/unit/models/openstack_helm/test_values.py
index c33615b..8f8f940 100644
--- a/atmosphere/tests/unit/models/openstack_helm/test_values.py
+++ b/atmosphere/tests/unit/models/openstack_helm/test_values.py
@@ -1,24 +1,26 @@
+import confspirator
import pykube
import pytest
-from atmosphere.models import conf
+from atmosphere.config import CONF
from atmosphere.models.openstack_helm import values as osh_values
class TestMemcachedValues:
def test_values_for_chart(self):
- data = conf.AtmosphereConfig.get_mock_object()
- data.memcached.secret_key = "foobar"
-
- values = osh_values.Values.for_chart("memcached", data)
+ values = osh_values.Values.for_chart("memcached")
assert {
- "endpoints": {"oslo_cache": {"auth": {"memcache_secret_key": "foobar"}}},
+ "endpoints": {
+ "oslo_cache": {
+ "auth": {"memcache_secret_key": CONF.memcached.secret_key}
+ }
+ },
"images": {
"pull_policy": "Always",
"tags": {
- "memcached": data.memcached.images.memcached,
- "prometheus_memcached_exporter": data.memcached.images.prometheus_memcached_exporter,
+ "memcached": CONF.images.memcached,
+ "prometheus_memcached_exporter": CONF.images.memcached_exporter,
},
},
"monitoring": {
@@ -29,10 +31,7 @@
} == values.to_primitive()
def test_apply_for_chart_with_no_existing_config(self, mocker):
- data = conf.AtmosphereConfig.get_mock_object()
- data.memcached.secret_key = "foobar"
-
- values = osh_values.Values.for_chart("memcached", data)
+ values = osh_values.Values.for_chart("memcached")
api = mocker.MagicMock()
@@ -48,13 +47,11 @@
mocked_secret_class.assert_called_once_with(api, values.secret())
mocked_secret.exists.assert_called_once()
mocked_secret.create.assert_called_once()
+ mocked_secret.reload.assert_called_once()
mocked_secret.update.assert_not_called()
def test_apply_for_chart_with_no_config_change(self, mocker):
- data = conf.AtmosphereConfig.get_mock_object()
- data.memcached.secret_key = "foobar"
-
- values = osh_values.Values.for_chart("memcached", data)
+ values = osh_values.Values.for_chart("memcached")
api = mocker.MagicMock()
@@ -70,16 +67,21 @@
mocked_secret_class.assert_called_once_with(api, values.secret())
mocked_secret.exists.assert_called_once()
mocked_secret.create.assert_not_called()
+ mocked_secret.reload.assert_called_once()
mocked_secret.update.assert_not_called()
def test_apply_for_chart_with_config_change(self, mocker):
- data = conf.AtmosphereConfig.get_mock_object()
- data.memcached.secret_key = "foobar"
+ old_values = osh_values.Values.for_chart("memcached")
- old_values = osh_values.Values.for_chart("memcached", data)
-
- data.memcached.secret_key = "barfoo"
- new_values = osh_values.Values.for_chart("memcached", data)
+ with confspirator.modify_conf(
+ CONF,
+ {
+ "atmosphere.memcached.secret_key": [
+ {"operation": "override", "value": "barfoo"}
+ ],
+ },
+ ):
+ new_values = osh_values.Values.for_chart("memcached")
api = mocker.MagicMock()
@@ -95,13 +97,11 @@
mocked_secret_class.assert_called_once_with(api, new_values.secret())
mocked_secret.exists.assert_called_once()
mocked_secret.create.assert_not_called()
+ mocked_secret.reload.assert_called_once()
mocked_secret.update.assert_called_once()
def test_apply_for_chart_with_unknown_failure(self, mocker):
- data = conf.AtmosphereConfig.get_mock_object()
- data.memcached.secret_key = "foobar"
-
- values = osh_values.Values.for_chart("memcached", data)
+ values = osh_values.Values.for_chart("memcached")
api = mocker.MagicMock()
mocked_secret = mocker.MagicMock()
diff --git a/atmosphere/tests/unit/models/test_config.py b/atmosphere/tests/unit/models/test_config.py
deleted file mode 100644
index c1aa2cf..0000000
--- a/atmosphere/tests/unit/models/test_config.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import uuid
-
-import pytest
-from schematics import exceptions
-
-from atmosphere.models import conf
-
-MEMCACHE_SECRET_KEY = uuid.uuid4().hex
-
-VALID_CONFIG = f"""
-[memcached]
-secret_key = "{MEMCACHE_SECRET_KEY}"
-"""
-
-
-def test_from_toml_with_valid_configuration():
- try:
- data = conf.from_toml(VALID_CONFIG)
- except exceptions.DataError:
- pytest.fail("Failed to parse valid configuration")
-
- assert data.memcached.secret_key == MEMCACHE_SECRET_KEY
-
-
-def test_from_toml_with_invalid_configuration():
- with pytest.raises(exceptions.DataError):
- conf.from_toml("")
-
-
-def test_from_file_with_valid_configuration(tmp_path):
- path = tmp_path / "config.toml"
- path.write_text(VALID_CONFIG)
-
- try:
- data = conf.from_file(path)
- except exceptions.DataError:
- pytest.fail("Failed to parse valid configuration")
-
- assert data.memcached.secret_key == MEMCACHE_SECRET_KEY
-
-
-def test_from_file_with_invalid_configuration(tmp_path):
- path = tmp_path / "config.toml"
- path.write_text("")
-
- with pytest.raises(exceptions.DataError):
- conf.from_file(path)
-
-
-def test_from_file_with_missing_file(tmp_path):
- path = tmp_path / "config.toml"
-
- with pytest.raises(FileNotFoundError):
- conf.from_file(path)
diff --git a/poetry.lock b/poetry.lock
index 128a29c..26d5b7c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -40,6 +40,23 @@
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
+name = "confspirator"
+version = "0.3.0"
+description = "A config library for handling nested incode config groups."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+netaddr = ">=0.7.18"
+pbr = ">=5.2.0"
+python-slugify = ">=3.0.2"
+PyYAML = ">=5.1"
+rfc3986 = ">=1.2.0"
+six = ">=1.12.0"
+toml = ">=0.10.2"
+
+[[package]]
name = "coverage"
version = "6.4.4"
description = "Code coverage measurement for Python"
@@ -120,6 +137,14 @@
python-versions = ">=3.6"
[[package]]
+name = "netaddr"
+version = "0.8.0"
+description = "A network address manipulation library for Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
@@ -131,6 +156,14 @@
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
+name = "pbr"
+version = "5.10.0"
+description = "Python Build Reasonableness"
+category = "main"
+optional = false
+python-versions = ">=2.6"
+
+[[package]]
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
@@ -255,6 +288,20 @@
dev = ["pre-commit", "pytest-asyncio", "tox"]
[[package]]
+name = "python-slugify"
+version = "6.1.2"
+description = "A Python slugify application that also handles Unicode"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.dependencies]
+text-unidecode = ">=1.3"
+
+[package.extras]
+unidecode = ["Unidecode (>=1.1.1)"]
+
+[[package]]
name = "PyYAML"
version = "6.0"
description = "YAML parser and emitter for Python"
@@ -281,6 +328,17 @@
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
+name = "rfc3986"
+version = "2.0.0"
+description = "Validating URI References per RFC 3986"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+idna2008 = ["idna"]
+
+[[package]]
name = "schematics"
version = "2.1.1"
description = "Python Data Structures for Humans"
@@ -289,6 +347,30 @@
python-versions = "*"
[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "text-unidecode"
+version = "1.3"
+description = "The most basic Text::Unidecode port"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "main"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
@@ -312,7 +394,7 @@
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
-content-hash = "878e26cc2b4158be9e89c5f00f83895dc93e9fd180fb44c739afdaa87d0a3430"
+content-hash = "164328b30d2cabb80939277c14bed8bd9db881b6e2f80f95f03d4230a48f5cf1"
[metadata.files]
attrs = [
@@ -331,6 +413,9 @@
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
+confspirator = [
+ {file = "confspirator-0.3.0.tar.gz", hash = "sha256:065c22e8c317c623668fd71c6c40038829b934ec320785c7d21e05b6a5b2c711"},
+]
coverage = [
{file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"},
{file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"},
@@ -407,10 +492,18 @@
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
+netaddr = [
+ {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"},
+ {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"},
+]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
+pbr = [
+ {file = "pbr-5.10.0-py2.py3-none-any.whl", hash = "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"},
+ {file = "pbr-5.10.0.tar.gz", hash = "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a"},
+]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
@@ -451,6 +544,10 @@
{file = "pytest-mock-3.8.2.tar.gz", hash = "sha256:77f03f4554392558700295e05aed0b1096a20d4a60a4f3ddcde58b0c31c8fca2"},
{file = "pytest_mock-3.8.2-py3-none-any.whl", hash = "sha256:8a9e226d6c0ef09fcf20c94eb3405c388af438a90f3e39687f84166da82d5948"},
]
+python-slugify = [
+ {file = "python-slugify-6.1.2.tar.gz", hash = "sha256:272d106cb31ab99b3496ba085e3fea0e9e76dcde967b5e9992500d1f785ce4e1"},
+ {file = "python_slugify-6.1.2-py2.py3-none-any.whl", hash = "sha256:7b2c274c308b62f4269a9ba701aa69a797e9bca41aeee5b3a9e79e36b6656927"},
+]
PyYAML = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
@@ -497,10 +594,26 @@
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
]
+rfc3986 = [
+ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"},
+ {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"},
+]
schematics = [
{file = "schematics-2.1.1-py2.py3-none-any.whl", hash = "sha256:be2d451bfb86789975e5ec0864aec569b63cea9010f0d24cbbd992a4e564c647"},
{file = "schematics-2.1.1.tar.gz", hash = "sha256:34c87f51a25063bb498ae1cc201891b134cfcb329baf9e9f4f3ae869b767560f"},
]
+six = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+text-unidecode = [
+ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
+ {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
+]
+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"},
diff --git a/pyproject.toml b/pyproject.toml
index 1c7be9a..d2055ba 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,10 +7,14 @@
authors = ["Mohammed Naser <mnaser@vexxhost.com>"]
readme = "README.md"
+[tool.poetry.scripts]
+atmosphere-operator = "atmosphere.cmd.operator:main"
+
[tool.poetry.dependencies]
python = "^3.10"
schematics = "^2.1.1"
pykube-ng = "^22.7.0"
+confspirator = "^0.3.0"
[tool.poetry.group.dev.dependencies]
pytest = "^7.1.3"