From 4fe9188b6a2758af6735ccc463780c04c8a1c6e3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 29 Oct 2025 10:53:15 +0100 Subject: [PATCH 1/5] test .deb update --- Taskfile.yml | 42 +++++++++++++++++++++++++++++ test.Dockerfile | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 test.Dockerfile diff --git a/Taskfile.yml b/Taskfile.yml index 06e54550..328f6b72 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -12,6 +12,9 @@ vars: RUNNER_VERSION: "0.5.0" VERSION: # if version is not passed we hack the semver by encoding the commit as pre-release sh: echo "${VERSION:-0.0.0-$(git rev-parse --short HEAD)}" + NEW_PACKAGE: + sh: ls -1 ./build/arduino-app-cli_*.deb 2>/dev/null | head -n 1 + GITHUB_TOKEN_FILE: ./github_token.txt tasks: init: @@ -123,6 +126,45 @@ tasks: echo "Examples successfully cloned." silent: false + build-image: + desc: "Builds the mock-repo Docker image (requires GITHUB_TOKEN_FILE)" + deps: [build-deb] + vars: + PKG_PATH: '{{.NEW_PACKAGE}}' + cmds: + # --- MODIFIED --- + # Check for both the package and the token file + - | + if [ ! -f "{{.GITHUB_TOKEN_FILE}}" ]; then + echo "Error: GitHub token file not found at {{.GITHUB_TOKEN_FILE}}" + echo "Please create this file and add your GitHub PAT to it." + exit 1 + fi + - | + echo "Using package: {{.PKG_PATH}}" + echo "Using GitHub token from: {{.GITHUB_TOKEN_FILE}}" + + # Enable BuildKit and pass the secret + DOCKER_BUILDKIT=1 docker build \ + --secret id=github_token,src={{.GITHUB_TOKEN_FILE}} \ + --build-arg NEW_PACKAGE_PATH={{.PKG_PATH}} \ + -t newdeb \ + -f test.Dockerfile . + status: + - '[[ -f "{{.PKG_PATH}}" ]]' + - '[[ -f "{{.DOCKERFILE_NAME}}" ]]' + # Re-build if token file changes + - '[[ -f "{{.GITHUB_TOKEN_FILE}}" ]]' + + test-deb: + desc: Test the debian package locally + deps: + - build-deb + vars: + VERSION: "0.6.3" + cmds: + - docker build --no-cache -t mock-apt-repo -f test.Dockerfile . + arduino-app-cli:build:local: desc: "Build the arduino-app-cli locally" cmds: diff --git a/test.Dockerfile b/test.Dockerfile new file mode 100644 index 00000000..d2fa9dfb --- /dev/null +++ b/test.Dockerfile @@ -0,0 +1,71 @@ +# 1. Use Debian base +FROM debian:trixie + +ENV DEBIAN_FRONTEND=noninteractive + +# 2. Install necessary tools +RUN apt update && apt install -y \ + dpkg-dev \ + apt-utils \ + adduser \ + && rm -rf /var/lib/apt/lists/* + +# 3. Symlink addgroup for minimal system scripts +RUN ln -s /usr/sbin/addgroup /usr/bin/addgroup || true + +# 4. Build args for parameterization +ARG OLD_PACKAGE_PATH=build/old_package +ARG NEW_PACKAGE_PATH=build +ARG APP_PACKAGE_NAME=arduino-app-cli +ARG ROUTER_PACKAGE_NAME=arduino-router +ARG ARCH=arm64 +ARG VERSION=0.6.3 + +# 5. Copy packages dynamically +COPY ${OLD_PACKAGE_PATH}/${APP_PACKAGE_NAME}*.deb /tmp/old_app.deb +COPY ${NEW_PACKAGE_PATH}/${APP_PACKAGE_NAME}*.deb /tmp/new_app.deb +COPY ${NEW_PACKAGE_PATH}/${ROUTER_PACKAGE_NAME}*.deb /tmp/new_router.deb + +# 6. Install old package + router dependency +RUN apt update && apt install -y \ + /tmp/old_app.deb \ + /tmp/new_router.deb \ + && rm /tmp/old_app.deb + +# 7. Setup local APT repo with new packages +RUN mkdir -p /var/www/html/myrepo/dists/trixie/main/binary-${ARCH} + +# Rename new packages to match their real package/version/arch +RUN mv /tmp/new_app.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/${APP_PACKAGE_NAME}_${VERSION}_${ARCH}.deb +RUN mv /tmp/new_router.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/${ROUTER_PACKAGE_NAME}_0.6.2-1_${ARCH}.deb + +# 8. Generate Packages.gz metadata +WORKDIR /var/www/html/myrepo +RUN dpkg-scanpackages dists/trixie/main/binary-arm64 /dev/null | gzip -9c > dists/trixie/main/binary-arm64/Packages.gz +WORKDIR / + +# 9. Configure local APT repo +RUN echo "deb [trusted=yes arch=${ARCH}] file:/var/www/html/myrepo trixie main" \ + > /etc/apt/sources.list.d/my-mock-repo.list + +# 10. Fix home dir for arduino user (optional) +RUN usermod -s /bin/bash arduino || true +RUN mkdir -p /home/arduino && chown -R arduino:arduino /home/arduino + +# 11. Entrypoint: show upgrade availability +RUN echo '#!/bin/bash\n\ +set -e\n\ +echo "--- Updating APT ---"\n\ +apt update\n\ +echo "--- Installed version ---"\n\ +dpkg -l | grep ${APP_PACKAGE_NAME} || true\n\ +echo "--- Upgrade candidate ---"\n\ +apt-cache policy ${APP_PACKAGE_NAME}\n\ +echo "--- Available upgrades ---"\n\ +apt list --upgradable | grep ${APP_PACKAGE_NAME} || echo "No upgrade found"\n\ +echo "--- Simulating upgrade ---"\n\ +apt upgrade --simulate\n\ +' > /entrypoint.sh + +RUN chmod +x /entrypoint.sh +CMD ["/entrypoint.sh"] From 1e534da7f4207e2f2168279e608c1df987bdaf1b Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 4 Nov 2025 11:21:02 +0100 Subject: [PATCH 2/5] task test --- Taskfile.yml | 3 +++ test.Dockerfile | 69 ++++++++++++++++--------------------------------- 2 files changed, 25 insertions(+), 47 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 328f6b72..a718d77d 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -164,6 +164,9 @@ tasks: VERSION: "0.6.3" cmds: - docker build --no-cache -t mock-apt-repo -f test.Dockerfile . + - docker run --rm -it --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro --name apt-test-update mock-apt-repo + - docker exec -d apt-test-update /usr/bin/arduino-router + arduino-app-cli:build:local: desc: "Build the arduino-app-cli locally" diff --git a/test.Dockerfile b/test.Dockerfile index d2fa9dfb..94f8e11a 100644 --- a/test.Dockerfile +++ b/test.Dockerfile @@ -1,71 +1,46 @@ -# 1. Use Debian base FROM debian:trixie -ENV DEBIAN_FRONTEND=noninteractive +ENV container docker +STOPSIGNAL SIGRTMIN+3 +VOLUME ["/sys/fs/cgroup"] -# 2. Install necessary tools -RUN apt update && apt install -y \ - dpkg-dev \ - apt-utils \ - adduser \ +# Install systemd + dependencies +RUN apt update && apt install -y systemd systemd-sysv dbus \ + dpkg-dev apt-utils adduser gzip \ && rm -rf /var/lib/apt/lists/* -# 3. Symlink addgroup for minimal system scripts -RUN ln -s /usr/sbin/addgroup /usr/bin/addgroup || true - -# 4. Build args for parameterization +# Copy your packages and setup repo (as before) ARG OLD_PACKAGE_PATH=build/old_package ARG NEW_PACKAGE_PATH=build ARG APP_PACKAGE_NAME=arduino-app-cli ARG ROUTER_PACKAGE_NAME=arduino-router ARG ARCH=arm64 -ARG VERSION=0.6.3 -# 5. Copy packages dynamically COPY ${OLD_PACKAGE_PATH}/${APP_PACKAGE_NAME}*.deb /tmp/old_app.deb COPY ${NEW_PACKAGE_PATH}/${APP_PACKAGE_NAME}*.deb /tmp/new_app.deb COPY ${NEW_PACKAGE_PATH}/${ROUTER_PACKAGE_NAME}*.deb /tmp/new_router.deb -# 6. Install old package + router dependency -RUN apt update && apt install -y \ - /tmp/old_app.deb \ - /tmp/new_router.deb \ - && rm /tmp/old_app.deb - -# 7. Setup local APT repo with new packages -RUN mkdir -p /var/www/html/myrepo/dists/trixie/main/binary-${ARCH} - -# Rename new packages to match their real package/version/arch -RUN mv /tmp/new_app.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/${APP_PACKAGE_NAME}_${VERSION}_${ARCH}.deb -RUN mv /tmp/new_router.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/${ROUTER_PACKAGE_NAME}_0.6.2-1_${ARCH}.deb +RUN apt update && apt install -y /tmp/old_app.deb /tmp/new_router.deb \ + && rm /tmp/old_app.deb \ + && mkdir -p /var/www/html/myrepo/dists/trixie/main/binary-${ARCH} \ + && mv /tmp/new_app.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/ \ + && mv /tmp/new_router.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/ -# 8. Generate Packages.gz metadata WORKDIR /var/www/html/myrepo -RUN dpkg-scanpackages dists/trixie/main/binary-arm64 /dev/null | gzip -9c > dists/trixie/main/binary-arm64/Packages.gz +RUN dpkg-scanpackages dists/trixie/main/binary-${ARCH} /dev/null | gzip -9c > dists/trixie/main/binary-${ARCH}/Packages.gz WORKDIR / -# 9. Configure local APT repo +RUN usermod -s /bin/bash arduino || true +RUN mkdir -p /home/arduino && chown -R arduino:arduino /home/arduino + + RUN echo "deb [trusted=yes arch=${ARCH}] file:/var/www/html/myrepo trixie main" \ > /etc/apt/sources.list.d/my-mock-repo.list -# 10. Fix home dir for arduino user (optional) -RUN usermod -s /bin/bash arduino || true -RUN mkdir -p /home/arduino && chown -R arduino:arduino /home/arduino -# 11. Entrypoint: show upgrade availability -RUN echo '#!/bin/bash\n\ -set -e\n\ -echo "--- Updating APT ---"\n\ -apt update\n\ -echo "--- Installed version ---"\n\ -dpkg -l | grep ${APP_PACKAGE_NAME} || true\n\ -echo "--- Upgrade candidate ---"\n\ -apt-cache policy ${APP_PACKAGE_NAME}\n\ -echo "--- Available upgrades ---"\n\ -apt list --upgradable | grep ${APP_PACKAGE_NAME} || echo "No upgrade found"\n\ -echo "--- Simulating upgrade ---"\n\ -apt upgrade --simulate\n\ -' > /entrypoint.sh +VOLUME [ "/sys/fs/cgroup" ] + + -RUN chmod +x /entrypoint.sh -CMD ["/entrypoint.sh"] +# CMD: systemd must be PID 1 +CMD ["/sbin/init"] From 07f79370a167b0fd17a1d3c80b1f1205405362f1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 5 Nov 2025 10:23:54 +0100 Subject: [PATCH 3/5] dind --- .github/workflows/test-update.yml | 124 ++++++++++++++++++++++++++++++ Taskfile.yml | 33 ++++---- test.Dockerfile | 40 ++++------ 3 files changed, 153 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/test-update.yml diff --git a/.github/workflows/test-update.yml b/.github/workflows/test-update.yml new file mode 100644 index 00000000..7e34a67f --- /dev/null +++ b/.github/workflows/test-update.yml @@ -0,0 +1,124 @@ +name: Build & Update with Arduino CLI + +on: + push: + branches: + - main + - test_package_update + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-and-update: + runs-on: ubuntu-22.04 + + env: + TAG_VERSION: "v0.6.7" + ARCH: amd64 + APPCLI_REPO: arduino/arduino-app-cli + ROUTER_REPO: arduino/arduino-router + + APPCLI_TAG: "" + APPCLI_REGEX: "amd64\\.deb$" + + ROUTER_TAG: "" + ROUTER_REGEX: "amd64\\.deb$" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build deb + run: | + go tool task build-deb VERSION=${TAG_VERSION} ARCH=${ARCH} + + - name: Fetch .debs dynamically into build/stable + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + mkdir -p build/stable + + fetch_deb () { + local repo="$1" tag="${2:-}" regex="$3" + + echo "==> Resolving release for ${repo} (tag='${tag:-}')" + if [ -n "${tag}" ]; then + url="https://api.github.com/repos/${repo}/releases/tags/${tag}" + else + url="https://api.github.com/repos/${repo}/releases/latest" + fi + + rel="$(curl -sfL -H "Authorization: token ${GH_TOKEN}" -H "Accept: application/vnd.github+json" "${url}")" + + name="$(echo "$rel" | jq -r --arg re "${regex}" '.assets[] | select(.name | test($re)) | .name' | head -n1)" + dl="$(echo "$rel" | jq -r --arg re "${regex}" '.assets[] | select(.name | test($re)) | .browser_download_url' | head -n1)" + + if [ -z "${name}" ] || [ "${name}" = "null" ] || [ -z "${dl}" ] || [ "${dl}" = "null" ]; then + echo "!! No asset found in ${repo} matching regex: ${regex}" + echo " Available assets:" + echo "$rel" | jq -r '.assets[].name' + exit 1 + fi + + echo "Found: ${name}" + echo "Downloading: ${dl}" + + curl -sfL -H "Authorization: token ${GH_TOKEN}" \ + -o "build/stable/${name}" \ + "${dl}" + + ls -lh "build/stable/${name}" + } + + fetch_deb "${APPCLI_REPO}" "${APPCLI_TAG}" "${APPCLI_REGEX}" + fetch_deb "${ROUTER_REPO}" "${ROUTER_TAG}" "${ROUTER_REGEX}" + + echo "✅ Downloaded files:" + ls -lh build/stable/ + ls -lh build/ + + - name: Build Docker image (no cache) + run: | + docker build -t mock-apt-repo -f test.Dockerfile . + + - name: Run mock-apt-repo container + run: | + docker run --rm -d \ + --privileged \ + --cgroupns=host \ + -v /sys/fs/cgroup:/sys/fs/cgroup:rw \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e DOCKER_HOST=unix:///var/run/docker.sock \ + --name apt-test-update \ + mock-apt-repo + + - name: Run arduino-app-cli current version + run: | + docker exec --user arduino apt-test-update arduino-app-cli version + + - name: Run arduino-app-cli with auto-yes (as arduino) + run: | + mkdir -p artifacts + docker exec apt-test-update sh -lc 'su - arduino -c "yes | arduino-app-cli system update"' \ + | tee artifacts/arduino-system-update.log + + - name: Run arduino-app-cli version updated + run: | + docker exec --user arduino apt-test-update arduino-app-cli version + + - name: Restart arduino-app-cli service and verify + run: | + docker exec apt-test-update sh -lc ' + sudo systemctl status arduino-app-cli --no-pager + sudo systemctl status arduino-router --no-pager + ' + + diff --git a/Taskfile.yml b/Taskfile.yml index a718d77d..9984c807 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -13,7 +13,7 @@ vars: VERSION: # if version is not passed we hack the semver by encoding the commit as pre-release sh: echo "${VERSION:-0.0.0-$(git rev-parse --short HEAD)}" NEW_PACKAGE: - sh: ls -1 ./build/arduino-app-cli_*.deb 2>/dev/null | head -n 1 + sh: ls -1 ./build/arduino-app-cli_*.deb 2>/dev/null | head -n 1 GITHUB_TOKEN_FILE: ./github_token.txt tasks: @@ -130,26 +130,26 @@ tasks: desc: "Builds the mock-repo Docker image (requires GITHUB_TOKEN_FILE)" deps: [build-deb] vars: - PKG_PATH: '{{.NEW_PACKAGE}}' + PKG_PATH: "{{.NEW_PACKAGE}}" cmds: # --- MODIFIED --- # Check for both the package and the token file - | - if [ ! -f "{{.GITHUB_TOKEN_FILE}}" ]; then - echo "Error: GitHub token file not found at {{.GITHUB_TOKEN_FILE}}" - echo "Please create this file and add your GitHub PAT to it." - exit 1 - fi + if [ ! -f "{{.GITHUB_TOKEN_FILE}}" ]; then + echo "Error: GitHub token file not found at {{.GITHUB_TOKEN_FILE}}" + echo "Please create this file and add your GitHub PAT to it." + exit 1 + fi - | - echo "Using package: {{.PKG_PATH}}" - echo "Using GitHub token from: {{.GITHUB_TOKEN_FILE}}" - - # Enable BuildKit and pass the secret - DOCKER_BUILDKIT=1 docker build \ - --secret id=github_token,src={{.GITHUB_TOKEN_FILE}} \ - --build-arg NEW_PACKAGE_PATH={{.PKG_PATH}} \ - -t newdeb \ - -f test.Dockerfile . + echo "Using package: {{.PKG_PATH}}" + echo "Using GitHub token from: {{.GITHUB_TOKEN_FILE}}" + + # Enable BuildKit and pass the secret + DOCKER_BUILDKIT=1 docker build \ + --secret id=github_token,src={{.GITHUB_TOKEN_FILE}} \ + --build-arg NEW_PACKAGE_PATH={{.PKG_PATH}} \ + -t newdeb \ + -f test.Dockerfile . status: - '[[ -f "{{.PKG_PATH}}" ]]' - '[[ -f "{{.DOCKERFILE_NAME}}" ]]' @@ -167,7 +167,6 @@ tasks: - docker run --rm -it --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro --name apt-test-update mock-apt-repo - docker exec -d apt-test-update /usr/bin/arduino-router - arduino-app-cli:build:local: desc: "Build the arduino-app-cli locally" cmds: diff --git a/test.Dockerfile b/test.Dockerfile index 94f8e11a..e95a7689 100644 --- a/test.Dockerfile +++ b/test.Dockerfile @@ -1,30 +1,21 @@ FROM debian:trixie -ENV container docker -STOPSIGNAL SIGRTMIN+3 -VOLUME ["/sys/fs/cgroup"] - -# Install systemd + dependencies -RUN apt update && apt install -y systemd systemd-sysv dbus \ - dpkg-dev apt-utils adduser gzip \ - && rm -rf /var/lib/apt/lists/* - -# Copy your packages and setup repo (as before) -ARG OLD_PACKAGE_PATH=build/old_package -ARG NEW_PACKAGE_PATH=build -ARG APP_PACKAGE_NAME=arduino-app-cli -ARG ROUTER_PACKAGE_NAME=arduino-router +RUN apt update && \ + apt install -y systemd systemd-sysv dbus \ + sudo docker.io ca-certificates curl gnupg \ + dpkg-dev apt-utils adduser gzip && \ + rm -rf /var/lib/apt/lists/* + ARG ARCH=arm64 -COPY ${OLD_PACKAGE_PATH}/${APP_PACKAGE_NAME}*.deb /tmp/old_app.deb -COPY ${NEW_PACKAGE_PATH}/${APP_PACKAGE_NAME}*.deb /tmp/new_app.deb -COPY ${NEW_PACKAGE_PATH}/${ROUTER_PACKAGE_NAME}*.deb /tmp/new_router.deb +COPY build/stable/arduino-app-cli*.deb /tmp/stable.deb +COPY build/arduino-app-cli*.deb /tmp/unstable.deb +COPY build/stable/arduino-router*.deb /tmp/router.deb -RUN apt update && apt install -y /tmp/old_app.deb /tmp/new_router.deb \ - && rm /tmp/old_app.deb \ +RUN apt update && apt install -y /tmp/stable.deb /tmp/router.deb \ + && rm /tmp/stable.deb /tmp/router.deb \ && mkdir -p /var/www/html/myrepo/dists/trixie/main/binary-${ARCH} \ - && mv /tmp/new_app.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/ \ - && mv /tmp/new_router.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/ + && mv /tmp/unstable.deb /var/www/html/myrepo/dists/trixie/main/binary-${ARCH}/ WORKDIR /var/www/html/myrepo RUN dpkg-scanpackages dists/trixie/main/binary-${ARCH} /dev/null | gzip -9c > dists/trixie/main/binary-${ARCH}/Packages.gz @@ -32,15 +23,10 @@ WORKDIR / RUN usermod -s /bin/bash arduino || true RUN mkdir -p /home/arduino && chown -R arduino:arduino /home/arduino - +RUN usermod -aG docker arduino RUN echo "deb [trusted=yes arch=${ARCH}] file:/var/www/html/myrepo trixie main" \ > /etc/apt/sources.list.d/my-mock-repo.list - -VOLUME [ "/sys/fs/cgroup" ] - - - # CMD: systemd must be PID 1 CMD ["/sbin/init"] From f97fdf1f94e8e009e1c06bf542e04dcc4880b67d Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 5 Nov 2025 14:30:57 +0100 Subject: [PATCH 4/5] taskfile udpate --- .github/workflows/test-update.yml | 11 +---------- Taskfile.yml | 3 --- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/workflows/test-update.yml b/.github/workflows/test-update.yml index 7e34a67f..1682bb1d 100644 --- a/.github/workflows/test-update.yml +++ b/.github/workflows/test-update.yml @@ -112,13 +112,4 @@ jobs: - name: Run arduino-app-cli version updated run: | - docker exec --user arduino apt-test-update arduino-app-cli version - - - name: Restart arduino-app-cli service and verify - run: | - docker exec apt-test-update sh -lc ' - sudo systemctl status arduino-app-cli --no-pager - sudo systemctl status arduino-router --no-pager - ' - - + docker exec --user arduino apt-test-update arduino-app-cli version< \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml index 9984c807..0e26d348 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -160,12 +160,9 @@ tasks: desc: Test the debian package locally deps: - build-deb - vars: - VERSION: "0.6.3" cmds: - docker build --no-cache -t mock-apt-repo -f test.Dockerfile . - docker run --rm -it --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro --name apt-test-update mock-apt-repo - - docker exec -d apt-test-update /usr/bin/arduino-router arduino-app-cli:build:local: desc: "Build the arduino-app-cli locally" From 53e356e3f97acf5d5bd7a6a4efabe5f76bd1b6d3 Mon Sep 17 00:00:00 2001 From: Giulio Pilotto Date: Fri, 7 Nov 2025 16:25:46 +0100 Subject: [PATCH 5/5] using a go script to test update --- .github/workflows/test-update.yml | 92 +----- Taskfile.yml | 3 +- internal/testtools/deb_test.go | 264 ++++++++++++++++++ internal/testtools/package_update.go | 38 +++ .../testtools/test.Dockerfile | 0 5 files changed, 308 insertions(+), 89 deletions(-) create mode 100644 internal/testtools/deb_test.go create mode 100644 internal/testtools/package_update.go rename test.Dockerfile => internal/testtools/test.Dockerfile (100%) diff --git a/.github/workflows/test-update.yml b/.github/workflows/test-update.yml index 1682bb1d..165a7a17 100644 --- a/.github/workflows/test-update.yml +++ b/.github/workflows/test-update.yml @@ -10,22 +10,11 @@ on: permissions: contents: read + jobs: build-and-update: runs-on: ubuntu-22.04 - env: - TAG_VERSION: "v0.6.7" - ARCH: amd64 - APPCLI_REPO: arduino/arduino-app-cli - ROUTER_REPO: arduino/arduino-router - - APPCLI_TAG: "" - APPCLI_REGEX: "amd64\\.deb$" - - ROUTER_TAG: "" - ROUTER_REGEX: "amd64\\.deb$" - steps: - name: Checkout uses: actions/checkout@v4 @@ -35,81 +24,8 @@ jobs: with: go-version-file: go.mod - - name: Build deb - run: | - go tool task build-deb VERSION=${TAG_VERSION} ARCH=${ARCH} - - - name: Fetch .debs dynamically into build/stable + - name: Run dep package update test env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - mkdir -p build/stable - - fetch_deb () { - local repo="$1" tag="${2:-}" regex="$3" - - echo "==> Resolving release for ${repo} (tag='${tag:-}')" - if [ -n "${tag}" ]; then - url="https://api.github.com/repos/${repo}/releases/tags/${tag}" - else - url="https://api.github.com/repos/${repo}/releases/latest" - fi - - rel="$(curl -sfL -H "Authorization: token ${GH_TOKEN}" -H "Accept: application/vnd.github+json" "${url}")" - - name="$(echo "$rel" | jq -r --arg re "${regex}" '.assets[] | select(.name | test($re)) | .name' | head -n1)" - dl="$(echo "$rel" | jq -r --arg re "${regex}" '.assets[] | select(.name | test($re)) | .browser_download_url' | head -n1)" - - if [ -z "${name}" ] || [ "${name}" = "null" ] || [ -z "${dl}" ] || [ "${dl}" = "null" ]; then - echo "!! No asset found in ${repo} matching regex: ${regex}" - echo " Available assets:" - echo "$rel" | jq -r '.assets[].name' - exit 1 - fi - - echo "Found: ${name}" - echo "Downloading: ${dl}" - - curl -sfL -H "Authorization: token ${GH_TOKEN}" \ - -o "build/stable/${name}" \ - "${dl}" - - ls -lh "build/stable/${name}" - } - - fetch_deb "${APPCLI_REPO}" "${APPCLI_TAG}" "${APPCLI_REGEX}" - fetch_deb "${ROUTER_REPO}" "${ROUTER_TAG}" "${ROUTER_REGEX}" - - echo "✅ Downloaded files:" - ls -lh build/stable/ - ls -lh build/ - - - name: Build Docker image (no cache) - run: | - docker build -t mock-apt-repo -f test.Dockerfile . - - - name: Run mock-apt-repo container - run: | - docker run --rm -d \ - --privileged \ - --cgroupns=host \ - -v /sys/fs/cgroup:/sys/fs/cgroup:rw \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -e DOCKER_HOST=unix:///var/run/docker.sock \ - --name apt-test-update \ - mock-apt-repo - - - name: Run arduino-app-cli current version - run: | - docker exec --user arduino apt-test-update arduino-app-cli version - - - name: Run arduino-app-cli with auto-yes (as arduino) - run: | - mkdir -p artifacts - docker exec apt-test-update sh -lc 'su - arduino -c "yes | arduino-app-cli system update"' \ - | tee artifacts/arduino-system-update.log - - - name: Run arduino-app-cli version updated + GH_TOKEN: ${{ secrets.ARDUINOBOT_TOKEN }} run: | - docker exec --user arduino apt-test-update arduino-app-cli version< \ No newline at end of file + go test -v ./internal/testtools/deb_test.go --arch amd64 \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml index 0e26d348..82f91b35 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -105,9 +105,10 @@ tasks: deps: - build-deb:clone-examples cmds: - - docker build --build-arg BINARY_NAME=arduino-app-cli --build-arg DEB_NAME=arduino-app-cli --build-arg VERSION={{ .VERSION }} --build-arg ARCH={{ .ARCH }} --build-arg RELEASE={{ .RELEASE }} --output=./build -f debian/Dockerfile . + - docker build --build-arg BINARY_NAME=arduino-app-cli --build-arg DEB_NAME=arduino-app-cli --build-arg VERSION={{ .VERSION }} --build-arg ARCH={{ .ARCH }} --build-arg RELEASE={{ .RELEASE }} --output={{ .OUTPUT }} -f debian/Dockerfile . vars: ARCH: '{{.ARCH | default "arm64"}}' + OUTéPUT: '{{.OUTPUT | default "./build"}}' build-deb:clone-examples: desc: "Clones the examples repo directly into the debian structure" diff --git a/internal/testtools/deb_test.go b/internal/testtools/deb_test.go new file mode 100644 index 00000000..af813bce --- /dev/null +++ b/internal/testtools/deb_test.go @@ -0,0 +1,264 @@ +package testtools + +import ( + "bytes" + "flag" + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +var arch = flag.String("arch", "amd64", "target architecture") + +func TestStableToUnstable(t *testing.T) { + tagAppCli := FetchDebPackage(t, "arduino-app-cli", "latest", *arch) + FetchDebPackage(t, "arduino-router", "latest", *arch) + majorTag := majorTag(t, tagAppCli) + _ = minorTag(t, tagAppCli) + + fmt.Printf("Updating from stable version %s to unstable version %s \n", tagAppCli, majorTag) + fmt.Printf("Building local deb version %s \n", majorTag) + buildDebVersion(t, majorTag, *arch) + + fmt.Println("**** BUILD docker image *****") + buildDockerImage(t, "test.Dockerfile", "test-apt-update") + fmt.Println("**** RUN docker image *****") + runDockerCommand(t, "test-apt-update") + preUpdateVersion := runDockerSystemVersion(t, "apt-test-update") + runDockerSystemUpdate(t, "apt-test-update") + postUpdateVersion := runDockerSystemVersion(t, "apt-test-update") + runDockerCleanUp(t, "apt-test-update") + require.Equal(t, preUpdateVersion, "Arduino App CLI "+tagAppCli) + require.Equal(t, postUpdateVersion, "Arduino App CLI "+majorTag) + +} + +func TestUnstableToStable(t *testing.T) { + tagAppCli := FetchDebPackage(t, "arduino-app-cli", "latest", *arch) + FetchDebPackage(t, "arduino-router", "latest", *arch) + minorTag := minorTag(t, tagAppCli) + moveDeb(t, "build/stable", "build/", "arduino-app-cli", tagAppCli, *arch) + + fmt.Printf("Updating from unstable version %s to stable version %s \n", minorTag, tagAppCli) + fmt.Printf("Building local deb version %s \n", minorTag) + buildDebVersion(t, minorTag, *arch) + moveDeb(t, "build/", "build/stable", "arduino-app-cli", tagAppCli, *arch) + + fmt.Println("**** BUILD docker image *****") + buildDockerImage(t, "test.Dockerfile", "test-apt-update") + fmt.Println("**** RUN docker image *****") + runDockerCommand(t, "test-apt-update") + preUpdateVersion := runDockerSystemVersion(t, "apt-test-update") + runDockerSystemUpdate(t, "apt-test-update") + postUpdateVersion := runDockerSystemVersion(t, "apt-test-update") + runDockerCleanUp(t, "apt-test-update") + require.Equal(t, preUpdateVersion, "Arduino App CLI "+tagAppCli) + require.Equal(t, postUpdateVersion, "Arduino App CLI "+minorTag) + +} + +func FetchDebPackage(t *testing.T, repo, version, arch string) string { + t.Helper() + + cmd := exec.Command( + "gh", "release", "list", + "--repo", "github.com/arduino/"+repo, + "--exclude-pre-releases", + "--limit", "1", + ) + + output, err := cmd.CombinedOutput() + if err != nil { + log.Fatalf("command failed: %v\nOutput: %s", err, output) + } + + fmt.Println(string(output)) + + fields := strings.Fields(string(output)) + if len(fields) == 0 { + log.Fatal("could not parse tag from gh release list output") + } + tag := fields[0] + tagPath := strings.TrimPrefix(tag, "v") + + debFile := fmt.Sprintf("build/stable/%s_%s-1_%s.deb", repo, tagPath, arch) + fmt.Println(debFile) + if _, err := os.Stat(debFile); err == nil { + fmt.Printf("✅ %s already exists, skipping download.\n", debFile) + return tag + } + fmt.Println("Detected tag:", tag) + cmd2 := exec.Command( + "gh", "release", "download", + tag, + "--repo", "github.com/arduino/"+repo, + "--pattern", "*", + "--dir", "./build/stable", + ) + + out, err := cmd2.CombinedOutput() + if err != nil { + log.Fatalf("download failed: %v\nOutput: %s", err, out) + } + + return tag + +} + +func buildDebVersion(t *testing.T, tagVersion, arch string) { + t.Helper() + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + outputDir := filepath.Join(cwd, "build") + + cmd := exec.Command( + "go", "tool", "task", "build-deb", + fmt.Sprintf("VERSION=%s", tagVersion), + fmt.Sprintf("ARCH=%s", arch), + fmt.Sprintf("OUTPUT=%s", outputDir), + ) + + if err := cmd.Run(); err != nil { + log.Fatalf("failed to run build command: %v", err) + } +} + +func majorTag(t *testing.T, tag string) string { + t.Helper() + + parts := strings.Split(tag, ".") + last := parts[len(parts)-1] + + // Remove potential prefix 'v' from the first part, but not from the patch + lastNum, _ := strconv.Atoi(strings.TrimPrefix(last, "v")) + lastNum++ + + parts[len(parts)-1] = strconv.Itoa(lastNum) + newTag := strings.Join(parts, ".") + + return newTag +} + +func minorTag(t *testing.T, tag string) string { + t.Helper() + + parts := strings.Split(tag, ".") + last := parts[len(parts)-1] + + lastNum, _ := strconv.Atoi(strings.TrimPrefix(last, "v")) + if lastNum > 0 { + lastNum-- + } + + parts[len(parts)-1] = strconv.Itoa(lastNum) + newTag := strings.Join(parts, ".") + + if !strings.HasPrefix(newTag, "v") { + newTag = "v" + newTag + } + return newTag +} + +func buildDockerImage(t *testing.T, dockerfile, name string) { + t.Helper() + + cmd := exec.Command("docker", "build", "-t", name, "-f", dockerfile, ".") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("docker build failed: %v\nOutput:\n%s", err, string(out)) + } + +} + +func runDockerCommand(t *testing.T, containerImageName string) { + t.Helper() + + cmd := exec.Command( + "docker", "run", "--rm", "-d", + "--privileged", + "--cgroupns=host", + "-v", "/sys/fs/cgroup:/sys/fs/cgroup:rw", + "-v", "/var/run/docker.sock:/var/run/docker.sock", + "-e", "DOCKER_HOST=unix:///var/run/docker.sock", + "--name", "apt-test-update", + containerImageName, + ) + + if err := cmd.Run(); err != nil { + t.Fatalf("failed to run container: %v", err) + } + +} + +func runDockerSystemVersion(t *testing.T, containerName string) string { + t.Helper() + + cmd := exec.Command( + "docker", "exec", + "--user", "arduino", + containerName, + "arduino-app-cli", "version", + ) + + output, err := cmd.CombinedOutput() + if err != nil { + log.Fatalf("command failed: %v\nOutput: %s", err, output) + } + + return string(output) + +} + +func runDockerSystemUpdate(t *testing.T, containerName string) { + t.Helper() + var buf bytes.Buffer + + cmd := exec.Command( + "docker", "exec", + containerName, + "sh", "-lc", + `su - arduino -c "yes | arduino-app-cli system update"`, + ) + + cmd.Stdout = io.MultiWriter(os.Stdout, &buf) + + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error running system update: %v\n", err) + os.Exit(1) + } + +} + +func runDockerCleanUp(t *testing.T, containerName string) { + + cleanupCmd := exec.Command("docker", "rm", "-f", containerName) + + fmt.Println("🧹 Removing Docker container " + containerName) + if err := cleanupCmd.Run(); err != nil { + fmt.Printf("⚠️ Warning: could not remove container (might not exist): %v\n", err) + } + +} + +func moveDeb(t *testing.T, startDir, targetDir, repo string, tagVersion string, arch string) { + tagPath := strings.TrimPrefix(tagVersion, "v") + + debFile := fmt.Sprintf("%s/%s_%s-1_%s.deb", startDir, repo, tagPath, arch) + + moveCmd := exec.Command("mv", debFile, targetDir) + + fmt.Printf("📦 Moving %s → %s\n", debFile, targetDir) + if err := moveCmd.Run(); err != nil { + panic(fmt.Errorf("failed to move deb file: %w", err)) + } +} diff --git a/internal/testtools/package_update.go b/internal/testtools/package_update.go new file mode 100644 index 00000000..f0fd39d7 --- /dev/null +++ b/internal/testtools/package_update.go @@ -0,0 +1,38 @@ +// This file is part of arduino-app-cli. +// +// Copyright 2025 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-app-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. +package testtools + +import ( + "os" + "os/exec" + "runtime" + "testing" +) + +func DockerBuild(t *testing.T) { + + if runtime.GOOS != "linux" && os.Getenv("CI") != "" { + t.Skip("Skipping tests in CI that requires docker on non-Linux systems") + } + t.Helper() + + cmd := exec.Command("docker", "build", "-t", "adbd", "-f", "test.Dockerfile", ".") + cmd.Dir = getBaseProjectPath(t) + err := cmd.Run() + if err != nil { + t.Fatalf("failed to build adb daemon: %v", err) + } + +} diff --git a/test.Dockerfile b/internal/testtools/test.Dockerfile similarity index 100% rename from test.Dockerfile rename to internal/testtools/test.Dockerfile