From 849294d6890fe1b812c51da007e4f0b066e5b3bc Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Fri, 10 Jan 2025 16:47:02 +0100
Subject: [PATCH 01/17] commit to create draft PR

---
 .gitignore                                    |   4 +
 docker-compose.yml => docker-compose.base.yml |  32 +++--
 docker-compose.monitoring.yml                 |  18 +--
 wis2box-ctl.py                                | 110 +++++++++++++++++-
 4 files changed, 133 insertions(+), 31 deletions(-)
 rename docker-compose.yml => docker-compose.base.yml (86%)

diff --git a/.gitignore b/.gitignore
index 20bf19c9..b9f3b024 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,7 @@ wis2box.env
 docker/.env
 
 .ipynb_checkpoints
+tests/data/.ssh/id_rsa
+tests/data/.ssh/id_rsa.pub
+
+docker-compose.yml
diff --git a/docker-compose.yml b/docker-compose.base.yml
similarity index 86%
rename from docker-compose.yml
rename to docker-compose.base.yml
index f45a6684..1758dc78 100644
--- a/docker-compose.yml
+++ b/docker-compose.base.yml
@@ -13,7 +13,7 @@ services:
 
   wis2box-ui:
     container_name: wis2box-ui
-    image: ghcr.io/wmo-im/wis2box-ui:latest
+    image: wmoim/wis2box-ui:latest
     restart: always
     env_file:
       - wis2box.env
@@ -22,14 +22,14 @@ services:
 
   wis2box-webapp:
     container_name: wis2box-webapp
-    image: ghcr.io/wmo-im/wis2box-webapp:latest
+    image: wmoim/wis2box-webapp:latest
     env_file:
       - wis2box.env
     restart: always
 
   wis2box-api:
     container_name: wis2box-api
-    image: ghcr.io/wmo-im/wis2box-api:latest
+    image: wmoim/wis2box-api:latest
     restart: always
     env_file:
       - wis2box.env
@@ -104,10 +104,8 @@ services:
 
   mosquitto:
     container_name: mosquitto
-    #image: ghcr.io/wmo-im/wis2box-broker:latest
+    image: wmoim/wis2box-broker:local
     restart: always
-    build:
-      context: ./wis2box-broker
     env_file:
       - wis2box.env
     volumes:
@@ -115,13 +113,10 @@ services:
 
   wis2box-management:
     container_name: wis2box-management
+    image: wmoim/wis2box-management:local
     mem_limit: 1g
     memswap_limit: 1g
     restart: always
-    #image: ghcr.io/wmo-im/wis2box-management:latest
-    build:
-      context: ./wis2box-management
-    #user: wis2box:wis2box
     env_file:
       - wis2box.env
     volumes:
@@ -134,9 +129,22 @@ services:
         condition: service_healthy
     command: ["wis2box", "pubsub" , "subscribe"]
 
+  # mqtt_metrics_collector, listens to mqtt-broker
+  mqtt_metrics_collector:
+    container_name: mqtt_metrics_collector
+    restart: unless-stopped
+    env_file:
+      - wis2box.env
+    image: wmoim/wis2box-mqtt-metrics-collector:local
+    depends_on:
+      - mosquitto
+      - wis2box-management
+    ports:
+      - 8001:8001
+
   wis2box-auth:
     container_name: wis2box-auth
-    image: ghcr.io/wmo-im/wis2box-auth:latest
+    image: wmoim/wis2box-auth:latest
     restart: always
     env_file:
       - wis2box.env
@@ -147,7 +155,7 @@ services:
 
   wis2downloader:
     container_name: wis2downloader
-    image: ghcr.io/wmo-im/wis2downloader:v0.3.2
+    image: wmoim/wis2downloader:latest
     restart: always
     env_file:
       - wis2box.env
diff --git a/docker-compose.monitoring.yml b/docker-compose.monitoring.yml
index d4291023..eade2ea8 100644
--- a/docker-compose.monitoring.yml
+++ b/docker-compose.monitoring.yml
@@ -34,22 +34,6 @@ services:
       vpcbr: # this is the place where we assign the static ipv4 address
         ipv4_address: 10.5.0.2
       default:
-
-  # mqtt_metrics_collector, listens to mqtt-broker
-  mqtt_metrics_collector:
-    <<: *logging
-    container_name: mqtt_metrics_collector
-    restart: unless-stopped
-    env_file:
-      - wis2box.env
-    #image: ghcr.io/wmo-im/wis2box-mqtt-metrics-collector:latest
-    build:
-      context: ./wis2box-mqtt-metrics-collector
-    depends_on:
-      - mosquitto
-      - wis2box-management
-    ports:
-      - 8001:8001
     
   # prometheus to collect metrics
   prometheus:
@@ -129,6 +113,8 @@ services:
     <<: *logging
   wis2box-auth:
     <<: *logging
+  mqtt_metrics_collector:
+    <<: *logging
   minio:
     <<: *logging
   web-proxy:
diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index 070a8d19..5d0e2788 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -22,8 +22,11 @@
 
 import argparse
 import os
+import re
+import requests
 import subprocess
 
+
 if subprocess.call(['docker', 'compose'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) > 0:
     DOCKER_COMPOSE_COMMAND = 'docker-compose'
 else:
@@ -92,6 +95,99 @@
 
 args = parser.parse_args()
 
+# define ENUM for docker image usage, LATEST or LOCAL
+LOCAL = 0
+LATEST = 1
+
+LOCAL_IMAGES = [
+    'wis2box-management',
+    'wis2box-broker',
+    'wis2box-mqtt-metrics-collector'
+]
+
+def build_local_images() -> None:
+    """
+    Build local images
+
+    :returns: None.
+    """
+    for image in LOCAL_IMAGES:
+        print(f'Building {image}')
+        run(split(f'docker build -t wmoim/{image}:local {image}'))
+
+    return None
+
+def get_latest_image_tag(image: str) -> str:
+    """
+    list image tags by querying docker hub api
+    skip the 'latest' tag
+    return the most recent tag
+
+    :param image: required, string. Name of the image.
+
+    :returns: string. The most recent tag.
+    """
+
+    url = f"https://hub.docker.com/v2/repositories/wmoim/{image}/tags/"
+    tags = []
+    try:
+        # Paginate through results to collect all tags
+        while url:
+            response = requests.get(url)
+            response.raise_for_status()  # Raise an error for HTTP failures
+            data = response.json()
+            tags.extend([
+                tag['name'] for tag in data.get('results', [])
+                if tag['name'] != 'latest'  # Skip 'latest' tag
+            ])
+            url = data.get('next')  # Get the next page URL
+    except requests.RequestException as e:
+        raise RuntimeError(f"Failed to fetch tags for image '{image}': {e}")
+
+    if not tags:
+        raise ValueError(f"No valid tags found for image '{image}'")
+    else:
+        print(f"Found {len(tags)} tags for image '{image}: {tags}'")
+
+    # define a function to sort tags by version number
+    def tag_sort_key(tag):
+        # Extract numeric and non-numeric parts
+        parts = re.split(r'(\d+)', tag)  # Split into numeric and non-numeric segments
+        return [
+            int(part) if part.isdigit() else 'ZZZZZZZZZZ'  # Use a large number for non-numeric parts
+            for part in parts
+        ]
+
+    # Sort tags by version number in descending order
+    tags.sort(key=tag_sort_key, reverse=True)
+    return tags[0]
+    
+
+def update_docker_images(image_type: int) -> None:
+    """
+    Write docker-compose.yml using docker-compose.base.yml as base
+    and set image-versions based on the latest available images in wmoim
+
+    image_type: int. LATEST or LOCAL
+
+    :returns: None.
+    """
+    
+    with open('docker-compose.base.yml', 'r') as f:
+        lines = f.readlines()
+    
+        with open('docker-compose.yml', 'w') as f:
+            for line in lines:
+                if 'image: wmoim/' in line and image_type == LATEST:
+                    image = line.split('wmoim/')[1].split(':')[0]
+                    print(f'Get latest image tag for {image}')
+                    tag = get_latest_image_tag(image)
+                    print(f'Set {image} to {tag}')
+                    f.write(f'    image: wmoim/{image}:{tag}\n')
+                else:
+                    f.write(line)
+
+        return None
 
 def split(value: str) -> list:
     """
@@ -169,8 +265,8 @@ def make(args) -> None:
     if args.command == "config":
         run(split(f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} config'))
     elif args.command == "build":
-        run(split(
-            f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} build {containers}'))
+        build_local_images()
+        update_docker_images(LOCAL)
     elif args.command in ["up", "start", "start-dev"]:
         run(split(
             'docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions'),
@@ -199,7 +295,15 @@ def make(args) -> None:
             run(split(
                 f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} down --remove-orphans {containers}'))
     elif args.command == "update":
-        run(split(f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} pull'))
+        update_docker_images(LATEST)
+        # restart all containers
+        run(split(
+                f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} down --remove-orphans'))
+        run(split(
+                f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} up -d'))
+        # prune dangling images
+        _ = run(split('docker images --filter dangling=true -q --no-trunc'))
+        run(split(f'docker rmi {_}'))
     elif args.command == "prune":
         run(split('docker builder prune -f'))
         run(split('docker container prune -f'))

From 8680e78a57b5f1951460ebc8a70bba3766730ab5 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Fri, 10 Jan 2025 16:58:56 +0100
Subject: [PATCH 02/17] use LATEST by default

---
 wis2box-ctl.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index 5d0e2788..14d88687 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -268,6 +268,9 @@ def make(args) -> None:
         build_local_images()
         update_docker_images(LOCAL)
     elif args.command in ["up", "start", "start-dev"]:
+        # if docker-compose.yml does not exist, run update to create it
+        if not os.path.exists('docker-compose.yml'):
+            update_docker_images(LATEST)
         run(split(
             'docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions'),
             silence_stderr=True)

From fc9fb41fc4b9bf2868f48adbd2524abeb11be2cd Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Mon, 13 Jan 2025 14:06:48 +0100
Subject: [PATCH 03/17] try out new test command

---
 .github/workflows/tests-docker.yml | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/tests-docker.yml b/.github/workflows/tests-docker.yml
index 36f926c9..07ee6581 100644
--- a/.github/workflows/tests-docker.yml
+++ b/.github/workflows/tests-docker.yml
@@ -43,13 +43,9 @@ jobs:
         sed -i "s/localhost/$IP/g" wis2box.env
         cat wis2box.env
         python3 wis2box-ctl.py config
-    - name: build wis2box
+    - name: build wis2box and check status ⚙️
       run: |
-        python3 wis2box-ctl.py build
-        python3 wis2box-ctl.py update
-    - name: start containers ⚙️
-      run: |
-        python3 wis2box-ctl.py start
+        python3 wis2box-ctl.py update-local-build
         python3 wis2box-ctl.py status -a
     - name: show environment and check collections exist ⚙️
       run: |

From 21ef25ec22852746daca0280b567773c3dc699d9 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Mon, 13 Jan 2025 16:34:58 +0100
Subject: [PATCH 04/17] make different update commands, function for cleaning
 images

---
 wis2box-ctl.py | 153 +++++++++++++++++++++++++++++--------------------
 1 file changed, 92 insertions(+), 61 deletions(-)

diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index 14d88687..bef73c0b 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -25,6 +25,7 @@
 import re
 import requests
 import subprocess
+import shutil
 
 
 if subprocess.call(['docker', 'compose'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) > 0:
@@ -41,25 +42,17 @@
     """
 
 parser = argparse.ArgumentParser(
-    description='manage a compposition of docker containers to implement a wis 2 box',
-    formatter_class=argparse.RawTextHelpFormatter)
+    description='Manage a composition of Docker containers to implement wis2box',
+    formatter_class=argparse.RawTextHelpFormatter
+)
 
-parser.add_argument(
-    '--ssl',
-    dest='ssl',
-    action='store_true',
-    help='run wis2box with SSL enabled')
-
-parser.add_argument(
-    '--simulate',
-    dest='simulate',
-    action='store_true',
-    help='simulate execution by printing action rather than executing')
+parser.add_argument('--simulate',
+                    action='store_true',
+                    help='Simulate execution by printing action rather than executing')
 
 commands = [
     'build',
     'config',
-    'down',
     'execute',
     'lint',
     'logs',
@@ -70,41 +63,54 @@
     'start-dev',
     'status',
     'stop',
-    'up',
     'update',
+    'update-local-build',
+    'update-latest-tags'
 ]
 
 parser.add_argument('command',
                     choices=commands,
-                    help="""
+                    help="""The command to execute:
     - config: validate and view Docker configuration
-    - build [containers]: build all services
-    - start [containers]: start system
-    - start-dev [containers]: start system in local development mode
-    - login [container]: login to the container (default: wis2box-management)
-    - login-root [container]: login to the container as root
-    - stop: stop [container] system
+    - build: build all services
+    - start: start system
+    - start-dev: start system in local development mode
+    - login: login to the container (default: wis2box-management)
+    - stop: stop system
     - update: update Docker images
     - prune: cleanup dangling containers and images
-    - restart [containers]: restart one or all containers
-    - status [containers|-a]: view status of wis2box containers
+    - restart: restart containers
+    - status: view status of wis2box containers
     - lint: run PEP8 checks against local Python code
     """)
 
-parser.add_argument('args', nargs=argparse.REMAINDER)
+parser.add_argument('args', nargs=argparse.REMAINDER, help='Additional arguments for the command')
 
 args = parser.parse_args()
 
-# define ENUM for docker image usage, LATEST or LOCAL
-LOCAL = 0
-LATEST = 1
-
 LOCAL_IMAGES = [
     'wis2box-management',
     'wis2box-broker',
     'wis2box-mqtt-metrics-collector'
 ]
 
+def remove_docker_images(filter: str) -> None:
+    # Get the IDs of images matching the filter
+    result = subprocess.run(
+        ['docker', 'images', '--filter', f'reference={filter}', '-q', '--no-trunc'],
+        capture_output=True,
+        text=True
+    )
+    
+    image_ids = result.stdout.strip()
+    if image_ids:  # If there are images to remove
+        for image_id in image_ids.splitlines():
+            try:
+                subprocess.run(['docker', 'rmi', image_id], check=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
+            except subprocess.CalledProcessError as e:
+                # do nothing
+                pass
+
 def build_local_images() -> None:
     """
     Build local images
@@ -163,30 +169,56 @@ def tag_sort_key(tag):
     return tags[0]
     
 
-def update_docker_images(image_type: int) -> None:
+def update_docker_images(use_local_build: bool = False, use_latest: bool = False) -> None:
     """
     Write docker-compose.yml using docker-compose.base.yml as base
-    and set image-versions based on the latest available images in wmoim
-
-    image_type: int. LATEST or LOCAL
+    
 
+    use_local_build: required, boolean. If True, build local images
+    use_tags: required, boolean. If True, pull last tagged release for image. If False, use local images or tag='latest' # noqa
+    
     :returns: None.
     """
     
+    if os.path.exists('docker-compose.yml'):
+        print('Backing up current docker-compose.yml to docker-compose.yml.bak')
+        shutil.copy('docker-compose.yml', 'docker-compose.yml.bak')
+
+    if use_local_build:
+        print('Building local images')
+        build_local_images()
+
+    print('Updating docker-compose.yml')
+
     with open('docker-compose.base.yml', 'r') as f:
         lines = f.readlines()
-    
         with open('docker-compose.yml', 'w') as f:
             for line in lines:
-                if 'image: wmoim/' in line and image_type == LATEST:
+                if 'image: wmoim/' in line:
                     image = line.split('wmoim/')[1].split(':')[0]
-                    print(f'Get latest image tag for {image}')
-                    tag = get_latest_image_tag(image)
+                    
+                    # determine the tag to use
+                    tag = 'latest'
+                    if image in LOCAL_IMAGES and use_local_build:
+                        tag = 'local'
+                    elif image not in LOCAL_IMAGES and use_local_build:
+                        tag = 'latest'
+                    elif not use_latest:
+                        print(f'Get latest image tag for {image}')
+                        tag = get_latest_image_tag(image)
+                    
+                    # pull the image if it is not local, o
+                    if tag != 'local':
+                        print(f'Pulling wmoim/{image}:{tag}')
+                        # pull the latest tag for the image
+                        run(split(f'docker pull wmoim/{image}:{tag}'))
+                    
+                    # update the image tag in the docker-compose.yml
                     print(f'Set {image} to {tag}')
                     f.write(f'    image: wmoim/{image}:{tag}\n')
                 else:
                     f.write(line)
-
+        print('docker-compose.yml updated')
         return None
 
 def split(value: str) -> list:
@@ -251,11 +283,8 @@ def make(args) -> None:
             if 'WIS2BOX_SSL_CERT' in line:
                 ssl_cert = line.split('=')[1].strip()
     docker_compose_args = DOCKER_COMPOSE_ARGS
-    if args.ssl or (ssl_key and ssl_cert):
+    if (ssl_key and ssl_cert):
         docker_compose_args +=" --file docker-compose.ssl.yml"
-    if args.ssl and not (ssl_key and ssl_cert):
-        print("ERROR: SSL is enabled but WIS2BOX_SSL_KEY and WIS2BOX_SSL_CERT are not set in wis2box.env")
-        exit(1)
     # if you selected a bunch of them, default to all
     containers = "" if not args.args else ' '.join(args.args)
 
@@ -264,13 +293,9 @@ def make(args) -> None:
 
     if args.command == "config":
         run(split(f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} config'))
-    elif args.command == "build":
-        build_local_images()
-        update_docker_images(LOCAL)
     elif args.command in ["up", "start", "start-dev"]:
-        # if docker-compose.yml does not exist, run update to create it
         if not os.path.exists('docker-compose.yml'):
-            update_docker_images(LATEST)
+            update_docker_images()
         run(split(
             'docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions'),
             silence_stderr=True)
@@ -282,6 +307,22 @@ def make(args) -> None:
                 run(split(f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} --file docker-compose.dev.yml up -d'))
             else:
                 run(split(f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} up -d'))
+    elif args.command in ["update", "update-local-build", "update-latest-tags"]:
+        if args.command == "update-local-build":
+            update_docker_images(use_local_build=True)
+        elif args.command == "update-use-latest":
+            update_docker_images(use_local_build=False, use_latest=True)
+        else:
+            update_docker_images()
+        # restart all containers
+        run(split(
+                f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} down --remove-orphans'))
+        run(split(
+                f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} up -d'))
+        # perform cleanup of images after update, unless updating local build
+        if not args.command == "update-local-build":
+            remove_docker_images('wmoim/wis2*')
+            remove_docker_images('ghcr.io/wmo-im/wis2*')
     elif args.command == "execute":
         run(['docker', 'exec', '-i', 'wis2box-management', 'sh', '-c', containers])
     elif args.command == "login":
@@ -297,24 +338,14 @@ def make(args) -> None:
         else:
             run(split(
                 f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} down --remove-orphans {containers}'))
-    elif args.command == "update":
-        update_docker_images(LATEST)
-        # restart all containers
-        run(split(
-                f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} down --remove-orphans'))
-        run(split(
-                f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} up -d'))
-        # prune dangling images
-        _ = run(split('docker images --filter dangling=true -q --no-trunc'))
-        run(split(f'docker rmi {_}'))
     elif args.command == "prune":
         run(split('docker builder prune -f'))
         run(split('docker container prune -f'))
         run( split('docker volume prune -f'))
-        _ = run(split('docker images --filter dangling=true -q --no-trunc'))
-        run(split(f'docker rmi {_}'))
-        _ = run(split('docker ps -a -q'))
-        run(split(f'docker rm {_}'))
+        # prune any unused images starting with wmoim/wis2
+        remove_docker_images('wmoim/wis2*')
+        # prune any unused images starting with ghcr.io/wmo-im/wis2
+        remove_docker_images('ghcr.io/wmo-im/wis2*')
     elif args.command == "restart":
         if containers:
             run(split(

From 1db610039632bdb7d67954eafc2fe83a237f6466 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Mon, 13 Jan 2025 16:40:10 +0100
Subject: [PATCH 05/17] update gitignore

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index b9f3b024..e24b1508 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,4 @@ tests/data/.ssh/id_rsa
 tests/data/.ssh/id_rsa.pub
 
 docker-compose.yml
+docker-compose.yml.bak

From 0166f93072e4c43271380bf912d846568447b22b Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Mon, 13 Jan 2025 16:53:49 +0100
Subject: [PATCH 06/17] update tests

---
 .github/workflows/tests-docker.yml | 5 ++++-
 .github/workflows/zaproxy.yml      | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/tests-docker.yml b/.github/workflows/tests-docker.yml
index 07ee6581..41afca7b 100644
--- a/.github/workflows/tests-docker.yml
+++ b/.github/workflows/tests-docker.yml
@@ -43,9 +43,12 @@ jobs:
         sed -i "s/localhost/$IP/g" wis2box.env
         cat wis2box.env
         python3 wis2box-ctl.py config
-    - name: build wis2box and check status ⚙️
+    - name: build wis2box locally ⚙️
       run: |
         python3 wis2box-ctl.py update-local-build
+    - name: start containers ⚙️
+      run: |
+        python3 wis2box-ctl.py start
         python3 wis2box-ctl.py status -a
     - name: show environment and check collections exist ⚙️
       run: |
diff --git a/.github/workflows/zaproxy.yml b/.github/workflows/zaproxy.yml
index a8796082..f9c43cbf 100644
--- a/.github/workflows/zaproxy.yml
+++ b/.github/workflows/zaproxy.yml
@@ -11,7 +11,7 @@ jobs:
     - name: build and start containers using tests/test.env ⚙️
       run: |
         cp tests/test.env wis2box.env
-        python3 wis2box-ctl.py build
+        python3 wis2box-ctl.py update-local-build
         python3 wis2box-ctl.py start
         python3 wis2box-ctl.py status -a
         sleep 30

From f8593027cf1d64463e907fa70239f7294bcc7b2c Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Mon, 13 Jan 2025 17:11:48 +0100
Subject: [PATCH 07/17] updated command for quickstart

---
 docs/source/reference/quickstart.rst | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/docs/source/reference/quickstart.rst b/docs/source/reference/quickstart.rst
index da6c6c05..d9236571 100644
--- a/docs/source/reference/quickstart.rst
+++ b/docs/source/reference/quickstart.rst
@@ -24,12 +24,11 @@ To run with the 'quickstart' configuration, copy this file to ``wis2box.env`` in
     cp tests/test.env wis2box.env
 
 
-Build and update wis2box:
+Build and update wis2box from the source code:
 
 .. code-block:: bash
 
-    python3 wis2box-ctl.py build
-    python3 wis2box-ctl.py update
+    python3 wis2box-ctl.py update-local-build
 
 
 Start wis2box and login to the wis2box-management container:

From 46c4326a3d8effd9bb2262fe9f244668e94a98aa Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Tue, 14 Jan 2025 10:47:21 +0100
Subject: [PATCH 08/17] don't manage wis2downloader in wis2box-release as
 requested by David Berry

---
 docker-compose.base.yml |  2 +-
 wis2box-ctl.py          | 14 +++++++-------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/docker-compose.base.yml b/docker-compose.base.yml
index 1758dc78..c755ec92 100644
--- a/docker-compose.base.yml
+++ b/docker-compose.base.yml
@@ -155,7 +155,7 @@ services:
 
   wis2downloader:
     container_name: wis2downloader
-    image: wmoim/wis2downloader:latest
+    image: wmoim/wis2downloader:v0.3.2
     restart: always
     env_file:
       - wis2box.env
diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index bef73c0b..8616322f 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -194,7 +194,7 @@ def update_docker_images(use_local_build: bool = False, use_latest: bool = False
         lines = f.readlines()
         with open('docker-compose.yml', 'w') as f:
             for line in lines:
-                if 'image: wmoim/' in line:
+                if 'image: wmoim/wis2box' in line:
                     image = line.split('wmoim/')[1].split(':')[0]
                     
                     # determine the tag to use
@@ -321,8 +321,8 @@ def make(args) -> None:
                 f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} up -d'))
         # perform cleanup of images after update, unless updating local build
         if not args.command == "update-local-build":
-            remove_docker_images('wmoim/wis2*')
-            remove_docker_images('ghcr.io/wmo-im/wis2*')
+            remove_docker_images('wmoim/wis2box*')
+            remove_docker_images('ghcr.io/wmo-im/wis2box*')
     elif args.command == "execute":
         run(['docker', 'exec', '-i', 'wis2box-management', 'sh', '-c', containers])
     elif args.command == "login":
@@ -342,10 +342,10 @@ def make(args) -> None:
         run(split('docker builder prune -f'))
         run(split('docker container prune -f'))
         run( split('docker volume prune -f'))
-        # prune any unused images starting with wmoim/wis2
-        remove_docker_images('wmoim/wis2*')
-        # prune any unused images starting with ghcr.io/wmo-im/wis2
-        remove_docker_images('ghcr.io/wmo-im/wis2*')
+        # prune any unused images starting with wmoim/wis2box
+        remove_docker_images('wmoim/wis2box*')
+        # prune any unused images starting with ghcr.io/wmo-im/wis2box
+        remove_docker_images('ghcr.io/wmo-im/wis2box*')
     elif args.command == "restart":
         if containers:
             run(split(

From 3018a20cb5165e687383c70a01ef3ac30f14af8e Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Tue, 14 Jan 2025 10:54:00 +0100
Subject: [PATCH 09/17] set ref of wis2downloader back to original

---
 docker-compose.base.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docker-compose.base.yml b/docker-compose.base.yml
index c755ec92..67b75d03 100644
--- a/docker-compose.base.yml
+++ b/docker-compose.base.yml
@@ -155,7 +155,7 @@ services:
 
   wis2downloader:
     container_name: wis2downloader
-    image: wmoim/wis2downloader:v0.3.2
+    image: ghcr.io/wmo-im/wis2downloader:v0.3.2
     restart: always
     env_file:
       - wis2box.env

From bfefbf691f26540c33b6c3724d7afcbf729972d5 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Tue, 14 Jan 2025 17:34:18 +0100
Subject: [PATCH 10/17] use github release to determine tags ...

---
 docker-compose.base.yml |  14 ++---
 wis2box-ctl.py          | 130 +++++++++++++++++-----------------------
 wis2box.version         |   1 +
 3 files changed, 63 insertions(+), 82 deletions(-)
 create mode 100644 wis2box.version

diff --git a/docker-compose.base.yml b/docker-compose.base.yml
index 67b75d03..8951accb 100644
--- a/docker-compose.base.yml
+++ b/docker-compose.base.yml
@@ -13,7 +13,7 @@ services:
 
   wis2box-ui:
     container_name: wis2box-ui
-    image: wmoim/wis2box-ui:latest
+    image: ghcr.io/wmo-im/wis2box-ui:latest
     restart: always
     env_file:
       - wis2box.env
@@ -22,14 +22,14 @@ services:
 
   wis2box-webapp:
     container_name: wis2box-webapp
-    image: wmoim/wis2box-webapp:latest
+    image: ghcr.io/wmo-im/wis2box-webapp:latest
     env_file:
       - wis2box.env
     restart: always
 
   wis2box-api:
     container_name: wis2box-api
-    image: wmoim/wis2box-api:latest
+    image: ghcr.io/wmo-im/wis2box-api:latest
     restart: always
     env_file:
       - wis2box.env
@@ -104,7 +104,7 @@ services:
 
   mosquitto:
     container_name: mosquitto
-    image: wmoim/wis2box-broker:local
+    image: ghcr.io/wmo-im/wis2box-broker:latest
     restart: always
     env_file:
       - wis2box.env
@@ -113,7 +113,7 @@ services:
 
   wis2box-management:
     container_name: wis2box-management
-    image: wmoim/wis2box-management:local
+    image: ghcr.io/wmo-im/wis2box-management:latest
     mem_limit: 1g
     memswap_limit: 1g
     restart: always
@@ -135,7 +135,7 @@ services:
     restart: unless-stopped
     env_file:
       - wis2box.env
-    image: wmoim/wis2box-mqtt-metrics-collector:local
+    image: ghcr.io/wmo-im/wis2box-mqtt-metrics-collector:latest
     depends_on:
       - mosquitto
       - wis2box-management
@@ -144,7 +144,7 @@ services:
 
   wis2box-auth:
     container_name: wis2box-auth
-    image: wmoim/wis2box-auth:latest
+    image: ghcr.io/wmo-im/wis2box-auth:latest
     restart: always
     env_file:
       - wis2box.env
diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index 8616322f..3728e3b0 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -27,6 +27,11 @@
 import subprocess
 import shutil
 
+# read wis2box.version file if it exists
+WIS2BOX_VERSION = 'LOCAL_BUILD'
+if os.path.exists('wis2box.version'):
+    with open('wis2box.version', 'r') as f:
+        WIS2BOX_VERSION = f.read().strip()
 
 if subprocess.call(['docker', 'compose'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) > 0:
     DOCKER_COMPOSE_COMMAND = 'docker-compose'
@@ -63,9 +68,7 @@
     'start-dev',
     'status',
     'stop',
-    'update',
-    'update-local-build',
-    'update-latest-tags'
+    'update'
 ]
 
 parser.add_argument('command',
@@ -119,63 +122,52 @@ def build_local_images() -> None:
     """
     for image in LOCAL_IMAGES:
         print(f'Building {image}')
-        run(split(f'docker build -t wmoim/{image}:local {image}'))
-
+        run(split(f'docker build -t ghcr.io/wmo-im/{image}:local {image}'))
     return None
-
-def get_latest_image_tag(image: str) -> str:
+    
+def get_latest_release_tag(image: str, major_release: str = '') -> str:
     """
-    list image tags by querying docker hub api
-    skip the 'latest' tag
-    return the most recent tag
 
-    :param image: required, string. Name of the image.
+    Fetches the latest release tag for a GitHub repository.
+    
+    :param image: required, string. The name of the image repository.
+    :param major_release: required, string. The major release version.
 
-    :returns: string. The most recent tag.
+    :return: The latest release tag or an error message if not found.
     """
-
-    url = f"https://hub.docker.com/v2/repositories/wmoim/{image}/tags/"
-    tags = []
-    try:
-        # Paginate through results to collect all tags
-        while url:
-            response = requests.get(url)
-            response.raise_for_status()  # Raise an error for HTTP failures
-            data = response.json()
-            tags.extend([
-                tag['name'] for tag in data.get('results', [])
-                if tag['name'] != 'latest'  # Skip 'latest' tag
-            ])
-            url = data.get('next')  # Get the next page URL
-    except requests.RequestException as e:
-        raise RuntimeError(f"Failed to fetch tags for image '{image}': {e}")
-
-    if not tags:
-        raise ValueError(f"No valid tags found for image '{image}'")
-    else:
-        print(f"Found {len(tags)} tags for image '{image}: {tags}'")
-
-    # define a function to sort tags by version number
-    def tag_sort_key(tag):
-        # Extract numeric and non-numeric parts
-        parts = re.split(r'(\d+)', tag)  # Split into numeric and non-numeric segments
-        return [
-            int(part) if part.isdigit() else 'ZZZZZZZZZZ'  # Use a large number for non-numeric parts
-            for part in parts
-        ]
-
-    # Sort tags by version number in descending order
-    tags.sort(key=tag_sort_key, reverse=True)
-    return tags[0]
+    url = f'https://api.github.com/repos/wmo-im/{image}/releases'
+    headers = {'Accept': 'application/vnd.github.v3+json'}
     
+    print(f'Fetching latest release tag from GitHub for {image} using wis2box-version={major_release}')
 
-def update_docker_images(use_local_build: bool = False, use_latest: bool = False) -> None:
+    options = []
+    try:
+        response = requests.get(url, headers=headers)
+        if response.status_code == 200:
+            releases = response.json()
+            for release in releases:
+                print(release['tag_name'])
+                if major_release in release['tag_name']:
+                    options.append(release['tag_name'])
+        else:
+            print(f'Error fetching latest release tag for {image}: {response.status_code}')
+    except requests.exceptions.RequestException as e:
+        print(f'Error fetching latest release tag for {image}: {e}')
+
+    # throw error if options is empty
+    if not options:
+        raise ValueError(f'No release tags found for {image} with major release {major_release}')
+
+    # sort descending and return the first element
+    options.sort(reverse=True)
+    return options[0]
+            
+def update_docker_images(wis2box_version: str) -> None:
     """
     Write docker-compose.yml using docker-compose.base.yml as base
     
 
-    use_local_build: required, boolean. If True, build local images
-    use_tags: required, boolean. If True, pull last tagged release for image. If False, use local images or tag='latest' # noqa
+    :param wis2box_version: required, string. The version of wis2box to use.
     
     :returns: None.
     """
@@ -184,38 +176,31 @@ def update_docker_images(use_local_build: bool = False, use_latest: bool = False
         print('Backing up current docker-compose.yml to docker-compose.yml.bak')
         shutil.copy('docker-compose.yml', 'docker-compose.yml.bak')
 
-    if use_local_build:
+    if wis2box_version == 'LOCAL_BUILD':
         print('Building local images')
         build_local_images()
 
-    print('Updating docker-compose.yml')
+    print(f'Updating docker-compose.yml for wis2box_version={wis2box_version}')
 
     with open('docker-compose.base.yml', 'r') as f:
         lines = f.readlines()
         with open('docker-compose.yml', 'w') as f:
             for line in lines:
-                if 'image: wmoim/wis2box' in line:
-                    image = line.split('wmoim/')[1].split(':')[0]
-                    
-                    # determine the tag to use
+                if 'image: ghcr.io/wmo-im/wis2box' in line:
+                    image = line.split('ghcr.io/wmo-im/')[1].split(':')[0]
                     tag = 'latest'
-                    if image in LOCAL_IMAGES and use_local_build:
+                    if image in LOCAL_IMAGES and wis2box_version == 'LOCAL_BUILD':
                         tag = 'local'
-                    elif image not in LOCAL_IMAGES and use_local_build:
-                        tag = 'latest'
-                    elif not use_latest:
-                        print(f'Get latest image tag for {image}')
-                        tag = get_latest_image_tag(image)
-                    
-                    # pull the image if it is not local, o
+                    elif wis2box_version != 'LOCAL_BUILD':
+                        tag = get_latest_release_tag(image, wis2box_version)
+                    # pull the image if it is not local
                     if tag != 'local':
-                        print(f'Pulling wmoim/{image}:{tag}')
+                        print(f'Pulling ghcr.io/wmo-im/{image}:{tag}')
                         # pull the latest tag for the image
-                        run(split(f'docker pull wmoim/{image}:{tag}'))
-                    
+                        run(split(f'docker pull ghcr.io/wmo-im/{image}:{tag}'))
                     # update the image tag in the docker-compose.yml
                     print(f'Set {image} to {tag}')
-                    f.write(f'    image: wmoim/{image}:{tag}\n')
+                    f.write(f'    image: ghcr.io/wmo-im/{image}:{tag}\n')
                 else:
                     f.write(line)
         print('docker-compose.yml updated')
@@ -307,20 +292,15 @@ def make(args) -> None:
                 run(split(f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} --file docker-compose.dev.yml up -d'))
             else:
                 run(split(f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} up -d'))
-    elif args.command in ["update", "update-local-build", "update-latest-tags"]:
-        if args.command == "update-local-build":
-            update_docker_images(use_local_build=True)
-        elif args.command == "update-use-latest":
-            update_docker_images(use_local_build=False, use_latest=True)
-        else:
-            update_docker_images()
+    elif args.command in ["update"]:
+        update_docker_images(WIS2BOX_VERSION)
         # restart all containers
         run(split(
                 f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} down --remove-orphans'))
         run(split(
                 f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} up -d'))
         # perform cleanup of images after update, unless updating local build
-        if not args.command == "update-local-build":
+        if not WIS2BOX_VERSION == 'LOCAL_BUILD':
             remove_docker_images('wmoim/wis2box*')
             remove_docker_images('ghcr.io/wmo-im/wis2box*')
     elif args.command == "execute":
diff --git a/wis2box.version b/wis2box.version
new file mode 100644
index 00000000..452a44d4
--- /dev/null
+++ b/wis2box.version
@@ -0,0 +1 @@
+LOCAL_BUILD
\ No newline at end of file

From dbb016970f11cfc845b754b062c5d90fd9d4ef99 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Tue, 14 Jan 2025 17:35:51 +0100
Subject: [PATCH 11/17] zaproxy fix for updated PR

---
 .github/workflows/zaproxy.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/zaproxy.yml b/.github/workflows/zaproxy.yml
index f9c43cbf..a016435f 100644
--- a/.github/workflows/zaproxy.yml
+++ b/.github/workflows/zaproxy.yml
@@ -11,7 +11,7 @@ jobs:
     - name: build and start containers using tests/test.env ⚙️
       run: |
         cp tests/test.env wis2box.env
-        python3 wis2box-ctl.py update-local-build
+        python3 wis2box-ctl.py update
         python3 wis2box-ctl.py start
         python3 wis2box-ctl.py status -a
         sleep 30

From 5823fc36e3dec6315e1dcd885fb771a145d560c1 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Tue, 14 Jan 2025 17:37:43 +0100
Subject: [PATCH 12/17] commit update tests-docker

---
 .github/workflows/tests-docker.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/tests-docker.yml b/.github/workflows/tests-docker.yml
index 41afca7b..3a7a3bb4 100644
--- a/.github/workflows/tests-docker.yml
+++ b/.github/workflows/tests-docker.yml
@@ -45,7 +45,7 @@ jobs:
         python3 wis2box-ctl.py config
     - name: build wis2box locally ⚙️
       run: |
-        python3 wis2box-ctl.py update-local-build
+        python3 wis2box-ctl.py update
     - name: start containers ⚙️
       run: |
         python3 wis2box-ctl.py start

From cd9aa8f15093cea15c0cd4dca23013b9eccb0185 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Tue, 14 Jan 2025 17:42:11 +0100
Subject: [PATCH 13/17] rename major_release to wis2box_version for consistency

---
 wis2box-ctl.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index 3728e3b0..31bc067f 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -125,20 +125,20 @@ def build_local_images() -> None:
         run(split(f'docker build -t ghcr.io/wmo-im/{image}:local {image}'))
     return None
     
-def get_latest_release_tag(image: str, major_release: str = '') -> str:
+def get_latest_release_tag(image: str, wis2box_version: str = '') -> str:
     """
 
     Fetches the latest release tag for a GitHub repository.
     
     :param image: required, string. The name of the image repository.
-    :param major_release: required, string. The major release version.
+    :param wis2box_version: required, string. The major release version.
 
     :return: The latest release tag or an error message if not found.
     """
     url = f'https://api.github.com/repos/wmo-im/{image}/releases'
     headers = {'Accept': 'application/vnd.github.v3+json'}
     
-    print(f'Fetching latest release tag from GitHub for {image} using wis2box-version={major_release}')
+    print(f'Fetching latest release tag from GitHub for {image} using wis2box-version={wis2box_version}')
 
     options = []
     try:
@@ -147,7 +147,7 @@ def get_latest_release_tag(image: str, major_release: str = '') -> str:
             releases = response.json()
             for release in releases:
                 print(release['tag_name'])
-                if major_release in release['tag_name']:
+                if wis2box_version in release['tag_name']:
                     options.append(release['tag_name'])
         else:
             print(f'Error fetching latest release tag for {image}: {response.status_code}')
@@ -156,7 +156,7 @@ def get_latest_release_tag(image: str, major_release: str = '') -> str:
 
     # throw error if options is empty
     if not options:
-        raise ValueError(f'No release tags found for {image} with major release {major_release}')
+        raise ValueError(f'No release tags found for {image} with major release {wis2box_version}')
 
     # sort descending and return the first element
     options.sort(reverse=True)

From 359b5d4744e94de6045c95718e1fcfeac8142db7 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Thu, 16 Jan 2025 14:47:20 +0100
Subject: [PATCH 14/17] use separate release referencing images

---
 .github/workflows/tests-docker.yml |  3 +-
 wis2box-ctl.py                     | 96 ++++++++++++++++++++----------
 2 files changed, 66 insertions(+), 33 deletions(-)

diff --git a/.github/workflows/tests-docker.yml b/.github/workflows/tests-docker.yml
index 3a7a3bb4..dcdaf6eb 100644
--- a/.github/workflows/tests-docker.yml
+++ b/.github/workflows/tests-docker.yml
@@ -36,10 +36,11 @@ jobs:
         mkdir -p tests/data/downloads
         chown -R $(whoami):docker tests/data/downloads
         chmod -R 775 tests/data/downloads
-    - name: setup wis2box configuration, replace localhost with IP on host 📦
+    - name: setup wis2box configuration, replace localhost with IP on host, use LOCAL_BUILD as wis2box.version 📦
       run: |
         export IP=$(hostname -I | awk '{print $1}')
         cp tests/test.env wis2box.env
+        echo "LOCAL_BUILD" > wis2box.version
         sed -i "s/localhost/$IP/g" wis2box.env
         cat wis2box.env
         python3 wis2box-ctl.py config
diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index 31bc067f..d25e67bf 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -22,11 +22,12 @@
 
 import argparse
 import os
-import re
 import requests
 import subprocess
 import shutil
 
+from packaging.version import Version
+
 # read wis2box.version file if it exists
 WIS2BOX_VERSION = 'LOCAL_BUILD'
 if os.path.exists('wis2box.version'):
@@ -92,9 +93,9 @@
 args = parser.parse_args()
 
 LOCAL_IMAGES = [
-    'wis2box-management',
-    'wis2box-broker',
-    'wis2box-mqtt-metrics-collector'
+    'ghcr.io/wmo-im/wis2box-management',
+    'ghcr.io/wmo-im/wis2box-broker',
+    'ghcr.io/wmo-im/wis2box-mqtt-metrics-collector'
 ]
 
 def remove_docker_images(filter: str) -> None:
@@ -125,49 +126,76 @@ def build_local_images() -> None:
         run(split(f'docker build -t ghcr.io/wmo-im/{image}:local {image}'))
     return None
     
-def get_latest_release_tag(image: str, wis2box_version: str = '') -> str:
+def get_resolved_version(base_version: str) -> str:
     """
+    Fetches the latest release tag for the wis2box-images repository.
 
-    Fetches the latest release tag for a GitHub repository.
-    
-    :param image: required, string. The name of the image repository.
-    :param wis2box_version: required, string. The major release version.
+    :rbase_version: required, string. The major release version.
 
     :return: The latest release tag or an error message if not found.
     """
-    url = f'https://api.github.com/repos/wmo-im/{image}/releases'
+
+    # NOTE using maaikelimper/wis2box-images for demo purposes, should be wmo-im/wis2box-images
+    url = f'https://api.github.com/repos/maaikelimper/wis2box-images/releases'
     headers = {'Accept': 'application/vnd.github.v3+json'}
     
-    print(f'Fetching latest release tag from GitHub for {image} using wis2box-version={wis2box_version}')
-
     options = []
     try:
         response = requests.get(url, headers=headers)
         if response.status_code == 200:
             releases = response.json()
             for release in releases:
-                print(release['tag_name'])
-                if wis2box_version in release['tag_name']:
+                if base_version in release['tag_name']:
                     options.append(release['tag_name'])
         else:
-            print(f'Error fetching latest release tag for {image}: {response.status_code}')
+            print(f'Error fetching latest release tag for {base_version}: {response.status_code}')
     except requests.exceptions.RequestException as e:
-        print(f'Error fetching latest release tag for {image}: {e}')
+        print(f'Error fetching latest release tag for {base_version}: {e}')
 
     # throw error if options is empty
     if not options:
-        raise ValueError(f'No release tags found for {image} with major release {wis2box_version}')
+        raise ValueError(f'No wis2box-release found matching wis2box-version={base_version}')
 
-    # sort descending and return the first element
-    options.sort(reverse=True)
-    return options[0]
+    # Use semantic versioning for sorting
+    sorted_versions = sorted(options, key=lambda v: Version(v), reverse=True)
+
+    resolved_version = sorted_versions[0]
+    return resolved_version
+
+def get_latest_release_tag(image: str, resolved_version: str = '') -> str:
+    """
+
+    Fetches the latest release tag for a GitHub repository.
+    
+    :param image: required, string. The name of the image repository.
+    :param base_version: required, string. The major release version.
+
+    :return: The latest release tag or an error message if not found.
+    """
+    
+    # look up the image tag for the release by downloading the wis2box-images.json file
+    # NOTE using maaikelimper/wis2box-images for demo purposes, should be wmo-im/wis2box-images
+    url = f'https://github.com/maaikelimper/wis2box-images/releases/download/{resolved_version}/wis2box-images.json'
+    try:
+        response = requests.get(url)
+        if response.status_code == 200:
+            images = response.json()
+            if image in images:
+                return images[image]
+        else:
+            print(f'Error fetching image tag for {image} from {url}: {response.status_code}')
+    except requests.exceptions.RequestException as e:
+        print(f'Error fetching image tag for {image} from {url}: {e}')
+
+    # throw error if image tag is not found
+    raise ValueError(f'No image tag found for {image} in {url}')
             
-def update_docker_images(wis2box_version: str) -> None:
+def update_docker_images(base_version: str) -> None:
     """
     Write docker-compose.yml using docker-compose.base.yml as base
     
 
-    :param wis2box_version: required, string. The version of wis2box to use.
+    :param base_version: required, string. The version of wis2box to use.
     
     :returns: None.
     """
@@ -176,31 +204,35 @@ def update_docker_images(wis2box_version: str) -> None:
         print('Backing up current docker-compose.yml to docker-compose.yml.bak')
         shutil.copy('docker-compose.yml', 'docker-compose.yml.bak')
 
-    if wis2box_version == 'LOCAL_BUILD':
+    if base_version == 'LOCAL_BUILD':
         print('Building local images')
         build_local_images()
 
-    print(f'Updating docker-compose.yml for wis2box_version={wis2box_version}')
+    resolved_version = base_version
+    if base_version != 'LOCAL_BUILD':
+        resolved_version = get_resolved_version(base_version)
+
+    print(f'Updating docker-compose.yml, using base_version={resolved_version}')
 
     with open('docker-compose.base.yml', 'r') as f:
         lines = f.readlines()
         with open('docker-compose.yml', 'w') as f:
             for line in lines:
-                if 'image: ghcr.io/wmo-im/wis2box' in line:
-                    image = line.split('ghcr.io/wmo-im/')[1].split(':')[0]
+                if 'image: ' in line:
+                    image = line.split('image: ')[1].split(':')[0]
                     tag = 'latest'
-                    if image in LOCAL_IMAGES and wis2box_version == 'LOCAL_BUILD':
+                    if image in LOCAL_IMAGES and base_version == 'LOCAL_BUILD':
                         tag = 'local'
-                    elif wis2box_version != 'LOCAL_BUILD':
-                        tag = get_latest_release_tag(image, wis2box_version)
+                    elif base_version != 'LOCAL_BUILD':
+                        tag = get_latest_release_tag(image, resolved_version)
                     # pull the image if it is not local
                     if tag != 'local':
-                        print(f'Pulling ghcr.io/wmo-im/{image}:{tag}')
+                        print(f'Pulling {image}:{tag}')
                         # pull the latest tag for the image
-                        run(split(f'docker pull ghcr.io/wmo-im/{image}:{tag}'))
+                        run(split(f'docker pull {image}:{tag}'))
                     # update the image tag in the docker-compose.yml
                     print(f'Set {image} to {tag}')
-                    f.write(f'    image: ghcr.io/wmo-im/{image}:{tag}\n')
+                    f.write(f'    image: {image}:{tag}\n')
                 else:
                     f.write(line)
         print('docker-compose.yml updated')

From 5e7e2c026f6f08bfcb7622019a7b7a4e88ee531d Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Thu, 16 Jan 2025 15:31:36 +0100
Subject: [PATCH 15/17] fix build images

---
 wis2box-ctl.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index d25e67bf..3c23e2c6 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -121,9 +121,11 @@ def build_local_images() -> None:
 
     :returns: None.
     """
+
     for image in LOCAL_IMAGES:
         print(f'Building {image}')
-        run(split(f'docker build -t ghcr.io/wmo-im/{image}:local {image}'))
+        context = image.split('/')[-1]
+        run(split(f'docker build -t {image}:local {context}'))
     return None
     
 def get_resolved_version(base_version: str) -> str:

From 0b0748abb743084a74de8abdc5dadaa34dc86f47 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Thu, 16 Jan 2025 15:56:37 +0100
Subject: [PATCH 16/17] fix update-flow for LOCAL_BUILD

---
 wis2box-ctl.py | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/wis2box-ctl.py b/wis2box-ctl.py
index 3c23e2c6..1405bfd2 100755
--- a/wis2box-ctl.py
+++ b/wis2box-ctl.py
@@ -177,7 +177,12 @@ def get_latest_release_tag(image: str, resolved_version: str = '') -> str:
     
     # look up the image tag for the release by downloading the wis2box-images.json file
     # NOTE using maaikelimper/wis2box-images for demo purposes, should be wmo-im/wis2box-images
-    url = f'https://github.com/maaikelimper/wis2box-images/releases/download/{resolved_version}/wis2box-images.json'
+    github_repo = 'maaikelimper/wis2box-images'
+
+    url = f'https://github.com/{github_repo}/releases/download/{resolved_version}/wis2box-images.json'
+    if resolved_version == 'LOCAL_BUILD':
+        # use the version of images in main branch
+        url = f'https://raw.githubusercontent.com/{github_repo}/refs/heads/main/wis2box-images.json'
     try:
         response = requests.get(url)
         if response.status_code == 200:
@@ -222,10 +227,10 @@ def update_docker_images(base_version: str) -> None:
             for line in lines:
                 if 'image: ' in line:
                     image = line.split('image: ')[1].split(':')[0]
-                    tag = 'latest'
+                    tag = ''
                     if image in LOCAL_IMAGES and base_version == 'LOCAL_BUILD':
                         tag = 'local'
-                    elif base_version != 'LOCAL_BUILD':
+                    else:
                         tag = get_latest_release_tag(image, resolved_version)
                     # pull the image if it is not local
                     if tag != 'local':

From 781996d5f83c511d3d11553bef4528a18aff54f7 Mon Sep 17 00:00:00 2001
From: Maaike <maaike.limper@gmail.com>
Date: Thu, 16 Jan 2025 16:40:41 +0100
Subject: [PATCH 17/17] update zap rules, ignore new warning with low risk

---
 .zap/rules.tsv | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.zap/rules.tsv b/.zap/rules.tsv
index fcdad969..dc50cafa 100644
--- a/.zap/rules.tsv
+++ b/.zap/rules.tsv
@@ -22,3 +22,4 @@
 10110	IGNORE	Dangerous JS Functions	Low
 10105	IGNORE	Authentication Credentials Captured	Medium
 10003	IGNORE	Vulnerable JS Library	Medium
+90004	IGNORE	Insufficient Site Isolation Against Spectre Vulnerability	Low