From 82d96920c9b7321252dfe38f1d2aef3aab342733 Mon Sep 17 00:00:00 2001 From: Veerendra Date: Sat, 30 Nov 2024 23:54:35 +0100 Subject: [PATCH] 7 Add prefix and suffix (#8) * 7 Add prefix and suffix * Fix lints * Fix lints * fix lints * Adjust examples and add logo * Add workflow_dispatch * Fix lints --- .github/workflows/release.yml | 11 ++- README.md | 56 +++++++++++---- examples/README.md | 71 +++++++------------ examples/base/docker-compose-traefik.yml | 44 ------------ examples/base/kompozition.yml | 2 - .../overlay/docker-compose-traefik-patch.yml | 4 -- .../docker-compose-pg-patch.yml | 0 .../docker-compose-traefik-patch.yml | 12 ++++ .../overlay/{ => homeserver}/kompozition.yaml | 4 +- .../docker-compose-traefik-patch.yml | 46 ++++++++++++ .../overlay/public_wordpress/kompozition.yaml | 15 ++++ kompozit/kompozit.py | 32 +++++---- setup.py | 14 ++-- 13 files changed, 180 insertions(+), 131 deletions(-) delete mode 100644 examples/overlay/docker-compose-traefik-patch.yml rename examples/overlay/{ => homeserver}/docker-compose-pg-patch.yml (100%) create mode 100644 examples/overlay/homeserver/docker-compose-traefik-patch.yml rename examples/overlay/{ => homeserver}/kompozition.yaml (88%) create mode 100644 examples/overlay/public_wordpress/docker-compose-traefik-patch.yml create mode 100644 examples/overlay/public_wordpress/kompozition.yaml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d4f41e..f4c995c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,9 +3,16 @@ name: Release on: release: types: [created] + workflow_dispatch: + inputs: + blog: + description: "Release Manually" + required: true + default: "yes" jobs: - deploy: + publish: + name: Publish PyPi runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -26,7 +33,7 @@ jobs: twine upload --repository pypi dist/* docker-build: - name: Docker Build + name: Docker Build & Push runs-on: ubuntu-22.04 steps: - name: Checkout Code diff --git a/README.md b/README.md index 738ce59..f9100e0 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,30 @@ # Kompozit -> :construction_worker: This tool is currently in beta and still under development! +![Docker Pulls](https://img.shields.io/docker/pulls/veerendra2/kompozit) ![PyPI - Status](https://img.shields.io/pypi/status/kompozit) ![PyPI - Version](https://img.shields.io/pypi/v/kompozit) [![Release](https://github.com/veerendra2/kompozit/actions/workflows/release.yml/badge.svg)](https://github.com/veerendra2/kompozit/actions/workflows/release.yml) + +> 🚧 This tool is currently under development! Declarative Configuration Management Tool for Docker Compose. -_Like [`kustomize`](https://kustomize.io/), but for [Docker Compose](https://docs.docker.com/compose/)._ +_Like [`kustomize.io`](https://kustomize.io/), but for [Docker Compose](https://docs.docker.com/compose/)._ + +

+ kompozit logo +

+ +## Features -Kompozit provides flexible, declarative overlays to manage complex Docker Compose configurations with support for: +Kompozit simplifies complex Docker Compose setups using declarative overlays, supporting: -- **[`patchesJSON6902`](https://datatracker.ietf.org/doc/html/rfc6902)**: JSON Merge Patch for precise modifications. -- **[`patchesStrategicMerge`](https://stackoverflow.com/q/71165168/2200798)**: Strategic Merge Patch for hierarchical changes. +- **[`patchesJSON6902`](https://datatracker.ietf.org/doc/html/rfc6902)**: Precise modifications with JSON Merge Patch. +- **[`patchesStrategicMerge`](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md)**: Flexible hierarchical changes with Strategic Merge Patch. -## But Why...? :thinking: +## Resources + +- 📖 [Documentation](https://veerendra2.gitbook.io/kompozit) +- 🛠️ [Examples](https://github.com/veerendra2/kompozit/tree/main/examples) + +## But Why...? 🤔 There are scenarios where you might need different Docker Compose configurations for the same application on different machines. @@ -21,18 +34,14 @@ Additionally, kompozit allows you to combine multiple `docker-compose.yml` files - For instance, you can keep a generic `docker-compose.yml` for PostgreSQL in a central location and customize it for different stacks in other locations as needed. -## Installation :computer: +## Installation 💻 -```bash -python -m pip install kompozit -``` +### PyPi -## Usage :gear: +> [https://pypi.org/project/kompozit/](https://pypi.org/project/kompozit/) ```bash -git clone git@github.com:veerendra2/kompozit.git -cd kompozit -python -m pip install . +python -m pip install kompozit kompozit --help usage: kompozit [-h] [-b BUILD_PATH] [-o OUTPUT_DIR] [-v] @@ -46,15 +55,32 @@ options: -o, --output-dir OUTPUT_DIR Directory to save the generated Docker Compose files. (default: None) -v, --version Show kompozit version +``` + +### Docker + +> [https://hub.docker.com/r/veerendra2/kompozit](https://hub.docker.com/r/veerendra2/kompozit) + +```bash +docker pull veerendra2/kompozit +``` + +## Usage ⚙️ + +```bash +git clone git@github.com:veerendra2/kompozit.git +cd kompozit +python -m pip install . kompozit --build ./examples/overlay +... # inside docker docker pull kompozit:latest docker run -it --rm -v ./examples:/examples kompozit:latest -b /examples/overlay ``` -## Local Development :wrench: +## Local Development 🔧 ```bash git clone git@github.com:veerendra2/kompozit.git diff --git a/examples/README.md b/examples/README.md index c70aa36..af9e109 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,64 +1,47 @@ # Example +[Docs](https://veerendra2.gitbook.io/kompozit) + ```bash -$ python3 -m pip install kompozit -$ kompozit -b examples/overlay/ +python3 -m pip install kompozit + +kompozit -b examples/overlay/homeserver/ --- networks: - db: + public: attachable: true -services: - postgres: - container_name: postgres - hostname: postgres - image: postgres:14-alpine - labels: - - com.centurylinklabs.watchtower.enable=false - - traefik.enable=false - networks: - - db - restart: always - volumes: - - acme:/var/lib/postgresql/data:rw -volumes: - acme: null ---- -networks: + internal: false private: attachable: true internal: true - public: - attachable: true - internal: false +volumes: + acme: null services: - traefik: - command: - - NO_COMMAND_TEST + dev-traefik-test: + image: traefik:v2 + hostname: traefik container_name: traefik + restart: unless-stopped environment: - CLOUDFLARE_DNS_API_TOKEN: ${CLOUDFLARE_DNS_API_TOKEN} DUCKDNS_TOKEN: ${DUCKDNS_TOKEN} - hostname: traefik - image: traefik:v2 - labels: - - com.centurylinklabs.watchtower.enable=true - - traefik.enable=true - - traefik.docker.network=traefik_public - - traefik.http.routers.api.tls=true - - traefik.http.routers.api.entryPoints=websecure - - traefik.http.routers.api.service=api@internal - - traefik.http.routers.api.tls.certresolver=letsencrypt - - traefik.http.routers.api.rule=Host(`${MY_DOMAIN}`) - networks: - - public - - private + CLOUDFLARE_DNS_API_TOKEN: ${CLOUDFLARE_DNS_API_TOKEN} ports: - 80:80/tcp - 443:443/tcp - restart: unless-stopped + networks: + - public + - private volumes: - /var/run/docker.sock:/var/run/docker.sock - acme:/letsencrypt -volumes: - acme: null + command: + - --log.level=INFO + - --api.insecure=false + - --api.dashboard=false + - --providers.docker=true + - --providers.docker.exposedByDefault=false + - --global.sendAnonymousUsage=false + - --global.checkNewVersion=false + labels: + - com.centurylinklabs.watchtower.enable=true ``` diff --git a/examples/base/docker-compose-traefik.yml b/examples/base/docker-compose-traefik.yml index e08239c..255082a 100644 --- a/examples/base/docker-compose-traefik.yml +++ b/examples/base/docker-compose-traefik.yml @@ -16,50 +16,6 @@ services: hostname: traefik container_name: traefik restart: unless-stopped - command: - - "--log.level=INFO" - - "--api.insecure=false" - - "--api.dashboard=false" - - "--providers.docker=true" - - "--providers.docker.exposedByDefault=false" - - "--global.sendAnonymousUsage=false" - - "--global.checkNewVersion=false" - # ---------------------------------- ACME -------------------------------------------- - - "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" - - "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=5" - - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=${MY_PROVIDER}" - - "--certificatesresolvers.letsencrypt.acme.email=mail@mail.com" - - "--certificatesresolvers.letsencrypt.acme.dnschallenge.disablePropagationCheck=true" - - "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53" - - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - # -------------------------------- ENTRYPOINT ----------------------------------------- - - "--entrypoints.web.address=:80" - - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - - "--entrypoints.websecure.address=:443" - # -------------------------------- PROXY ----------------------------------------- - - "--entryPoints.web.forwardedHeaders.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7" - - "--entryPoints.web.proxyProtocol.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7" - - "--entryPoints.websecure.forwardedHeaders.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7" - - "--entryPoints.websecure.proxyProtocol.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7" - - "--entryPoints.web.forwardedHeaders.insecure=false" - - "--entryPoints.web.proxyProtocol.insecure=false" - - "--entryPoints.websecure.forwardedHeaders.insecure=false" - - "--entryPoints.websecure.proxyProtocol.insecure=false" - # -------------------------------- PLUGINS ----------------------------------------- - - --experimental.plugins.fail2ban.modulename=github.com/juitde/traefik-plugin-fail2ban - - --experimental.plugins.fail2ban.version=v0.2.0 - - --experimental.plugins.fail2ban.modulename=github.com/Paxxs/traefik-get-real-ip - - --experimental.plugins.fail2ban.version=v1.0.2 - labels: - - com.centurylinklabs.watchtower.enable=true - - traefik.enable=true - - traefik.docker.network=traefik_public - - traefik.http.routers.api.tls=true - - traefik.http.routers.api.entryPoints=websecure - - traefik.http.routers.api.service=api@internal - - traefik.http.routers.api.tls.certresolver=letsencrypt - - traefik.http.routers.api.rule=Host(`${MY_DOMAIN}`) environment: DUCKDNS_TOKEN: "${DUCKDNS_TOKEN}" CLOUDFLARE_DNS_API_TOKEN: "${CLOUDFLARE_DNS_API_TOKEN}" diff --git a/examples/base/kompozition.yml b/examples/base/kompozition.yml index 0a53d64..245a285 100644 --- a/examples/base/kompozition.yml +++ b/examples/base/kompozition.yml @@ -2,5 +2,3 @@ resources: - docker-compose-pg.yml - docker-compose-traefik.yml -namePrefix: ggg- -nameSufix: -veer diff --git a/examples/overlay/docker-compose-traefik-patch.yml b/examples/overlay/docker-compose-traefik-patch.yml deleted file mode 100644 index b59f5ee..0000000 --- a/examples/overlay/docker-compose-traefik-patch.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - traefik: - command: - - NO_COMMAND_TEST diff --git a/examples/overlay/docker-compose-pg-patch.yml b/examples/overlay/homeserver/docker-compose-pg-patch.yml similarity index 100% rename from examples/overlay/docker-compose-pg-patch.yml rename to examples/overlay/homeserver/docker-compose-pg-patch.yml diff --git a/examples/overlay/homeserver/docker-compose-traefik-patch.yml b/examples/overlay/homeserver/docker-compose-traefik-patch.yml new file mode 100644 index 0000000..deb0635 --- /dev/null +++ b/examples/overlay/homeserver/docker-compose-traefik-patch.yml @@ -0,0 +1,12 @@ +services: + traefik: + command: + - "--log.level=INFO" + - "--api.insecure=false" + - "--api.dashboard=false" + - "--providers.docker=true" + - "--providers.docker.exposedByDefault=false" + - "--global.sendAnonymousUsage=false" + - "--global.checkNewVersion=false" + labels: + - com.centurylinklabs.watchtower.enable=true diff --git a/examples/overlay/kompozition.yaml b/examples/overlay/homeserver/kompozition.yaml similarity index 88% rename from examples/overlay/kompozition.yaml rename to examples/overlay/homeserver/kompozition.yaml index 7809a5b..8aebdc0 100644 --- a/examples/overlay/kompozition.yaml +++ b/examples/overlay/homeserver/kompozition.yaml @@ -1,9 +1,9 @@ --- resources: - - ../base + - ../../base namePrefix: dev- -nameSufix: -test +nameSuffix: -test patchesStrategicMerge: - path: docker-compose-traefik-patch.yml diff --git a/examples/overlay/public_wordpress/docker-compose-traefik-patch.yml b/examples/overlay/public_wordpress/docker-compose-traefik-patch.yml new file mode 100644 index 0000000..3aba5d7 --- /dev/null +++ b/examples/overlay/public_wordpress/docker-compose-traefik-patch.yml @@ -0,0 +1,46 @@ +services: + traefik: + command: + - "--log.level=INFO" + - "--api.insecure=false" + - "--api.dashboard=false" + - "--providers.docker=true" + - "--providers.docker.exposedByDefault=false" + - "--global.sendAnonymousUsage=false" + - "--global.checkNewVersion=false" + # ---------------------------------- ACME -------------------------------------------- + - "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" + - "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=5" + - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=${MY_PROVIDER}" + - "--certificatesresolvers.letsencrypt.acme.email=mail@mail.com" + - "--certificatesresolvers.letsencrypt.acme.dnschallenge.disablePropagationCheck=true" + - "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + # -------------------------------- ENTRYPOINT ----------------------------------------- + - "--entrypoints.web.address=:80" + - "--entrypoints.web.http.redirections.entrypoint.to=websecure" + - "--entrypoints.web.http.redirections.entrypoint.scheme=https" + - "--entrypoints.websecure.address=:443" + # -------------------------------- PROXY ----------------------------------------- + - "--entryPoints.web.forwardedHeaders.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7" + - "--entryPoints.web.proxyProtocol.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7" + - "--entryPoints.websecure.forwardedHeaders.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7" + - "--entryPoints.websecure.proxyProtocol.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7" + - "--entryPoints.web.forwardedHeaders.insecure=false" + - "--entryPoints.web.proxyProtocol.insecure=false" + - "--entryPoints.websecure.forwardedHeaders.insecure=false" + - "--entryPoints.websecure.proxyProtocol.insecure=false" + # -------------------------------- PLUGINS ----------------------------------------- + - --experimental.plugins.fail2ban.modulename=github.com/juitde/traefik-plugin-fail2ban + - --experimental.plugins.fail2ban.version=v0.2.0 + - --experimental.plugins.fail2ban.modulename=github.com/Paxxs/traefik-get-real-ip + - --experimental.plugins.fail2ban.version=v1.0.2 + labels: + - com.centurylinklabs.watchtower.enable=true + - traefik.enable=true + - traefik.docker.network=traefik_public + - traefik.http.routers.api.tls=true + - traefik.http.routers.api.entryPoints=websecure + - traefik.http.routers.api.service=api@internal + - traefik.http.routers.api.tls.certresolver=letsencrypt + - traefik.http.routers.api.rule=Host(`${MY_DOMAIN}`) \ No newline at end of file diff --git a/examples/overlay/public_wordpress/kompozition.yaml b/examples/overlay/public_wordpress/kompozition.yaml new file mode 100644 index 0000000..199bb98 --- /dev/null +++ b/examples/overlay/public_wordpress/kompozition.yaml @@ -0,0 +1,15 @@ +--- +resources: + - ../../base + +namePrefix: dev- +nameSuffix: -test + +patchesStrategicMerge: + - path: docker-compose-traefik-patch.yml + +patchesJSON6902: + - patch: + - op: replace + path: /services/traefik/image + value: "traefik:v2" diff --git a/kompozit/kompozit.py b/kompozit/kompozit.py index 0a3230f..a24205b 100755 --- a/kompozit/kompozit.py +++ b/kompozit/kompozit.py @@ -12,7 +12,7 @@ from jsonpatch import JsonPatchConflict from jsonpointer import JsonPointerException -__version__ = "0.1.0-beta" +__version__ = "0.2.0-beta" CONFIG_FILE_NAMES = ["kompozition.yaml", "kompozition.yml"] @@ -109,12 +109,15 @@ def resolve_paths(overlay_path): def apply_patches(config, output_dir): """Apply patches to resources""" - if output_dir: - os.makedirs(output_dir, exist_ok=True) - resources = config.get("resources", []) json_patches = config.get("patchesJSON6902", []) patches_strategic_merge = config.get("patchesStrategicMerge", []) + yaml_dump_kwargs = { + "indent": 2, + "explicit_start": True, + "encoding": "utf-8", + "sort_keys": False, + } for resource in resources: resource_data = load_yaml(resource) @@ -122,8 +125,7 @@ def apply_patches(config, output_dir): # patchesJSON6902 for patch in json_patches: try: - patch_obj = jsonpatch.JsonPatch(patch["patch"]) - resource_data = patch_obj.apply(resource_data) + resource_data = jsonpatch.JsonPatch(patch["patch"]).apply(resource_data) except (JsonPointerException, JsonPatchConflict): continue @@ -132,18 +134,22 @@ def apply_patches(config, output_dir): patch_base_file = os.path.splitext(os.path.basename(patch["path"]))[0] resource_base_file = os.path.splitext(os.path.basename(resource))[0] if resource_base_file == patch_base_file.replace("-patch", ""): - patch_data = load_yaml(patch["path"]) - resource_data = CUSTOM_MERGER.merge(resource_data, patch_data) + resource_data = CUSTOM_MERGER.merge( + resource_data, load_yaml(patch["path"]) + ) + + # update namePrefix and nameSufix + for svc in resource_data["services"].copy(): + new_svc = config.get("namePrefix", "") + svc + config.get("nameSuffix", "") + resource_data["services"][new_svc] = resource_data["services"].pop(svc) if output_dir: + os.makedirs(output_dir, exist_ok=True) output_file = os.path.join(output_dir, os.path.basename(resource)) with open(output_file, encoding="utf-8" "w") as file: - yaml.dump(resource_data, file, indent=2) + yaml.dump(resource_data, stream=file, **yaml_dump_kwargs) else: - print("---") - yaml.dump( - resource_data, stream=sys.stdout, indent=2, default_flow_style=False - ) + yaml.dump(resource_data, stream=sys.stdout, **yaml_dump_kwargs) def main(): diff --git a/setup.py b/setup.py index 8efd8d4..75ee6f2 100644 --- a/setup.py +++ b/setup.py @@ -8,11 +8,11 @@ from setuptools import find_packages, setup -with open("kompozit/kompozit.py", "r", encoding="utf-8") as file: +with open("kompozit/kompozit.py", encoding="utf-8") as file: REGEX_VERSION = r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]' version = re.search(REGEX_VERSION, file.read(), re.MULTILINE).group(1) # type: ignore[union-attr] -with open("README.md", "r", encoding="utf-8") as file: +with open("README.md", encoding="utf-8") as file: readme = file.read() setup( @@ -23,11 +23,11 @@ long_description=readme, long_description_content_type="text/markdown", author="veerendra2", - author_email="veerendra2@github.com", + author_email="vk.tyk23@simplelogin.com", url="https://github.com/veerendra2/kompozit", download_url=f"https://github.com/veerendra2/kompozit/archive/{version}.tar.gz", project_urls={ - "Documentation": "https://kompozit.readthedocs.io/en/latest/", + "Documentation": "https://veerendra2.gitbook.io/kompozit", }, keywords=["gitops", "cicd", "docker-compose", "configuration management"], license="MIT", @@ -47,7 +47,11 @@ "Programming Language :: Python :: 3.13", "Topic :: Utilities", ], - install_requires=["deepmerge==2.0", "jsonpatch==1.33", "PyYAML==6.0.2"], + install_requires=[ + "deepmerge==2.0", + "jsonpatch==1.33", + "PyYAML==6.0.2", + ], python_requires=">=3.9", entry_points={"console_scripts": ["kompozit = kompozit.kompozit:main"]}, )