From 1c3e9a18e2b3f217534bb707e999fda058e53fd2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ma=C3=ABl=20Gramain?= <mael@enpls.org>
Date: Mon, 21 Oct 2024 20:45:18 +0200
Subject: [PATCH] add ci for exposed ports

---
 .ci/port_attribution_watcher.py | 81 +++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)
 create mode 100644 .ci/port_attribution_watcher.py

diff --git a/.ci/port_attribution_watcher.py b/.ci/port_attribution_watcher.py
new file mode 100644
index 0000000..8432fc9
--- /dev/null
+++ b/.ci/port_attribution_watcher.py
@@ -0,0 +1,81 @@
+import os
+import yaml
+from pathlib import Path
+
+def recursiveSearchForDockerComposeFiles(watchFolder: Path):
+    dockerComposeList = []
+    for root, _, files in os.walk(watchFolder):
+        for file in files:
+            if file == "docker-compose.yaml":
+                dockerComposeList.append(Path(root) / file)
+    return dockerComposeList
+
+def readDockerComposeFile(dockerComposeFile: Path) -> yaml:
+    # Load the Docker Compose file
+    with open(dockerComposeFile, 'r') as file:
+        docker_compose = yaml.safe_load(file)
+    return docker_compose
+
+def getExposedPortsFromDockerComposeFile(dockerComposeFile: str):
+    exposedPorts = []
+    services = readDockerComposeFile(dockerComposeFile).get('services', {})
+    # Iterate through services to find ports
+    for service, config in services.items():
+        ports = config.get('ports', [])
+        for port in ports:
+            # Split the port mapping if it's in the "host:container" format
+            exposedPort = port.split(':')[0]  # Get the host port
+            exposedPorts.append(exposedPort)
+    return exposedPorts
+
+def checkPortUnique(ports: dict) -> bool:
+    portNumbers = []
+    wrong = False
+    for project in ports:
+        for port in ports[project]:
+            if port in portNumbers:
+                if not wrong:
+                    print("\n\nšŸ”“ Some ports are not unique šŸ–\n")
+                print(f"šŸ”“ ERROR : Port *{port}* is not unique")
+                wrong = True
+                for project2 in ports:
+                    if port in ports[project2]:
+                        print(f"\t{project} and {project2}")
+            portNumbers.append(port)
+    return wrong
+
+def main(watchFolder: str):
+    # For each folder, read docker-compose.yaml and look for exposed ports
+    dockerComposeList = recursiveSearchForDockerComposeFiles(watchFolder)
+    ports = {}
+    for dockerComposeFile in dockerComposeList:
+        # Get exposed ports
+        exposedPorts = getExposedPortsFromDockerComposeFile(dockerComposeFile)
+        ports[dockerComposeFile] = exposedPorts
+        if len(exposedPorts) == 0:
+            print(f"\nšŸ“¦ {dockerComposeFile}"
+                  f"\nšŸ”“ No exposed ports found"
+                  f"\n")
+        else:
+            print(f"\nšŸ“¦ {dockerComposeFile}"
+                f"\nšŸ”— Exposed ports: {exposedPorts}"
+                f"\n")
+    
+    wrong = checkPortUnique(ports)
+    
+    if wrong:
+        exit(1)
+    else:
+        print("\n\nāœ… All ports are unique\n\n")
+        exit(0)
+
+if __name__ == "__main__":
+    print("Traefik label watcher CI/CD")
+    # Read folder from env
+    watchFolder = os.getenv("WATCH_FOLDER")
+    if watchFolder == None:
+        watchFolder = "."
+        
+    watchFolder = Path(watchFolder)
+    
+    main(watchFolder)
\ No newline at end of file