-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
242fb97
commit 08f9738
Showing
12 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
## Abdicate | ||
|
||
Abdicate is an opinionated technology-agnostic specification to model applications and their resource dependency relationships. | ||
It makes no assumptions about the technical implementation of the deployment or target system, | ||
allowing the same model throughout the entire life of the applications and without having to commit to a technology/tooling stack. | ||
|
||
Knowledge encapsulation | ||
|
||
Application model | ||
|
||
Convention | ||
|
||
|
||
## Help | ||
|
||
See [examples](tree/master/examples) for more details. | ||
|
||
## Installation | ||
|
||
Install using `pip install -U abdicate-spec`. | ||
|
||
Or `pip install git+https://github.com/abdicate-io/abdicate-spec.git` | ||
|
||
|
||
## A Simple Example | ||
|
||
```yaml | ||
version: "1.0" | ||
|
||
friendlyName: petstore-ws | ||
domains: | ||
- ecommerce | ||
components: | ||
- pets | ||
requires: | ||
databases: | ||
orm: | ||
alias: db | ||
interface: mysql:5 | ||
provides: | ||
rest: | ||
interface: http | ||
x-url: /v1/ | ||
x-swagger-url: /swagger-ui.html | ||
``` | ||
## Inspiration | ||
Docker compose, Helm charts, Terraform, Juju, ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# yaml-language-server: $schema=abdicate-1.0.schema.json | ||
version: "1.0" | ||
|
||
friendlyName: my-website | ||
requires: | ||
databases: | ||
orm: | ||
alias: db | ||
interface: postgres:10 | ||
celery_broker: | ||
alias: redis | ||
interface: redis:5 | ||
services: | ||
com.org.backend:celery_worker: | ||
alias: celery | ||
provides: | ||
website: | ||
interface: http | ||
x-port: 8000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# yaml-language-server: $abdicate-1.0.schema.json | ||
version: "1.0" | ||
|
||
baseImage: gcr.io/distroless/java | ||
friendlyName: petstore-ws | ||
domains: | ||
- ecommerce | ||
components: | ||
- pets | ||
requires: | ||
databases: | ||
orm: | ||
alias: db | ||
interface: mysql:5 | ||
provides: | ||
rest: | ||
interface: http | ||
x-url: /v1/ | ||
x-swagger-url: /swagger-ui.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[build-system] | ||
requires = [ | ||
"setuptools>=42", | ||
"wheel" | ||
] | ||
build-backend = "setuptools.build_meta" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import setuptools | ||
|
||
with open("README.md", "r", encoding="utf-8") as fh: | ||
long_description = fh.read() | ||
|
||
setuptools.setup( | ||
name="abdicate-spec", | ||
version="0.0.1", | ||
author="Jelle Hellemans", | ||
author_email="author@example.com", | ||
description="Abdicate is a specification to declare an application and its resource dependency relationships.", | ||
long_description=long_description, | ||
long_description_content_type="text/markdown", | ||
url="https://github.com/abdicate-io", | ||
project_urls={ | ||
"Bug Tracker": "https://github.com/abdicate-io/abdicate-spec/issues", | ||
}, | ||
classifiers=[ | ||
"Programming Language :: Python :: 3", | ||
"License :: OSI Approved :: MIT License", | ||
"Operating System :: OS Independent", | ||
], | ||
package_dir={"": "src"}, | ||
packages=setuptools.find_packages(where="src"), | ||
python_requires=">=3.7", | ||
test_suite="tests", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from pydantic import parse_obj_as | ||
|
||
from abdicate import model_1_0 | ||
|
||
_VERSIONS = { | ||
'1.0': model_1_0.Application, | ||
} | ||
|
||
def parse_object(object): | ||
version = object.get('version') | ||
if version not in _VERSIONS: | ||
raise ValueError('Version "{}" not supported, choice from: {}'.format(version, ', '.join(_VERSIONS.keys()))) | ||
return parse_obj_as(_VERSIONS.get(version), object) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import argparse | ||
|
||
from abdicate import _VERSIONS | ||
|
||
if __name__ == '__main__': | ||
parser = argparse.ArgumentParser(description='Produce a json schema.') | ||
parser.add_argument('--version', help='version of the schema.', required=True, choices=_VERSIONS.keys()) | ||
|
||
args = parser.parse_args() | ||
print(_VERSIONS.get(args.version).schema_json(indent=2)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import re | ||
from enum import Enum | ||
from typing import Dict, Optional, List | ||
from pydantic import BaseModel, constr, Extra, root_validator, Field | ||
|
||
DNSNAME_REGEX = r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{,63}(?<!-)$' | ||
ARTIFACT_REGEX = r'^([a-z\-_\.]+)(:([a-z\-_]+))?$' | ||
IMAGE_TAG_REGEX = r'^(?:(?=[^:\/]{4,253})(?!-)[a-zA-Z0-9-]{1,63}(?<!-)(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-))*(?::[0-9]{1,5})?/)?((?![._-])(?:[a-z0-9._-]*)(?<![._-])(?:/(?![._-])[a-z0-9._-]*(?<![._-]))*)((?::(?![.-])[a-zA-Z0-9_.-]{1,128})|@sha256:[a-z0-9]+)?$' | ||
|
||
|
||
class MountPermissionEnum(str, Enum): | ||
read = 'read' | ||
write = 'write' | ||
read_write = 'read_write' | ||
|
||
|
||
class DSPermissionEnum(str, Enum): | ||
read = 'read' | ||
read_write = 'read_write' | ||
|
||
|
||
class ExBaseModel(BaseModel): | ||
class Config: | ||
extra = Extra.allow | ||
schema_extra = { | ||
'patternProperties': {'^x-': {}}, | ||
} | ||
@root_validator | ||
def check_extra_fields(cls, values): | ||
"""Make sure extra fields are only valid fields matching regex""" | ||
pattern_regex = r'^x-' | ||
|
||
for k, v in list(values.items()): | ||
if k in cls.__fields__: | ||
continue | ||
|
||
assert re.match(pattern_regex, k), 'extra field "{}" not allowed (custom properties can be set with prefix x-)'.format(k) | ||
values[re.sub(pattern_regex, 'x_', k)] = values.pop(k) | ||
|
||
return values | ||
|
||
|
||
class Resource(ExBaseModel): | ||
alias: Optional[str] = None | ||
|
||
|
||
class Functional(ExBaseModel): | ||
domains: Optional[List[str]] = Field(description='List of functional domains this application belongs to.') | ||
components: Optional[List[str]] = Field(description='List of functional components this application belongs to.') | ||
|
||
|
||
class Database(Resource): | ||
""" | ||
A Database that will be utilized by the application | ||
These are databases for which the schema is managed by the application. | ||
""" | ||
interface: Optional[str] = None | ||
|
||
|
||
class DataStore(Resource): | ||
""" | ||
A dataStore that will be utilized by the application | ||
These are databases for which the application isn't the owner of the schemas. | ||
""" | ||
permission: DSPermissionEnum = DSPermissionEnum.read | ||
|
||
|
||
class Service(Resource): | ||
""" | ||
A service that will be utilized by the application | ||
""" | ||
pass | ||
|
||
|
||
class Queue(Resource): | ||
""" | ||
A queue that will be utilized by the application | ||
""" | ||
pass | ||
|
||
|
||
class Queues(Resource): | ||
receive: Optional[Dict[constr(regex=ARTIFACT_REGEX), Queue]] = Field(description='List queues with incoming messages') | ||
send: Optional[Dict[constr(regex=ARTIFACT_REGEX), Queue]] = Field(description='List queues with outgoing messages') | ||
|
||
|
||
class Property(Resource): | ||
""" | ||
A property that will be utilized by the application | ||
""" | ||
pass | ||
|
||
|
||
class Mount(Resource): | ||
""" | ||
A file or directory that will be utilized by the application | ||
""" | ||
permission: MountPermissionEnum = MountPermissionEnum.read | ||
|
||
|
||
class Requires(ExBaseModel): | ||
databases: Optional[Dict[constr(regex=ARTIFACT_REGEX), Database]] | ||
datastores: Optional[Dict[constr(regex=ARTIFACT_REGEX), DataStore]] | ||
services: Optional[Dict[constr(regex=ARTIFACT_REGEX), Service]] | ||
queues: Optional[Queues] | ||
properties: Optional[Dict[constr(regex=ARTIFACT_REGEX), Property]] | ||
mounts: Optional[Dict[constr(regex=ARTIFACT_REGEX), Mount]] | ||
|
||
|
||
class Provided(ExBaseModel): | ||
""" | ||
A provided interface that will be exposed by the application | ||
""" | ||
interface: Optional[str] = None | ||
|
||
|
||
class Application(ExBaseModel): | ||
""" | ||
Dependencies and description of the application. | ||
""" | ||
version: str | ||
|
||
baseImage: Optional[constr(regex=IMAGE_TAG_REGEX)] | ||
friendlyName: Optional[constr(regex=DNSNAME_REGEX)] | ||
functional: Optional[Functional] | ||
|
||
requires: Optional[Requires] | ||
provides: Optional[Dict[constr(regex=ARTIFACT_REGEX), Provided]] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import unittest | ||
|
||
import re | ||
|
||
from pydantic import parse_obj_as | ||
from pydantic.error_wrappers import ValidationError | ||
from abdicate.model_1_0 import ARTIFACT_REGEX, DNSNAME_REGEX, IMAGE_TAG_REGEX | ||
|
||
class ConstraintTests(unittest.TestCase): | ||
def test_valid_artifact_ids(self): | ||
for i, string in enumerate(['com.org.package:artifact', 'artifact', 'test:test', 'test_test', 'test-test']): | ||
with self.subTest(i=i, msg=string): | ||
self.assertTrue(re.match(ARTIFACT_REGEX, string), '{} does not match {}'.format(string, ARTIFACT_REGEX)) | ||
|
||
|
||
def test_invalid_artifact_ids(self): | ||
for i, string in enumerate(['com.org.package/artifact', 'Artifact', 'test@test']): | ||
with self.subTest(i=i, msg=string): | ||
self.assertFalse(re.match(ARTIFACT_REGEX, string), '{} does match {}'.format(string, ARTIFACT_REGEX)) | ||
|
||
|
||
def test_dnsnames(self): | ||
tests = [ | ||
('01010', False), | ||
('abc', True), | ||
('A0c', True), | ||
('A0c-', False), | ||
('-A0c', False), | ||
('A-0c', True), | ||
('o123456701234567012345670123456701234567012345670123456701234567', False), | ||
('o12345670123456701234567012345670123456701234567012345670123456', True), | ||
('', True), | ||
('a', True), | ||
('0--0', True), | ||
] | ||
|
||
for i, (string, expected) in enumerate(tests): | ||
with self.subTest(i=i, msg=string): | ||
self.assertEquals(re.match(DNSNAME_REGEX, string) is not None, expected, '{} {} match {}'.format(string, ("doesn't" if expected else "does"), DNSNAME_REGEX)) | ||
|
||
|
||
def test_imagetags(self): | ||
tests = [ | ||
('FOO', False), | ||
('myregistryhost:5000/fedora/httpd:version1.0', True), | ||
('fedora/httpd:version1.0.test', True), | ||
('fedora/httpd:version1.0', True), | ||
('rabbit:3', True), | ||
('rabbit', True), | ||
('registry/rabbit:3', True), | ||
('registry/rabbit', True), | ||
('rabbit@sha256:3235326357dfb65f1781dbc4df3b834546d8bf914e82cce58e6e6b6', True), | ||
('registry/rabbit@sha256:3235326357dfb65f1781dbc4df3b834546d8bf914e82cce58e6e6b6', True), | ||
('rabbit@sha256:3235326357dfb65f1781dbc4df3b834546d8bf914e82cce58e.6e6b6', False), | ||
] | ||
|
||
for i, (string, expected) in enumerate(tests): | ||
with self.subTest(i=i, msg=string): | ||
self.assertEquals(re.match(IMAGE_TAG_REGEX, string) is not None, expected, '{} {} match {}'.format(string, ("doesn't" if expected else "does"), IMAGE_TAG_REGEX)) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import unittest | ||
|
||
from pydantic import parse_obj_as | ||
from pydantic.error_wrappers import ValidationError | ||
from abdicate.model_1_0 import Application | ||
|
||
class ModelTests(unittest.TestCase): | ||
def test_application_allows_extenions(self): | ||
instance = {'version': '1.0', 'x-test': 'something'} | ||
item = parse_obj_as(Application, instance) | ||
self.assertEqual(item.x_test, 'something') | ||
|
||
def test_application_does_not_allows_unknown_properties(self): | ||
instance = {'version': '1.0', 'unknown': True} | ||
with self.assertRaises(ValidationError): | ||
item = parse_obj_as(Application, instance) | ||
|
||
if __name__ == '__main__': | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import unittest | ||
|
||
|
||
from pydantic.error_wrappers import ValidationError | ||
from abdicate import parse_object | ||
|
||
class ParseTests(unittest.TestCase): | ||
def test_parse_databases(self): | ||
instance = {'version': '1.0', 'requires': {'databases': {'orm': {'alias': 'db'}}}} | ||
item = parse_object(instance) | ||
self.assertEqual(item.requires.databases.get('orm').alias, 'db') | ||
|
||
def test_parse_queues(self): | ||
instance = {'version': '1.0', 'requires': {'queues': {'send': {'io.abdicate.queues:functional_queue_name': {'alias': 'receiveQueue'}}}}} | ||
item = parse_object(instance) | ||
self.assertEqual(item.requires.queues.send.get('io.abdicate.queues:functional_queue_name').alias, 'receiveQueue') | ||
|
||
if __name__ == '__main__': | ||
unittest.main() |