diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9077b4b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.vscode +.editorignore +.flake* +.git* +.python* +Dockerfile +Makefile +etc \ No newline at end of file diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml new file mode 100644 index 0000000..67cde92 --- /dev/null +++ b/.github/workflows/image.yml @@ -0,0 +1,42 @@ +# +name: Create and publish a Docker image + +on: + push: + branches: ["docker"] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index fb32aa6..ac27171 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ tests/api/ .theia/ plsc.yaml .python-version -Pipfile \ No newline at end of file +Pipfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9a31bde --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +ARG PYTHON_VERSION=3.11-alpine + +FROM python:${PYTHON_VERSION} + +RUN apk add py3-virtualenv py3-pytest python3-dev gcc musl-dev openldap-dev bash + +ENV VENV /.venv +RUN virtualenv ${VENV} +ENV PATH ${VENV}/bin:$PATH + +RUN adduser -D user + +WORKDIR /app + +ADD . . +RUN chown -R user:user ${VENV} . + +USER user + +RUN pip install --upgrade pip +RUN pip install -r requirements.txt + +CMD ["./run.sh"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a19417f --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +# Makefile + +all: pytest + +image: + docker build -t plsc . + +ldap_start: + etc/ldap_start.sh + +ldap_show: + etc/ldap_show.sh + +ldap_stop: + etc/ldap_stop.sh + +pytest: image ldap_start + docker run --rm -ti --network host plsc pytest + +clean: ldap_stop diff --git a/README.md b/README.md index ab09498..7c9b21d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ For local development we advice to make use of Python Virtual Environment. The i This is an example of what we need to specify source and destination. [ More complex examples will follow ] -``` +```bash --- ldap: src: @@ -34,7 +34,8 @@ src: ### Local development Create a virtual environment and install the required python packages: -``` + +```bash python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip @@ -42,10 +43,12 @@ pip install -r requirements.txt ``` ### Use -```sync_services``` will look for all ```labeledURI``` attributes in the source and create ```dc=``` branches on the destination containing all CO's that are linked to these services with bare ```People```, ```Groups``` and ```uid/gidNumberSequence``` structures. -```plsc``` will then mirror all ```People``` and ```Groups``` to the corresponding CO's in the destination LDAP, optionally converting attributes and dn's as defined in the code on the way: -``` +`sync_services` will look for all `labeledURI` attributes in the source and create `dc=` branches on the destination containing all CO's that are linked to these services with bare `People`, `Groups` and `uid/gidNumberSequence` structures. + +`plsc` will then mirror all `People` and `Groups` to the corresponding CO's in the destination LDAP, optionally converting attributes and dn's as defined in the code on the way: + +```bash # Here's the magic: Build the new person entry dst_entry = {} dst_entry['objectClass'] = ['inetOrgPerson', 'person', 'posixAccount'] @@ -56,7 +59,8 @@ pip install -r requirements.txt ``` And for groups: -``` + +```bash # Here's the magic: Build the new group entry m = re.search('^(?:GRP)?(?:CO)?(?:COU)?:(.*?)$', src_cn) dst_cn = src_type + "_" + m.group(1) if m.group(1) else "" @@ -72,7 +76,7 @@ And for groups: For local testing, you need a local LDAP to be started before running tests. When you have docker installed on your local machine, you can simple run: -``` +```bash etc/ldap_start.sh ``` @@ -82,7 +86,7 @@ If you do not have docker installed or wish to use an existing running LDAP serv You can specify LDAP connection and access constants in a local **.env** file, for exanple: -``` +```bash LDAP_URL="ldap://localhost:389" LDAP_ADMIN_PASSWORD="secret" LDAP_CONFIG_PASSWORD="config" @@ -106,20 +110,50 @@ When you omit the **SBS_URL** variable, the tests will run API requests agains t When all these preperations are completed, you can now run the tests: -``` +```bash pytest -v ``` After each Test cycle the contents of the LDAP can be inspected, for that run this command: -``` +```bash etc/ldap_show.sh ``` When finished you can tear down the local running LDAP instance, by: -``` +```bash etc/ldap_stop.sh ``` +# Using Make + +A Makefile is added to make life easier. Now you can run: + +```bash +make pytest +``` + +Or just make. This will build the docker image and run pytest next. + +```bash +make +``` + +# Docker + +A Dockerfile is added to produce an image that is capable of running PLSC. Since PLSC depends on an input YAML file holding the source and destionatination details, you have to provide sucan YANL file. +Using Docker you can do the following: + +Suppose you have prepared you YAML file (inspired by plsc.yml.example) and this file is 'my-plsc.yml' + +You have to mount this file into the container and then run **run.sh** with that file as parameter. + +Example: + +```bash +docker run -v ${pwd}/my-plsc.yml:/tmp/plsc.yaml plsc ./run.sh /tmp/plsc.yml +``` + +**run.sh** is the existing script that runs both _plsc_ordered.py_ and _plsc_flat_ diff --git a/tests/base_test.py b/tests/base_test.py index 6465bf6..2533c63 100644 --- a/tests/base_test.py +++ b/tests/base_test.py @@ -2,8 +2,6 @@ from aiohttp import web -from gera2ld.pyserve import run_forever, start_server_aiohttp - from sldap import SLdap from sbs import SBS @@ -47,6 +45,7 @@ def do_get(request): return web.json_response({}, status=404) +DEFAULT_LOCAL_HOST = '127.0.0.1' DEFAULT_LOCAL_PORT = 3333 @@ -72,23 +71,49 @@ class BaseTest(TestCase): @classmethod def setUpClass(cls): def start_server(loop): + + async def init_api_server(handle, host, port): + server = web.ServerRunner(web.Server(handle)) + await server.setup() + + site = web.TCPSite( + server, + host=host, + port=port + ) + await site.start() + + return server + logger.debug("BaseTest start_server") - handle = APIHandler() - asyncio.set_event_loop(loop) - run_forever(start_server_aiohttp(handle, ':{}'.format(DEFAULT_LOCAL_PORT))) + + api_server = init_api_server( + APIHandler(), + DEFAULT_LOCAL_HOST, + DEFAULT_LOCAL_PORT + ) + + try: + loop.run_until_complete(api_server) + loop.run_forever() + finally: + loop.close() def check_server(): logger.debug("BaseTest check_server") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) - while sock.connect_ex(('127.0.0.1', DEFAULT_LOCAL_PORT)) == 111: + while sock.connect_ex( + (DEFAULT_LOCAL_HOST, DEFAULT_LOCAL_PORT) + ) == 111: time.sleep(0.1) if not os.environ.get("SBS_URL", None): logger.debug("BaseTest setUpClass") cls.loop = asyncio.new_event_loop() - cls.x = threading.Thread(target=start_server, args=(cls.loop,), ) - cls.x.start() + cls.api = threading.Thread(target=start_server, args=(cls.loop,), ) + cls.api.start() + check_server() else: cls.loop = None @@ -97,8 +122,10 @@ def check_server(): def tearDownClass(cls): if cls.loop: logger.debug("BaseTest tearDownClass") + for task in asyncio.all_tasks(cls.loop): + task.cancel() cls.loop.call_soon_threadsafe(cls.loop.stop) - cls.x.join() + cls.api.join() def setUp(self): """ Run a complete PLSC cycle, 1st ordered structure, 2nd flat structure...