diff --git a/Makefile b/Makefile index c2e8957..8cd2f4c 100644 --- a/Makefile +++ b/Makefile @@ -4,4 +4,4 @@ push-all: .PHONY: build-vlei build-vlei: - @docker buildx build --platform=linux/amd64 --no-cache -f container/Dockerfile --tag gleif/vlei:latest --tag gleif/vlei:0.1.0 . + @docker buildx build --load --platform=linux/amd64 -f container/Dockerfile --tag gleif/vlei:latest --tag gleif/vlei:0.2.1 . diff --git a/container/Dockerfile b/container/Dockerfile index 778859f..f7e549a 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -1,15 +1,53 @@ -FROM python:3.10.4-buster +FROM python:3.12.6-slim AS builder -RUN apt-get update -RUN apt-get install -y ca-certificates +# Install compilation dependencies for Ubuntu including ca-certificates, libffi, and libsodium +RUN apt-get update && apt-get install -y \ + build-essential \ + libffi-dev \ + libsodium-dev \ + libssl-dev \ + python3-dev \ + python3-venv \ + python3-pip \ + curl + +SHELL ["/bin/bash", "-c"] + +# Setup Rust for blake3 dependency build +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y + +WORKDIR /vLEI + +RUN python -m venv venv +ENV PATH=/vLEI/venv/bin:${PATH} +RUN pip install --upgrade pip + +COPY requirements.txt setup.py /vLEI/ +RUN mkdir /vLEI/src +# Build vLEI-server +RUN . "$HOME/.cargo/env" && \ + pip install -r requirements.txt + +# Create smaller runner image by copying over built binary +FROM python:3.12.6-slim AS runner + +RUN apt-get update RUN apt-get install -y libsodium23 +# Prevents Python from buffering stdout and stderr, logs to stdout immediately ENV PYTHONUNBUFFERED=1 +# Set default encoding to UTF-8 ENV PYTHONIOENCODING=UTF-8 -WORKDIR /usr/local/var/ -RUN git clone https://github.com/WebOfTrust/vLEI +WORKDIR /vLEI + +COPY --from=builder /vLEI /vLEI +COPY src/ src/ +COPY ./schema/acdc /vLEI/schema +COPY ./samples/acdc /vLEI/credentials +COPY ./samples/oobis /vLEI/oobis + +ENV PATH=/vLEI/venv/bin:${PATH} -WORKDIR /usr/local/var/vLEI -RUN pip install -r requirements.txt +CMD ["vLEI-server", "-s", "/vLEI/schema", "-c", "/vLEI/credentials", "-o", "/vLEI/oobis"] diff --git a/setup.py b/setup.py index 11f3bcf..a13e32f 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ from setuptools import find_packages, setup setup( name='vlei', - version='0.0.1', # also change in src/vlei/__init__.py + version='0.2.1', # also change in src/vlei/__init__.py license='Apache Software License 2.0', description='Verifiable Legal Entity Identifier', long_description="Verifiable Legal Entity Identifier Schema Generator and Server", @@ -51,7 +51,7 @@ 'Operating System :: Unix', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Utilities', ], @@ -61,34 +61,34 @@ 'Issue Tracker': 'https://github.com/WebOfTrust/keripy/issues', }, keywords=[ - # eg: 'keyword1', 'keyword2', 'keyword3', + 'keri','acdc','vlei' ], - python_requires='>=3.10.4', + python_requires='>=3.12.2', install_requires=[ - 'lmdb>=1.2.1', - 'pysodium>=0.7.9', - 'blake3>=0.2.0', - 'msgpack>=1.0.2', - 'cbor2>=5.4.1', - 'multidict>=5.1.0', - 'ordered-set>=4.1.0', - 'hio>=0.5.8', - 'multicommand>=0.1.1', - 'jsonschema>=3.2.0', - 'falcon>=3.0.1', - 'daemonocle>=1.2.3', - 'hjson>=3.0.2', - 'PyYaml>=6.0', - 'apispec>=5.1.1', - 'mnemonic>=0.20', - 'keri>=1.0.0', + 'lmdb>=1.4.1', + 'pysodium>=0.7.17', + 'blake3>=0.4.1', + 'msgpack>=1.0.8', + 'cbor2>=5.6.2', + 'multidict>=6.0.5', + 'ordered-set>=4.1.0', + 'hio==0.6.14', + 'multicommand>=1.0.0', + 'jsonschema>=4.21.1', + 'falcon>=3.1.3', + 'daemonocle>=1.2.3', + 'hjson>=3.1.0', + 'PyYaml>=6.0.2', + 'apispec>=6.8.0', + 'mnemonic>=0.21', + 'keri>=1.2.1', ], extras_require={ }, tests_require=[ - 'coverage>=5.5', - 'pytest>=6.2.5', - ], + 'coverage>=7.4.4', + 'pytest>=8.1.1', + ], setup_requires=[ ], entry_points={ diff --git a/src/vlei/__init__.py b/src/vlei/__init__.py index 4fee852..f4ee8d0 100644 --- a/src/vlei/__init__.py +++ b/src/vlei/__init__.py @@ -1,4 +1,4 @@ # -*- encoding: utf-8 -*- -__version__ = '0.1.1' # also change in setup.py +__version__ = '0.2.1' # also change in setup.py diff --git a/src/vlei/app/caching.py b/src/vlei/app/caching.py index c450937..4ee843c 100644 --- a/src/vlei/app/caching.py +++ b/src/vlei/app/caching.py @@ -7,7 +7,9 @@ import os from keri.core import scheming +from keri import help +logger = help.ogler.getLogger() def cacheSchema(path, d): for root, dirs, files in os.walk(path): @@ -16,7 +18,7 @@ def cacheSchema(path, d): with open(os.path.join(root, file), 'r') as f: ked = json.load(f) schemer = scheming.Schemer(sed=ked) - print(f"caching schema {schemer.said}") + logger.info(f"caching schema {schemer.said}") d[schemer.said] = schemer.raw return d @@ -28,7 +30,7 @@ def cacheCredential(path, d): if file.endswith('-acdc.cesr'): with open(os.path.join(root, file), 'r') as f: said = file.removesuffix('-acdc.cesr') - print(f"caching credential {said}") + logger.info(f"caching credential {said}") d[said] = f.read() return d diff --git a/src/vlei/app/generating.py b/src/vlei/app/generating.py index 25668da..3d02465 100644 --- a/src/vlei/app/generating.py +++ b/src/vlei/app/generating.py @@ -1,7 +1,7 @@ from keri.core import coring -def populateSAIDS(d: dict, idage: str = coring.Ids.dollar, code: str = coring.MtrDex.Blake3_256): +def populateSAIDS(d: dict, idage: str = coring.Saids.dollar, code: str = coring.MtrDex.Blake3_256): if 'properties' in d: props = d['properties'] diff --git a/src/vlei/app/serving.py b/src/vlei/app/serving.py index a625262..1377ce2 100644 --- a/src/vlei/app/serving.py +++ b/src/vlei/app/serving.py @@ -11,12 +11,17 @@ class SchemaEnd: + """Returns ACDC credential schemas on HTTP GET""" def __init__(self, schemaDir, credDir): self.schemaCache = caching.cacheSchema(schemaDir, dict()) self.credentialCache = caching.cacheCredential(credDir, dict()) def on_get(self, _, rep, said): + """ + Returns either ACDC JSON Schema or ACDC Credential if the key (SAID) is in the cache. + The cache is loaded on startup with the -s (schema) and -c (credentials) arguments. + """ if said in self.schemaCache: data = self.schemaCache[said] diff --git a/src/vlei/generate.py b/src/vlei/generate.py index c62ac20..b51374d 100644 --- a/src/vlei/generate.py +++ b/src/vlei/generate.py @@ -7,7 +7,8 @@ import os from pathlib import Path -from keri.core import scheming, coring +from keri.core import coring, scheming + from vlei.app import generating @@ -23,43 +24,43 @@ def main(): # legal entity -> qvi edge p = f'{path}/../../schema/acdc/legal-entity-vLEI-credential.json' le = __load(p) - le['properties']['e']['oneOf'][1]['properties']['qvi']["properties"]['s']['const'] = qvi[coring.Ids.dollar] + le['properties']['e']['oneOf'][1]['properties']['qvi']["properties"]['s']['const'] = qvi[coring.Saids.dollar] le = generating.populateSAIDS(le) __save(p, le) # oor auth -> le edge p = f'{path}/../../schema/acdc/oor-authorization-vlei-credential.json' oorAuth = __load(p) - oorAuth['properties']['e']['oneOf'][1]['properties']['le']["properties"]['s']['const'] = le[coring.Ids.dollar] + oorAuth['properties']['e']['oneOf'][1]['properties']['le']["properties"]['s']['const'] = le[coring.Saids.dollar] oorAuth = generating.populateSAIDS(oorAuth) __save(p, oorAuth) # oor -> oor auth edge p = f'{path}/../../schema/acdc/legal-entity-official-organizational-role-vLEI-credential.json' oor = __load(p) - oor['properties']['e']['oneOf'][1]['properties']['auth']["properties"]['s']['const'] = oorAuth[coring.Ids.dollar] + oor['properties']['e']['oneOf'][1]['properties']['auth']["properties"]['s']['const'] = oorAuth[coring.Saids.dollar] oor = generating.populateSAIDS(oor) __save(p, oor) # ecr auth -> le edge p = f'{path}/../../schema/acdc/ecr-authorization-vlei-credential.json' ecrAuth = __load(p) - ecrAuth['properties']['e']['oneOf'][1]['properties']['le']["properties"]['s']['const'] = le[coring.Ids.dollar] + ecrAuth['properties']['e']['oneOf'][1]['properties']['le']["properties"]['s']['const'] = le[coring.Saids.dollar] ecrAuth = generating.populateSAIDS(ecrAuth) __save(p, ecrAuth) # ecr -> ecr auth edge and le edge p = f'{path}/../../schema/acdc/legal-entity-engagement-context-role-vLEI-credential.json' ecr = __load(p) - ecr['properties']['e']['oneOf'][1]['properties']['auth']["properties"]['s']['const'] = ecrAuth[coring.Ids.dollar] - ecr['properties']['e']['oneOf'][2]['properties']['le']["properties"]['s']['const'] = le[coring.Ids.dollar] + ecr['properties']['e']['oneOf'][1]['properties']['auth']["properties"]['s']['const'] = ecrAuth[coring.Saids.dollar] + ecr['properties']['e']['oneOf'][2]['properties']['le']["properties"]['s']['const'] = le[coring.Saids.dollar] ecr = generating.populateSAIDS(ecr) __save(p, ecr) p = f'{path}/../../schema/acdc/verifiable-ixbrl-report-attestation.json' vira = __load(p) - vira['properties']['e']['oneOf'][0]['properties']['oor']["properties"]['s']['const'] = oor[coring.Ids.dollar] - vira['properties']['e']['oneOf'][1]['properties']['ecr']["properties"]['s']['const'] = ecr[coring.Ids.dollar] + vira['properties']['e']['oneOf'][0]['properties']['oor']["properties"]['s']['const'] = oor[coring.Saids.dollar] + vira['properties']['e']['oneOf'][1]['properties']['ecr']["properties"]['s']['const'] = ecr[coring.Saids.dollar] vira = generating.populateSAIDS(vira) __save(p, vira) diff --git a/src/vlei/server.py b/src/vlei/server.py index 33f84b2..0aafc7b 100644 --- a/src/vlei/server.py +++ b/src/vlei/server.py @@ -4,11 +4,15 @@ """ import argparse +import logging +import signal import falcon from hio.base import doing from hio.core import http, tcp +from keri import help + from vlei.app import serving parser = argparse.ArgumentParser(description="Runs vLEI schema server") @@ -35,6 +39,8 @@ parser.add_argument("--cafilepath", action="store", required=False, default=None, help="TLS server CA certificate chain") +logger = help.ogler.getLogger() + def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None): """ @@ -62,15 +68,16 @@ def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None): def launch(args): + logger.setLevel(logging.INFO) app = falcon.App() port = int(args.http) keypath = args.keypath certpath = args.certpath cafilepath = args.cafilepath if keypath is not None and certpath is not None and cafilepath is not None: - print(f"Starting on port {port} with TLS enabled") + logger.info(f"vLEI-server starting on port {port} with TLS enabled") else: - print(f"Starting on port {port} with TLS disabled") + logger.info(f"vLEI-server starting on port {port} with TLS disabled") server = createHttpServer(port=int(args.http), app=app, keypath=args.keypath, certpath=args.certpath, cafilepath=args.cafilepath) @@ -82,10 +89,17 @@ def launch(args): doers = [httpServerDoer] + # Shutdown hook + def shutdownHandler(sig, frame): + logger.info("Received signal %s", signal.strsignal(sig)) + doist.exit() + signal.signal(signal.SIGTERM, shutdownHandler) + tock = 0.03125 doist = doing.Doist(limit=0.0, tock=tock, real=True) - doist.do(doers=doers) + doist.do(doers=doers) # Enters the doist loop until shutdown + logger.info("vLEI-server stopped") def main(): args = parser.parse_args() diff --git a/tests/test_schema.py b/tests/test_schema.py index 9f2f53d..e4f727d 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -2,7 +2,7 @@ from pathlib import Path import pytest -from keri.core import scheming, coring +from keri.core import coring, scheming from keri.kering import ValidationError @@ -59,21 +59,21 @@ def test_legal_entity_chain(): qvi = json.load(open(f'{__path()}/../schema/acdc/qualified-vLEI-issuer-vLEI-credential.json', 'r')) le = json.load(open(f'{__path()}/../schema/acdc/legal-entity-vLEI-credential.json', 'r')) - assert le['properties']['e']['oneOf'][1]['properties']['qvi']["properties"]['s']['const'] == qvi[coring.Ids.dollar] + assert le['properties']['e']['oneOf'][1]['properties']['qvi']["properties"]['s']['const'] == qvi[coring.Saids.dollar] def test_ecr_auth_chain(): auth = json.load(open(f'{__path()}/../schema/acdc/ecr-authorization-vlei-credential.json', 'r')) assert auth['properties']['e']['oneOf'][1]['properties']['le']["properties"]['s']['const'] == __le()[ - coring.Ids.dollar] + coring.Saids.dollar] def test_oor_auth_chain(): auth = json.load(open(f'{__path()}/../schema/acdc/oor-authorization-vlei-credential.json', 'r')) assert auth['properties']['e']['oneOf'][1]['properties']['le']["properties"]['s']['const'] == __le()[ - coring.Ids.dollar] + coring.Saids.dollar] def test_oor_chain(): @@ -83,7 +83,7 @@ def test_oor_chain(): auth = json.load(open(f'{Path(__file__).parent}/../schema/acdc/oor-authorization-vlei-credential.json', 'r')) assert oor['properties']['e']['oneOf'][1]['properties']['auth']["properties"]['s']['const'] == auth[ - coring.Ids.dollar] + coring.Saids.dollar] def test_ecr_chain(): @@ -91,10 +91,8 @@ def test_ecr_chain(): ecr = json.load( open(f'{__path()}/../schema/acdc/legal-entity-engagement-context-role-vLEI-credential.json', 'r')) - assert ecr['properties']['e']['oneOf'][1]['properties']['auth']["properties"]['s']['const'] == auth[ - coring.Ids.dollar] - assert ecr['properties']['e']['oneOf'][2]['properties']['le']["properties"]['s']['const'] == __le()[ - coring.Ids.dollar] + assert ecr['properties']['e']['oneOf'][1]['properties']['auth']["properties"]['s']['const'] == auth[coring.Saids.dollar] + assert ecr['properties']['e']['oneOf'][2]['properties']['le']["properties"]['s']['const'] == __le()[coring.Saids.dollar] def __le():