diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b6c2c0c..5c8aeed 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -3,6 +3,12 @@ name: Release
on:
release:
types: [created]
+ workflow_dispatch:
+ inputs:
+ blog:
+ description: "Release Manually"
+ required: true
+ default: "yes"
jobs:
deploy:
@@ -27,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..7bb2720 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)
+
+> ℹ Mostly likely you may want to use [Use multiple Compose files](https://docs.docker.com/compose/how-tos/multiple-compose-files/) in Docker Compose itself.
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 provides flexible, declarative overlays to manage complex Docker Compose configurations with support for:
+
+
+
-- **[`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.
+## Features
-## But Why...? :thinking:
+Kompozit simplifies complex Docker Compose setups using declarative overlays, supporting:
+
+- **[`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.
+
+## 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 be36928..5053362 100644
--- a/setup.py
+++ b/setup.py
@@ -7,11 +7,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(
@@ -22,11 +22,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="Apache License",
@@ -46,7 +46,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"]},
)