From 9ce08c7880781e456446efe42b7f6a11972b2ef2 Mon Sep 17 00:00:00 2001 From: NehaNaithani Date: Wed, 11 May 2022 21:42:19 +1200 Subject: [PATCH 01/22] Updated the variables --- azure-pipelines.yaml | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/azure-pipelines.yaml b/azure-pipelines.yaml index cf47988..31080a5 100644 --- a/azure-pipelines.yaml +++ b/azure-pipelines.yaml @@ -11,6 +11,13 @@ trigger: - LICENSE pr: none +variables: + imageName: 'focal-freedom-236620/router' + imageTag: 'dev' + ref: $(Build.SourceBranch) + buildTag: $(Build.BuildId) + isRelease: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')] + stages: - stage: Build jobs: @@ -32,13 +39,6 @@ stages: pool: $(poolImageName) - variables: - imageName: 'focal-freedom-236620/router' - imageTag: 'dev' - ref: $(Build.SourceBranch) - buildTag: $(Build.BuildId) - isRelease: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')] - steps: - script: | if [[ $(ref) == refs/tags* ]]; then @@ -67,10 +67,6 @@ stages: pool: 'Azure Pipelines' variables: - imageName: 'focal-freedom-236620/router' - imageTag: 'dev' - ref: $(Build.SourceBranch) - buildTag: $(Build.BuildId) DOCKER_CLI_EXPERIMENTAL: enabled steps: @@ -122,10 +118,6 @@ stages: pool: 'Azure Pipelines' variables: - imageName: 'focal-freedom-236620/router' - imageTag: 'dev' - ref: $(Build.SourceBranch) - buildTag: $(Build.BuildId) DOCKER_CLI_EXPERIMENTAL: enabled steps: From d7216d7d18f0a8cc9b63aaedc049c6a7b63d407b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 25 Mar 2026 01:21:34 +0300 Subject: [PATCH 02/22] merge prep --- .github/workflows/push.yaml | 14 +++++++------- Dockerfile | 8 ++++---- Dockerfile-dev | 8 ++++---- README.md | 4 ++-- go.mod | 4 ++-- go.sum | 4 ++-- internal/config/env.go | 2 +- internal/config/env_test.go | 2 +- internal/config/platform.go | 4 ++-- internal/exec/exec.go | 2 +- internal/qdr/amqp_mgmt.go | 6 +++--- internal/qdr/messaging.go | 2 +- internal/qdr/qdr.go | 2 +- internal/qdr/router_logging.go | 2 +- internal/router/router.go | 8 ++++---- internal/watch/ssl.go | 2 +- internal/watch/ssl_test.go | 2 +- main.go | 12 ++++++------ 18 files changed, 44 insertions(+), 44 deletions(-) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index b1f9cc8..6de2f8c 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -71,7 +71,7 @@ jobs: platforms: linux/amd64 push: ${{ github.event_name == 'push' }} tags: | - ghcr.io/datasance/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-amd64 + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-amd64 build_arm64: name: Build arm64 @@ -98,7 +98,7 @@ jobs: platforms: linux/arm64 push: ${{ github.event_name == 'push' }} tags: | - ghcr.io/datasance/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-arm64 + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-arm64 merge_manifest: name: Merge multi-platform manifest @@ -118,8 +118,8 @@ jobs: - name: Create and push multi-platform manifest run: | docker buildx imagetools create \ - -t ghcr.io/datasance/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.VERSION }} \ - -t ghcr.io/datasance/${{ env.IMAGE_NAME }}:latest \ - -t ghcr.io/datasance/${{ env.IMAGE_NAME }}:main \ - ghcr.io/datasance/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-amd64 \ - ghcr.io/datasance/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-arm64 + -t ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.VERSION }} \ + -t ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:latest \ + -t ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:main \ + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-amd64 \ + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-arm64 diff --git a/Dockerfile b/Dockerfile index aa8a0e5..fec0eda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,9 +59,9 @@ FROM golang:1.23-alpine AS go-builder ARG TARGETOS ARG TARGETARCH -RUN mkdir -p /go/src/github.com/datasance/router -WORKDIR /go/src/github.com/datasance/router -COPY . /go/src/github.com/datasance/router +RUN mkdir -p /go/src/github.com/eclipse-iofog/router +WORKDIR /go/src/github.com/eclipse-iofog/router +COPY . /go/src/github.com/eclipse-iofog/router RUN go fmt ./... RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o bin/router . @@ -85,7 +85,7 @@ ENV VERSION=${version} ENV QDROUTERD_HOME=/home/skrouterd COPY LICENSE /licenses/LICENSE -COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /home/skrouterd/bin/router +COPY --from=go-builder /go/src/github.com/eclipse-iofog/router/bin/router /home/skrouterd/bin/router COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo diff --git a/Dockerfile-dev b/Dockerfile-dev index bb408ba..88184f0 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -3,9 +3,9 @@ FROM golang:1.23-alpine AS go-builder ARG TARGETOS ARG TARGETARCH -RUN mkdir -p /go/src/github.com/datasance/router -WORKDIR /go/src/github.com/datasance/router -COPY . /go/src/github.com/datasance/router +RUN mkdir -p /go/src/github.com/eclipse-iofog/router +WORKDIR /go/src/github.com/eclipse-iofog/router +COPY . /go/src/github.com/eclipse-iofog/router RUN go fmt ./... RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o bin/router . @@ -14,7 +14,7 @@ RUN microdnf install -y tzdata && microdnf reinstall -y tzdata FROM quay.io/skupper/skupper-router:main COPY LICENSE /licenses/LICENSE -COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /home/skrouterd/bin/router +COPY --from=go-builder /go/src/github.com/eclipse-iofog/router/bin/router /home/skrouterd/bin/router COPY scripts/launch.sh /home/skrouterd/bin/launch.sh COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo diff --git a/README.md b/README.md index d34b435..6bb5d4e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # iofog-router -Builds an image of the Apache Qpid Dispatch Router designed for use with Eclipse ioFog and Datasance Pot. The router can run in **Pot** mode (config from iofog agent) or **Kubernetes** mode (config from a volume-mounted file at `QDROUTERD_CONF`). +Builds an image of the Apache Qpid Dispatch Router designed for use with Eclipse ioFog. The router can run in **iofog** mode (config from iofog agent) or **Kubernetes** mode (config from a volume-mounted file at `QDROUTERD_CONF`). ## Environment variables | Variable | Default | Description | |----------|---------|-------------| -| `SKUPPER_PLATFORM` | `pot` | Mode: `pot` (config from iofog SDK) or `kubernetes` (config from file at `QDROUTERD_CONF`). | +| `SKUPPER_PLATFORM` | `iofog` | Mode: `iofog` (config from iofog SDK) or `kubernetes` (config from file at `QDROUTERD_CONF`). | | `QDROUTERD_CONF` | `/tmp/skrouterd.json` | Path to the router JSON config file. In Kubernetes mode the operator must volume-mount the router ConfigMap at this path. | | `SSL_PROFILE_PATH` | `/etc/skupper-router-certs` | Directory under which SSL profile certs reside (e.g. `SSL_PROFILE_PATH//ca.crt`, `tls.crt`, `tls.key`). Certs are mounted here in both K8s and Pot. | diff --git a/go.mod b/go.mod index fa169cd..ac36c33 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ -module github.com/datasance/router +module github.com/eclipse-iofog/router go 1.23.0 toolchain go1.24.3 require ( - github.com/datasance/iofog-go-sdk/v3 v3.7.0 + github.com/eclipse-iofog/iofog-go-sdk/v3 v3.7.0-beta.0 github.com/fsnotify/fsnotify v1.7.0 github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5 gotest.tools/v3 v3.5.2 diff --git a/go.sum b/go.sum index 6038ece..43ba8c3 100644 --- a/go.sum +++ b/go.sum @@ -19,14 +19,14 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/datasance/iofog-go-sdk/v3 v3.7.0 h1:j9ceWQdOXVvF2xAjAtZ+19/28c2WUuM5g4kKG+LVYQQ= -github.com/datasance/iofog-go-sdk/v3 v3.7.0/go.mod h1:mLHD3oHazNzKsV8HoWDsI4g613fHs+rKPUFtYf0tB/U= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse-iofog/iofog-go-sdk/v3 v3.7.0-beta.0 h1:wjzFAC/XeqCnlJNT6T+5wQRcevUed46eiPChQxsKnHI= +github.com/eclipse-iofog/iofog-go-sdk/v3 v3.7.0-beta.0/go.mod h1:QMKVbhVHxFNCVTDgfg/bMsi8ZFxG1/yZgpn1RymlSQc= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= diff --git a/internal/config/env.go b/internal/config/env.go index e361cc5..4a263ab 100644 --- a/internal/config/env.go +++ b/internal/config/env.go @@ -3,7 +3,7 @@ package config import ( "os" - "github.com/datasance/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/resources/types" ) const ( diff --git a/internal/config/env_test.go b/internal/config/env_test.go index b21b8ee..3907b02 100644 --- a/internal/config/env_test.go +++ b/internal/config/env_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/datasance/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/resources/types" ) func TestGetConfigPath(t *testing.T) { diff --git a/internal/config/platform.go b/internal/config/platform.go index fd2b5b4..118dc97 100644 --- a/internal/config/platform.go +++ b/internal/config/platform.go @@ -5,8 +5,8 @@ import ( "slices" "strings" - "github.com/datasance/router/internal/resources/types" - "github.com/datasance/router/internal/utils" + "github.com/eclipse-iofog/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/utils" "k8s.io/utils/ptr" ) diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 806c2e0..accebd9 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/internal/qdr/amqp_mgmt.go b/internal/qdr/amqp_mgmt.go index 980ea55..f3ec918 100644 --- a/internal/qdr/amqp_mgmt.go +++ b/internal/qdr/amqp_mgmt.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/datasance/router/internal/config" - "github.com/datasance/router/internal/resources/types" - "github.com/datasance/router/internal/utils" + "github.com/eclipse-iofog/router/internal/config" + "github.com/eclipse-iofog/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/utils" amqp "github.com/interconnectedcloud/go-amqp" ) diff --git a/internal/qdr/messaging.go b/internal/qdr/messaging.go index 48ac4db..ec20ef5 100644 --- a/internal/qdr/messaging.go +++ b/internal/qdr/messaging.go @@ -6,7 +6,7 @@ import ( amqp "github.com/interconnectedcloud/go-amqp" - "github.com/datasance/router/internal/messaging" + "github.com/eclipse-iofog/router/internal/messaging" ) type TlsConfigRetriever interface { diff --git a/internal/qdr/qdr.go b/internal/qdr/qdr.go index 0fa31a6..d9e7c9b 100644 --- a/internal/qdr/qdr.go +++ b/internal/qdr/qdr.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/datasance/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/resources/types" ) type RouterConfig struct { diff --git a/internal/qdr/router_logging.go b/internal/qdr/router_logging.go index 73a9cb6..120d479 100644 --- a/internal/qdr/router_logging.go +++ b/internal/qdr/router_logging.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/datasance/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/resources/types" ) func RouterLogConfigToString(config []types.RouterLogConfig) string { diff --git a/internal/router/router.go b/internal/router/router.go index 7e1251c..929d7b9 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,9 +21,9 @@ import ( "path/filepath" "time" - "github.com/datasance/router/internal/config" - "github.com/datasance/router/internal/exec" - "github.com/datasance/router/internal/qdr" + "github.com/eclipse-iofog/router/internal/config" + "github.com/eclipse-iofog/router/internal/exec" + "github.com/eclipse-iofog/router/internal/qdr" ) type Config struct { diff --git a/internal/watch/ssl.go b/internal/watch/ssl.go index 8ba3209..337fb2c 100644 --- a/internal/watch/ssl.go +++ b/internal/watch/ssl.go @@ -11,7 +11,7 @@ import ( "github.com/fsnotify/fsnotify" - "github.com/datasance/router/internal/qdr" + "github.com/eclipse-iofog/router/internal/qdr" ) const debounceDuration = 500 * time.Millisecond diff --git a/internal/watch/ssl_test.go b/internal/watch/ssl_test.go index 24fa533..ccd42f2 100644 --- a/internal/watch/ssl_test.go +++ b/internal/watch/ssl_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/datasance/router/internal/qdr" + "github.com/eclipse-iofog/router/internal/qdr" ) func TestScanSSLProfileDir(t *testing.T) { diff --git a/main.go b/main.go index 2162854..eba0d69 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,11 +21,11 @@ import ( "sync" "time" - sdk "github.com/datasance/iofog-go-sdk/v3/pkg/microservices" - "github.com/datasance/router/internal/config" - qdr "github.com/datasance/router/internal/qdr" - rt "github.com/datasance/router/internal/router" - "github.com/datasance/router/internal/watch" + sdk "github.com/eclipse-iofog/iofog-go-sdk/v3/pkg/microservices" + "github.com/eclipse-iofog/router/internal/config" + qdr "github.com/eclipse-iofog/router/internal/qdr" + rt "github.com/eclipse-iofog/router/internal/router" + "github.com/eclipse-iofog/router/internal/watch" ) var ( From 46449e42b8123cf94ccc8c76cce86c5d6bc18314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 25 Mar 2026 01:25:38 +0300 Subject: [PATCH 03/22] github ci branches edited --- .github/workflows/push.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 6de2f8c..7a25223 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -2,7 +2,8 @@ name: CI on: push: branches: - - main + - master + - develop tags: [v*] paths-ignore: - README.md @@ -10,7 +11,8 @@ on: - LICENSE pull_request: branches: - - main + - master + - develop paths-ignore: - README.md - CHANGELOG.md From 4dcf964f708d3a99d654e7a065f80daf8d0c9d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 25 Mar 2026 01:39:05 +0300 Subject: [PATCH 04/22] workflow secrets edited --- .github/workflows/push.yaml | 4 +- azure-pipelines.yaml | 314 ++++++++++++++++++------------------ 2 files changed, 159 insertions(+), 159 deletions(-) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 7a25223..e6e6e5f 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -64,7 +64,7 @@ jobs: with: registry: "ghcr.io" username: ${{ github.actor }} - password: ${{ secrets.PAT }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push amd64 image uses: docker/build-push-action@v5 with: @@ -91,7 +91,7 @@ jobs: with: registry: "ghcr.io" username: ${{ github.actor }} - password: ${{ secrets.PAT }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push arm64 image uses: docker/build-push-action@v5 with: diff --git a/azure-pipelines.yaml b/azure-pipelines.yaml index 31080a5..3557fc2 100644 --- a/azure-pipelines.yaml +++ b/azure-pipelines.yaml @@ -1,162 +1,162 @@ -trigger: - tags: - include: - - v* - branches: - include: - - develop - paths: - exclude: - - README.md - - LICENSE -pr: none - -variables: - imageName: 'focal-freedom-236620/router' - imageTag: 'dev' - ref: $(Build.SourceBranch) - buildTag: $(Build.BuildId) - isRelease: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')] - -stages: -- stage: Build - jobs: - - job: RouterDockerImages - - timeoutInMinutes: 240 +# trigger: +# tags: +# include: +# - v* +# branches: +# include: +# - develop +# paths: +# exclude: +# - README.md +# - LICENSE +# pr: none + +# variables: +# imageName: 'focal-freedom-236620/router' +# imageTag: 'dev' +# ref: $(Build.SourceBranch) +# buildTag: $(Build.BuildId) +# isRelease: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')] + +# stages: +# - stage: Build +# jobs: +# - job: RouterDockerImages + +# timeoutInMinutes: 240 - strategy: - matrix: - amd64: - poolImageName: 'Azure Pipelines' - imageTagSuffix: 'amd64' - arm32v7: - poolImageName: 'RPi' - imageTagSuffix: 'arm32v7' - arm64v8: - poolImageName: 'build-farm-coral' - imageTagSuffix: 'arm64v8' - - pool: $(poolImageName) - - steps: - - script: | - if [[ $(ref) == refs/tags* ]]; then - TAG=$(echo $(ref) | sed "s|refs/tags/v||g") - echo "##vso[task.setvariable variable=imageTag]$TAG" - else - LATESTTAG=$(git tag | tail -1) - LATESTVERS=${LATESTTAG#?} - if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi - echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" - fi - displayName: 'Set image tag' - - - template: ./pipeline.yaml - parameters: - imageName: $(imageName) - imageTag: $(imageTag)-$(imageTagSuffix) - dockerFile: 'Dockerfile' - -- stage: Publish - jobs: - - job: Dev_ioFogRouterDockerManifest - - timeoutInMinutes: 240 +# strategy: +# matrix: +# amd64: +# poolImageName: 'Azure Pipelines' +# imageTagSuffix: 'amd64' +# arm32v7: +# poolImageName: 'RPi' +# imageTagSuffix: 'arm32v7' +# arm64v8: +# poolImageName: 'build-farm-coral' +# imageTagSuffix: 'arm64v8' + +# pool: $(poolImageName) + +# steps: +# - script: | +# if [[ $(ref) == refs/tags* ]]; then +# TAG=$(echo $(ref) | sed "s|refs/tags/v||g") +# echo "##vso[task.setvariable variable=imageTag]$TAG" +# else +# LATESTTAG=$(git tag | tail -1) +# LATESTVERS=${LATESTTAG#?} +# if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi +# echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" +# fi +# displayName: 'Set image tag' + +# - template: ./pipeline.yaml +# parameters: +# imageName: $(imageName) +# imageTag: $(imageTag)-$(imageTagSuffix) +# dockerFile: 'Dockerfile' + +# - stage: Publish +# jobs: +# - job: Dev_ioFogRouterDockerManifest + +# timeoutInMinutes: 240 - pool: 'Azure Pipelines' - - variables: - DOCKER_CLI_EXPERIMENTAL: enabled - - steps: - - script: | - if [[ $(ref) == refs/tags* ]]; then - TAG=$(echo $(ref) | sed "s|refs/tags/v||g") - echo "##vso[task.setvariable variable=imageTag]$TAG" - else - LATESTTAG=$(git tag | tail -1) - LATESTVERS=${LATESTTAG#?} - if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi - echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" - fi - displayName: 'Set image tag' - - - script: | - echo $(imageTag) - displayName: 'Check image tag' +# pool: 'Azure Pipelines' + +# variables: +# DOCKER_CLI_EXPERIMENTAL: enabled + +# steps: +# - script: | +# if [[ $(ref) == refs/tags* ]]; then +# TAG=$(echo $(ref) | sed "s|refs/tags/v||g") +# echo "##vso[task.setvariable variable=imageTag]$TAG" +# else +# LATESTTAG=$(git tag | tail -1) +# LATESTVERS=${LATESTTAG#?} +# if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi +# echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" +# fi +# displayName: 'Set image tag' + +# - script: | +# echo $(imageTag) +# displayName: 'Check image tag' - - task: Docker@2 - displayName: Login to Dockerhub - inputs: - command: login - containerRegistry: 'Edgeworx GCP' - - - script: | - docker pull gcr.io/$(imageName):$(imageTag)-amd64 - docker pull gcr.io/$(imageName):$(imageTag)-arm32v7 - docker pull gcr.io/$(imageName):$(imageTag)-arm64v8 - displayName: 'Pull amd64, arm32v7, and arm64v8 docker images' - - - script: | - docker manifest create \ - gcr.io/$(imageName):$(imageTag) \ - --amend gcr.io/$(imageName):$(imageTag)-amd64 \ - --amend gcr.io/$(imageName):$(imageTag)-arm32v7 \ - --amend gcr.io/$(imageName):$(imageTag)-arm64v8 - displayName: 'Create image manifest' - - - script: | - docker manifest push gcr.io/$(imageName):$(imageTag) - displayName: 'Push image manifest' - - - job: Prod_ioFogRouterDockerManifest - condition: eq(variables['isRelease'], true) - - timeoutInMinutes: 240 +# - task: Docker@2 +# displayName: Login to Dockerhub +# inputs: +# command: login +# containerRegistry: 'Edgeworx GCP' + +# - script: | +# docker pull gcr.io/$(imageName):$(imageTag)-amd64 +# docker pull gcr.io/$(imageName):$(imageTag)-arm32v7 +# docker pull gcr.io/$(imageName):$(imageTag)-arm64v8 +# displayName: 'Pull amd64, arm32v7, and arm64v8 docker images' + +# - script: | +# docker manifest create \ +# gcr.io/$(imageName):$(imageTag) \ +# --amend gcr.io/$(imageName):$(imageTag)-amd64 \ +# --amend gcr.io/$(imageName):$(imageTag)-arm32v7 \ +# --amend gcr.io/$(imageName):$(imageTag)-arm64v8 +# displayName: 'Create image manifest' + +# - script: | +# docker manifest push gcr.io/$(imageName):$(imageTag) +# displayName: 'Push image manifest' + +# - job: Prod_ioFogRouterDockerManifest +# condition: eq(variables['isRelease'], true) + +# timeoutInMinutes: 240 - pool: 'Azure Pipelines' - - variables: - DOCKER_CLI_EXPERIMENTAL: enabled - - steps: - - script: | - if [[ $(ref) == refs/tags* ]]; then - TAG=$(echo $(ref) | sed "s|refs/tags/v||g") - echo "##vso[task.setvariable variable=imageTag]$TAG" - else - LATESTTAG=$(git tag | tail -1) - LATESTVERS=${LATESTTAG#?} - if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi - echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" - fi - displayName: 'Set image tag' - - - script: | - echo $(imageTag) - displayName: 'Check image tag' +# pool: 'Azure Pipelines' + +# variables: +# DOCKER_CLI_EXPERIMENTAL: enabled + +# steps: +# - script: | +# if [[ $(ref) == refs/tags* ]]; then +# TAG=$(echo $(ref) | sed "s|refs/tags/v||g") +# echo "##vso[task.setvariable variable=imageTag]$TAG" +# else +# LATESTTAG=$(git tag | tail -1) +# LATESTVERS=${LATESTTAG#?} +# if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi +# echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" +# fi +# displayName: 'Set image tag' + +# - script: | +# echo $(imageTag) +# displayName: 'Check image tag' - - task: Docker@2 - displayName: Login to Dockerhub - inputs: - command: login - containerRegistry: 'Edgeworx GCP' - - - script: | - docker pull gcr.io/$(imageName):$(imageTag)-amd64 - docker pull gcr.io/$(imageName):$(imageTag)-arm32v7 - docker pull gcr.io/$(imageName):$(imageTag)-arm64v8 - displayName: 'Pull amd64, arm32v7, and arm64v8 docker images' - - - script: | - docker manifest create \ - gcr.io/$(imageName):$(imageTag) \ - --amend gcr.io/$(imageName):$(imageTag)-amd64 \ - --amend gcr.io/$(imageName):$(imageTag)-arm32v7 \ - --amend gcr.io/$(imageName):$(imageTag)-arm64v8 - displayName: 'Create image manifest' - - - script: | - docker manifest push gcr.io/$(imageName):$(imageTag) - displayName: 'Push image manifest' +# - task: Docker@2 +# displayName: Login to Dockerhub +# inputs: +# command: login +# containerRegistry: 'Edgeworx GCP' + +# - script: | +# docker pull gcr.io/$(imageName):$(imageTag)-amd64 +# docker pull gcr.io/$(imageName):$(imageTag)-arm32v7 +# docker pull gcr.io/$(imageName):$(imageTag)-arm64v8 +# displayName: 'Pull amd64, arm32v7, and arm64v8 docker images' + +# - script: | +# docker manifest create \ +# gcr.io/$(imageName):$(imageTag) \ +# --amend gcr.io/$(imageName):$(imageTag)-amd64 \ +# --amend gcr.io/$(imageName):$(imageTag)-arm32v7 \ +# --amend gcr.io/$(imageName):$(imageTag)-arm64v8 +# displayName: 'Create image manifest' + +# - script: | +# docker manifest push gcr.io/$(imageName):$(imageTag) +# displayName: 'Push image manifest' From 5692b056b0ddef3333c799a0edd91b48218ae18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 25 Mar 2026 01:54:16 +0300 Subject: [PATCH 05/22] fixed workflow yaml gh token --- .github/workflows/push.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index e6e6e5f..0c9686e 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -116,7 +116,7 @@ jobs: with: registry: "ghcr.io" username: ${{ github.actor }} - password: ${{ secrets.PAT }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Create and push multi-platform manifest run: | docker buildx imagetools create \ From 7938ab3aaa4d8f958343fe1238d64084f78d6b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:19:06 +0300 Subject: [PATCH 06/22] Add root NOTICE and remove per-file EPL headers Move licensing to a single NOTICE file and drop repeated header blocks from the wrapper entrypoint and router/exec packages. --- NOTICE | 8 ++++++ internal/exec/exec.go | 13 ---------- internal/router/router.go | 19 +++----------- main.go | 54 +++++++++++++++++++-------------------- 4 files changed, 38 insertions(+), 56 deletions(-) create mode 100644 NOTICE diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..93bf5f1 --- /dev/null +++ b/NOTICE @@ -0,0 +1,8 @@ +router +Copyright 2023-2026 Contributors to the Eclipse ioFog Project + +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +http://www.eclipse.org/legal/epl-2.0 + +SPDX-License-Identifier: EPL-2.0 diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 806c2e0..ed6d2dd 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - package exec import ( diff --git a/internal/router/router.go b/internal/router/router.go index 7e1251c..b75b01a 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - package router import ( @@ -21,9 +8,9 @@ import ( "path/filepath" "time" - "github.com/datasance/router/internal/config" - "github.com/datasance/router/internal/exec" - "github.com/datasance/router/internal/qdr" + "github.com/eclipse-iofog/router/internal/config" + "github.com/eclipse-iofog/router/internal/exec" + "github.com/eclipse-iofog/router/internal/qdr" ) type Config struct { diff --git a/main.go b/main.go index 2162854..14ff9ae 100644 --- a/main.go +++ b/main.go @@ -1,31 +1,19 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - package main import ( "context" "errors" + "fmt" "log" "os" "sync" "time" sdk "github.com/datasance/iofog-go-sdk/v3/pkg/microservices" - "github.com/datasance/router/internal/config" - qdr "github.com/datasance/router/internal/qdr" - rt "github.com/datasance/router/internal/router" - "github.com/datasance/router/internal/watch" + "github.com/eclipse-iofog/router/internal/config" + qdr "github.com/eclipse-iofog/router/internal/qdr" + rt "github.com/eclipse-iofog/router/internal/router" + "github.com/eclipse-iofog/router/internal/watch" ) var ( @@ -127,7 +115,7 @@ func runKubernetesMode() { } func runPotMode() { - ioFogClient, clientError := sdk.NewDefaultIoFogClient() + ioFogClient, clientError := sdk.NewDefaultIoFogClientV3() if clientError != nil { log.Fatalln(clientError.Error()) } @@ -156,7 +144,7 @@ func runPotMode() { }, } if err := updateConfig(ioFogClient, newConfig); err != nil { - log.Fatal(err) + log.Printf("Error updating config from ioFog local API: %v", err) } else { if err := router.UpdateRouter(newConfig); err != nil { log.Printf("Error updating router: %v", err) @@ -167,16 +155,28 @@ func runPotMode() { } func updateConfig(ioFogClient *sdk.IoFogClient, config interface{}) error { - attemptLimit := 5 - var err error + const attemptLimit = 5 + var lastErr error - for err = ioFogClient.GetConfigIntoStruct(config); err != nil && attemptLimit > 0; attemptLimit-- { - return err + for attempt := 1; attempt <= attemptLimit; attempt++ { + lastErr = ioFogClient.GetConfigIntoStruct(config) + if lastErr == nil { + return nil + } + if attempt == attemptLimit { + break + } + log.Printf("WARN: Failed to get config from ioFog local API (attempt %d/%d): %v", attempt, attemptLimit, lastErr) + time.Sleep(time.Duration(attempt) * time.Second) } - if attemptLimit == 0 { - return errors.New("Update config failed") + var authErr *sdk.AuthMaterialError + if errors.As(lastErr, &authErr) { + return fmt.Errorf("failed to load ioFog service-account auth material: %w", lastErr) } - - return nil + var apiErr *sdk.V3APIError + if errors.As(lastErr, &apiErr) { + return fmt.Errorf("ioFog local API returned a v3 error while getting config: %w", lastErr) + } + return fmt.Errorf("update config failed after %d attempts: %w", attemptLimit, lastErr) } From d18079563020b3386e2270a9519d6b73f9070ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:19:18 +0300 Subject: [PATCH 07/22] Retarget module to github.com/eclipse-iofog/router on Go 1.26.4 Update go.mod and all internal import paths to the Eclipse ioFog module name and bump the Go toolchain version used by the wrapper. --- go.mod | 10 +++------- go.sum | 8 ++------ internal/config/env.go | 2 +- internal/config/env_test.go | 2 +- internal/qdr/amqp_mgmt.go | 6 +++--- internal/qdr/messaging.go | 2 +- internal/qdr/qdr.go | 2 +- internal/qdr/router_logging.go | 2 +- internal/watch/ssl.go | 2 +- internal/watch/ssl_test.go | 2 +- 10 files changed, 15 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index fa169cd..b93e425 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,9 @@ -module github.com/datasance/router +module github.com/eclipse-iofog/router -go 1.23.0 - -toolchain go1.24.3 +go 1.26.4 require ( - github.com/datasance/iofog-go-sdk/v3 v3.7.0 + github.com/datasance/iofog-go-sdk/v3 v3.8.0-beta.4 github.com/fsnotify/fsnotify v1.7.0 github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5 gotest.tools/v3 v3.5.2 @@ -18,8 +16,6 @@ require ( github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.2 // indirect - github.com/eapache/channels v1.1.0 // indirect - github.com/eapache/queue v1.1.0 // indirect github.com/fortytw2/leaktest v1.3.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect diff --git a/go.sum b/go.sum index 6038ece..21099bd 100644 --- a/go.sum +++ b/go.sum @@ -19,14 +19,10 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/datasance/iofog-go-sdk/v3 v3.7.0 h1:j9ceWQdOXVvF2xAjAtZ+19/28c2WUuM5g4kKG+LVYQQ= -github.com/datasance/iofog-go-sdk/v3 v3.7.0/go.mod h1:mLHD3oHazNzKsV8HoWDsI4g613fHs+rKPUFtYf0tB/U= +github.com/datasance/iofog-go-sdk/v3 v3.8.0-beta.4 h1:x05LpO3NRWmEUaaixvGCyoO9TZEkB1kvoPbjsja3m50= +github.com/datasance/iofog-go-sdk/v3 v3.8.0-beta.4/go.mod h1:GcjbpcoTO1h6mnbFzt2DPOjxH9uyooWk6LIcJumW8/8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= -github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= diff --git a/internal/config/env.go b/internal/config/env.go index e361cc5..4a263ab 100644 --- a/internal/config/env.go +++ b/internal/config/env.go @@ -3,7 +3,7 @@ package config import ( "os" - "github.com/datasance/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/resources/types" ) const ( diff --git a/internal/config/env_test.go b/internal/config/env_test.go index b21b8ee..3907b02 100644 --- a/internal/config/env_test.go +++ b/internal/config/env_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/datasance/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/resources/types" ) func TestGetConfigPath(t *testing.T) { diff --git a/internal/qdr/amqp_mgmt.go b/internal/qdr/amqp_mgmt.go index 980ea55..f3ec918 100644 --- a/internal/qdr/amqp_mgmt.go +++ b/internal/qdr/amqp_mgmt.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/datasance/router/internal/config" - "github.com/datasance/router/internal/resources/types" - "github.com/datasance/router/internal/utils" + "github.com/eclipse-iofog/router/internal/config" + "github.com/eclipse-iofog/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/utils" amqp "github.com/interconnectedcloud/go-amqp" ) diff --git a/internal/qdr/messaging.go b/internal/qdr/messaging.go index 48ac4db..ec20ef5 100644 --- a/internal/qdr/messaging.go +++ b/internal/qdr/messaging.go @@ -6,7 +6,7 @@ import ( amqp "github.com/interconnectedcloud/go-amqp" - "github.com/datasance/router/internal/messaging" + "github.com/eclipse-iofog/router/internal/messaging" ) type TlsConfigRetriever interface { diff --git a/internal/qdr/qdr.go b/internal/qdr/qdr.go index 0fa31a6..d9e7c9b 100644 --- a/internal/qdr/qdr.go +++ b/internal/qdr/qdr.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/datasance/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/resources/types" ) type RouterConfig struct { diff --git a/internal/qdr/router_logging.go b/internal/qdr/router_logging.go index 73a9cb6..120d479 100644 --- a/internal/qdr/router_logging.go +++ b/internal/qdr/router_logging.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/datasance/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/resources/types" ) func RouterLogConfigToString(config []types.RouterLogConfig) string { diff --git a/internal/watch/ssl.go b/internal/watch/ssl.go index 8ba3209..337fb2c 100644 --- a/internal/watch/ssl.go +++ b/internal/watch/ssl.go @@ -11,7 +11,7 @@ import ( "github.com/fsnotify/fsnotify" - "github.com/datasance/router/internal/qdr" + "github.com/eclipse-iofog/router/internal/qdr" ) const debounceDuration = 500 * time.Millisecond diff --git a/internal/watch/ssl_test.go b/internal/watch/ssl_test.go index 24fa533..ccd42f2 100644 --- a/internal/watch/ssl_test.go +++ b/internal/watch/ssl_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/datasance/router/internal/qdr" + "github.com/eclipse-iofog/router/internal/qdr" ) func TestScanSSLProfileDir(t *testing.T) { From 0eda6863803fbc17517704d1f00f9378be7c1ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:20:22 +0300 Subject: [PATCH 08/22] Adopt ioFog SDK v3 LocalAPI client with bounded retry Switch pot mode to the v3 SDK client, retry transient config fetch failures, and log rather than exit on post-startup update errors. Document LocalAPI v3 and required service-account mounts in the README. --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d34b435..af2fd9e 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,19 @@ Builds an image of the Apache Qpid Dispatch Router designed for use with Eclipse | Variable | Default | Description | |----------|---------|-------------| -| `SKUPPER_PLATFORM` | `pot` | Mode: `pot` (config from iofog SDK) or `kubernetes` (config from file at `QDROUTERD_CONF`). | +| `SKUPPER_PLATFORM` | `pot` | Mode: `pot` or `iofog` (alias; config from iofog SDK) or `kubernetes` (config from file at `QDROUTERD_CONF`). | | `QDROUTERD_CONF` | `/tmp/skrouterd.json` | Path to the router JSON config file. In Kubernetes mode the operator must volume-mount the router ConfigMap at this path. | | `SSL_PROFILE_PATH` | `/etc/skupper-router-certs` | Directory under which SSL profile certs reside (e.g. `SSL_PROFILE_PATH//ca.crt`, `tls.crt`, `tls.key`). Certs are mounted here in both K8s and Pot. | +| `EDGELET_MICROSERVICE_UID` | *(required in iofog/pot mode)* | Edgelet microservice identity used by the SDK client. | +| `SSL` | `true` | Enables HTTPS/WSS for ioFog Local API in Pot mode. | + +## Pot mode and ioFog LocalAPI v3 + +In Pot mode, router reads config from ioFog Agent LocalAPI v3 using the SDK (`GET /v1/microservices/config`) and listens for update signals over control websocket (`/v1/microservices/control`). + +To work correctly, the container must have ioFog service-account material mounted: + +- token at `/var/run/secrets/edgelet.iofog.org/serviceaccount/token` +- CA at `/var/run/secrets/edgelet.iofog.org/serviceaccount/ca.crt` In Kubernetes mode the router does not use the Kubernetes API; the operator is responsible for mounting the router config at `QDROUTERD_CONF`. Config file changes are watched and applied to the running router via qdr (same as Pot mode). From c81e907418ec6ce2d3115fb1ef6c3a05c0a46148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:20:49 +0300 Subject: [PATCH 09/22] Accept SKUPPER_PLATFORM=iofog as an alias for pot mode Recognize iofog in platform detection and add tests covering pot, iofog, and kubernetes routing modes. --- internal/config/platform.go | 13 ++++- internal/config/platform_test.go | 80 +++++++++++++++++++++++++++++++ internal/resources/types/types.go | 1 + 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 internal/config/platform_test.go diff --git a/internal/config/platform.go b/internal/config/platform.go index fd2b5b4..491a67b 100644 --- a/internal/config/platform.go +++ b/internal/config/platform.go @@ -5,8 +5,8 @@ import ( "slices" "strings" - "github.com/datasance/router/internal/resources/types" - "github.com/datasance/router/internal/utils" + "github.com/eclipse-iofog/router/internal/resources/types" + "github.com/eclipse-iofog/router/internal/utils" "k8s.io/utils/ptr" ) @@ -58,6 +58,8 @@ func GetPlatform() types.Platform { configuredPlatform = &platform case types.PlatformPot: configuredPlatform = &platform + case types.PlatformIoFog: + configuredPlatform = &platform case types.PlatformKubernetes: configuredPlatform = &platform default: @@ -68,6 +70,13 @@ func GetPlatform() types.Platform { // IsKubernetesRouterMode returns true when SKUPPER_PLATFORM is "kubernetes" // (router config from ConfigMap). Default is pot (config from iofog SDK). +// SKUPPER_PLATFORM=pot or iofog both use SDK LocalAPI v3 mode. func IsKubernetesRouterMode() bool { return os.Getenv(types.ENV_PLATFORM) == string(types.PlatformKubernetes) } + +// IsPotRouterMode returns true when SKUPPER_PLATFORM is unset, "pot", or "iofog". +func IsPotRouterMode() bool { + platform := os.Getenv(types.ENV_PLATFORM) + return platform == "" || platform == string(types.PlatformPot) || platform == string(types.PlatformIoFog) +} diff --git a/internal/config/platform_test.go b/internal/config/platform_test.go new file mode 100644 index 0000000..79d0d2e --- /dev/null +++ b/internal/config/platform_test.go @@ -0,0 +1,80 @@ +package config + +import ( + "os" + "testing" + + "github.com/eclipse-iofog/router/internal/resources/types" +) + +func TestIsKubernetesRouterMode(t *testing.T) { + key := types.ENV_PLATFORM + defer func() { _ = os.Unsetenv(key) }() + + tests := []struct { + name string + env string + want bool + }{ + {"kubernetes", "kubernetes", true}, + {"pot", "pot", false}, + {"iofog alias", "iofog", false}, + {"unset defaults to pot mode", "", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.env == "" { + os.Unsetenv(key) + } else { + os.Setenv(key, tt.env) + } + if got := IsKubernetesRouterMode(); got != tt.want { + t.Errorf("IsKubernetesRouterMode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsPotRouterMode(t *testing.T) { + key := types.ENV_PLATFORM + defer func() { _ = os.Unsetenv(key) }() + + tests := []struct { + name string + env string + want bool + }{ + {"pot", "pot", true}, + {"iofog alias", "iofog", true}, + {"unset", "", true}, + {"kubernetes", "kubernetes", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.env == "" { + os.Unsetenv(key) + } else { + os.Setenv(key, tt.env) + } + if got := IsPotRouterMode(); got != tt.want { + t.Errorf("IsPotRouterMode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetPlatformAcceptsIoFog(t *testing.T) { + key := types.ENV_PLATFORM + defer func() { + _ = os.Unsetenv(key) + ClearPlatform() + }() + + os.Setenv(key, "iofog") + ClearPlatform() + if got := GetPlatform(); got != types.PlatformIoFog { + t.Errorf("GetPlatform() with iofog = %q, want %q", got, types.PlatformIoFog) + } +} diff --git a/internal/resources/types/types.go b/internal/resources/types/types.go index 6b4d1c9..b0e402f 100644 --- a/internal/resources/types/types.go +++ b/internal/resources/types/types.go @@ -188,6 +188,7 @@ type Platform string const ( PlatformKubernetes Platform = "kubernetes" PlatformPot Platform = "pot" + PlatformIoFog Platform = "iofog" PlatformPodman Platform = "podman" PlatformDocker Platform = "docker" PlatformLinux Platform = "linux" From 823c6900c666ee7266eb24ac56daa1d9a975dad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:20:58 +0300 Subject: [PATCH 10/22] Pin skupper-router 3.5.1 in production Dockerfile and add Dockerfile.dev Build embedded skupper from the 3.5.1 tag with the matching proton revision, use golang 1.26.4-alpine for the wrapper, and replace Dockerfile-dev with Dockerfile.dev based on quay.io/skupper/skupper-router:3.5.1. --- Dockerfile | 22 ++++++++++++---------- Dockerfile-dev | 23 ----------------------- Dockerfile.dev | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 33 deletions(-) delete mode 100644 Dockerfile-dev create mode 100644 Dockerfile.dev diff --git a/Dockerfile b/Dockerfile index aa8a0e5..b50b541 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,9 +16,9 @@ RUN microdnf -y --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install \ && microdnf clean all -y WORKDIR /build -# Clone skupper-router so repo contents are in /build (not /build/skupper-router) -RUN git clone --depth 1 --branch main https://github.com/skupperproject/skupper-router.git . -ENV PROTON_VERSION=main +# Clone skupper-router 3.5.1 so repo contents are in /build (not /build/skupper-router) +RUN git clone --depth 1 --branch 3.5.1 https://github.com/skupperproject/skupper-router.git . +ENV PROTON_VERSION=e5d5c2badb964684bf41ba509a110bf06a24712a ENV PROTON_SOURCE_URL=${PROTON_SOURCE_URL:-https://github.com/apache/qpid-proton/archive/${PROTON_VERSION}.tar.gz} ENV LWS_VERSION=v4.3.3 ENV LIBUNWIND_VERSION=v1.8.1 @@ -54,14 +54,14 @@ RUN dnf -y --setopt=install_weak_deps=0 --nodocs \ RUN [ -d /usr/share/buildinfo ] && cp -a /usr/share/buildinfo /output/usr/share/buildinfo ||: RUN [ -d /root/buildinfo ] && cp -a /root/buildinfo /output/root/buildinfo ||: -FROM golang:1.23-alpine AS go-builder +FROM golang:1.26.4-alpine AS go-builder ARG TARGETOS ARG TARGETARCH -RUN mkdir -p /go/src/github.com/datasance/router -WORKDIR /go/src/github.com/datasance/router -COPY . /go/src/github.com/datasance/router +RUN mkdir -p /go/src/github.com/eclipse-iofog/router +WORKDIR /go/src/github.com/eclipse-iofog/router +COPY . /go/src/github.com/eclipse-iofog/router RUN go fmt ./... RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o bin/router . @@ -85,10 +85,12 @@ ENV VERSION=${version} ENV QDROUTERD_HOME=/home/skrouterd COPY LICENSE /licenses/LICENSE -COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /home/skrouterd/bin/router +COPY --from=go-builder /go/src/github.com/eclipse-iofog/router/bin/router /home/skrouterd/bin/router COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo -# Env: SKUPPER_PLATFORM=pot|kubernetes (default pot), QDROUTERD_CONF (default /tmp/skrouterd.json), -# SSL_PROFILE_PATH (default /etc/skupper-router-certs). In K8s mode operator mounts config at QDROUTERD_CONF. +# Env: SKUPPER_PLATFORM=pot|iofog|kubernetes (default pot), QDROUTERD_CONF (default /tmp/skrouterd.json), +# SSL_PROFILE_PATH (default /etc/skupper-router-certs), EDGELET_MICROSERVICE_UID (required in pot mode), SSL=true|false. +# Pot mode uses ioFog LocalAPI v3 over HTTPS/WSS with service-account token and CA mounts. +# In K8s mode operator mounts config at QDROUTERD_CONF. CMD ["/home/skrouterd/bin/router"] \ No newline at end of file diff --git a/Dockerfile-dev b/Dockerfile-dev deleted file mode 100644 index bb408ba..0000000 --- a/Dockerfile-dev +++ /dev/null @@ -1,23 +0,0 @@ -FROM golang:1.23-alpine AS go-builder - -ARG TARGETOS -ARG TARGETARCH - -RUN mkdir -p /go/src/github.com/datasance/router -WORKDIR /go/src/github.com/datasance/router -COPY . /go/src/github.com/datasance/router -RUN go fmt ./... -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o bin/router . - -FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS tz -RUN microdnf install -y tzdata && microdnf reinstall -y tzdata - -FROM quay.io/skupper/skupper-router:main -COPY LICENSE /licenses/LICENSE -COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /home/skrouterd/bin/router -COPY scripts/launch.sh /home/skrouterd/bin/launch.sh -COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo - -# Env: SKUPPER_PLATFORM=pot|kubernetes (default pot), QDROUTERD_CONF (default /tmp/skrouterd.json), -# SSL_PROFILE_PATH (default /etc/skupper-router-certs). In K8s mode operator mounts config at QDROUTERD_CONF. -CMD ["/home/skrouterd/bin/router"] diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..54d0967 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,25 @@ +FROM golang:1.26.4-alpine AS go-builder + +ARG TARGETOS +ARG TARGETARCH + +RUN mkdir -p /go/src/github.com/eclipse-iofog/router +WORKDIR /go/src/github.com/eclipse-iofog/router +COPY . /go/src/github.com/eclipse-iofog/router +RUN go fmt ./... +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o bin/router . + +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS tz +RUN microdnf install -y tzdata && microdnf reinstall -y tzdata + +FROM quay.io/skupper/skupper-router:3.5.1 +COPY LICENSE /licenses/LICENSE +COPY --from=go-builder /go/src/github.com/eclipse-iofog/router/bin/router /home/skrouterd/bin/router +COPY scripts/launch.sh /home/skrouterd/bin/launch.sh +COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo + +# Env: SKUPPER_PLATFORM=pot|iofog|kubernetes (default pot), QDROUTERD_CONF (default /tmp/skrouterd.json), +# SSL_PROFILE_PATH (default /etc/skupper-router-certs), EDGELET_MICROSERVICE_UID (required in pot mode), SSL=true|false. +# Pot mode uses ioFog LocalAPI v3 over HTTPS/WSS with service-account token and CA mounts. +# In K8s mode operator mounts config at QDROUTERD_CONF. +CMD ["/home/skrouterd/bin/router"] From 8b5a3474681f5633d72d6be6d9ed0e936d8c3982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:21:08 +0300 Subject: [PATCH 11/22] Remove legacy Azure Pipeline definitions Drop azure-pipelines.yaml and pipeline.yaml in favor of upcoming GitHub Actions workflows. --- azure-pipelines.yaml | 162 ------------------------------------------- pipeline.yaml | 19 ----- 2 files changed, 181 deletions(-) delete mode 100644 azure-pipelines.yaml delete mode 100644 pipeline.yaml diff --git a/azure-pipelines.yaml b/azure-pipelines.yaml deleted file mode 100644 index 31080a5..0000000 --- a/azure-pipelines.yaml +++ /dev/null @@ -1,162 +0,0 @@ -trigger: - tags: - include: - - v* - branches: - include: - - develop - paths: - exclude: - - README.md - - LICENSE -pr: none - -variables: - imageName: 'focal-freedom-236620/router' - imageTag: 'dev' - ref: $(Build.SourceBranch) - buildTag: $(Build.BuildId) - isRelease: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')] - -stages: -- stage: Build - jobs: - - job: RouterDockerImages - - timeoutInMinutes: 240 - - strategy: - matrix: - amd64: - poolImageName: 'Azure Pipelines' - imageTagSuffix: 'amd64' - arm32v7: - poolImageName: 'RPi' - imageTagSuffix: 'arm32v7' - arm64v8: - poolImageName: 'build-farm-coral' - imageTagSuffix: 'arm64v8' - - pool: $(poolImageName) - - steps: - - script: | - if [[ $(ref) == refs/tags* ]]; then - TAG=$(echo $(ref) | sed "s|refs/tags/v||g") - echo "##vso[task.setvariable variable=imageTag]$TAG" - else - LATESTTAG=$(git tag | tail -1) - LATESTVERS=${LATESTTAG#?} - if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi - echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" - fi - displayName: 'Set image tag' - - - template: ./pipeline.yaml - parameters: - imageName: $(imageName) - imageTag: $(imageTag)-$(imageTagSuffix) - dockerFile: 'Dockerfile' - -- stage: Publish - jobs: - - job: Dev_ioFogRouterDockerManifest - - timeoutInMinutes: 240 - - pool: 'Azure Pipelines' - - variables: - DOCKER_CLI_EXPERIMENTAL: enabled - - steps: - - script: | - if [[ $(ref) == refs/tags* ]]; then - TAG=$(echo $(ref) | sed "s|refs/tags/v||g") - echo "##vso[task.setvariable variable=imageTag]$TAG" - else - LATESTTAG=$(git tag | tail -1) - LATESTVERS=${LATESTTAG#?} - if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi - echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" - fi - displayName: 'Set image tag' - - - script: | - echo $(imageTag) - displayName: 'Check image tag' - - - task: Docker@2 - displayName: Login to Dockerhub - inputs: - command: login - containerRegistry: 'Edgeworx GCP' - - - script: | - docker pull gcr.io/$(imageName):$(imageTag)-amd64 - docker pull gcr.io/$(imageName):$(imageTag)-arm32v7 - docker pull gcr.io/$(imageName):$(imageTag)-arm64v8 - displayName: 'Pull amd64, arm32v7, and arm64v8 docker images' - - - script: | - docker manifest create \ - gcr.io/$(imageName):$(imageTag) \ - --amend gcr.io/$(imageName):$(imageTag)-amd64 \ - --amend gcr.io/$(imageName):$(imageTag)-arm32v7 \ - --amend gcr.io/$(imageName):$(imageTag)-arm64v8 - displayName: 'Create image manifest' - - - script: | - docker manifest push gcr.io/$(imageName):$(imageTag) - displayName: 'Push image manifest' - - - job: Prod_ioFogRouterDockerManifest - condition: eq(variables['isRelease'], true) - - timeoutInMinutes: 240 - - pool: 'Azure Pipelines' - - variables: - DOCKER_CLI_EXPERIMENTAL: enabled - - steps: - - script: | - if [[ $(ref) == refs/tags* ]]; then - TAG=$(echo $(ref) | sed "s|refs/tags/v||g") - echo "##vso[task.setvariable variable=imageTag]$TAG" - else - LATESTTAG=$(git tag | tail -1) - LATESTVERS=${LATESTTAG#?} - if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi - echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" - fi - displayName: 'Set image tag' - - - script: | - echo $(imageTag) - displayName: 'Check image tag' - - - task: Docker@2 - displayName: Login to Dockerhub - inputs: - command: login - containerRegistry: 'Edgeworx GCP' - - - script: | - docker pull gcr.io/$(imageName):$(imageTag)-amd64 - docker pull gcr.io/$(imageName):$(imageTag)-arm32v7 - docker pull gcr.io/$(imageName):$(imageTag)-arm64v8 - displayName: 'Pull amd64, arm32v7, and arm64v8 docker images' - - - script: | - docker manifest create \ - gcr.io/$(imageName):$(imageTag) \ - --amend gcr.io/$(imageName):$(imageTag)-amd64 \ - --amend gcr.io/$(imageName):$(imageTag)-arm32v7 \ - --amend gcr.io/$(imageName):$(imageTag)-arm64v8 - displayName: 'Create image manifest' - - - script: | - docker manifest push gcr.io/$(imageName):$(imageTag) - displayName: 'Push image manifest' diff --git a/pipeline.yaml b/pipeline.yaml deleted file mode 100644 index 3328ee8..0000000 --- a/pipeline.yaml +++ /dev/null @@ -1,19 +0,0 @@ -parameters: - - name: imageName - type: string - - name: imageTag - type: string - - name: dockerFile - type: string - -steps: - - task: Docker@2 - displayName: Build and push image - inputs: - containerRegistry: 'Edgeworx GCP' - repository: ${{ parameters.imageName }} - command: buildAndPush - Dockerfile: ${{ parameters.dockerFile }} - tags: | - ${{ parameters.imageTag }} - arguments: --build-arg BASE_IMAGE_TAG=${{ parameters.imageTag }} From 4d1180e91513972dcb4e7f1863620f446e33c025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:21:16 +0300 Subject: [PATCH 12/22] Extend ignore rules for build output and local tooling Ignore bin/, .cache/, .cursor, and AGENTS.md in git; exclude bin from Docker build context. --- .dockerignore | 3 ++- .gitignore | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index 8d7488b..8c73a7a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ .idea .vscode x.json -zz.yaml \ No newline at end of file +zz.yaml +bin \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8d7488b..ef972b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ .idea .vscode x.json -zz.yaml \ No newline at end of file +zz.yaml +bin/ +.cache/ +.cursor +AGENTS.md From 42f4b1564967aaf3caea291106dbb6c65be20120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:41:10 +0300 Subject: [PATCH 13/22] Add golangci-lint config, Makefile, and vulncheck script. Introduce local quality targets for build, test, lint, fmt-check, vulncheck, and gosec aligned with the nats-server wrapper. --- .golangci.yaml | 79 ++++++++++++++++++++++++++++++++++++++++++++ Makefile | 76 ++++++++++++++++++++++++++++++++++++++++++ scripts/vulncheck.sh | 8 +++++ 3 files changed, 163 insertions(+) create mode 100644 .golangci.yaml create mode 100644 Makefile create mode 100755 scripts/vulncheck.sh diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..1a7ebd7 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,79 @@ +# golangci-lint configuration for router wrapper +# Target: Go 1.26.4, module github.com/eclipse-iofog/router + +version: "2" + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - ^\.cursor/ + - ^build/ + - ^vendor/ + +linters: + default: none + enable: + - govet + - revive + - staticcheck + - errcheck + - misspell + - errorlint + settings: + revive: + enable-all-rules: true + rules: + - {name: add-constant, disabled: true} + - {name: argument-limit, disabled: true} + - {name: cognitive-complexity, disabled: true} + - {name: confusing-naming, disabled: true} + - {name: confusing-results, disabled: true} + - {name: cyclomatic, disabled: true} + - {name: early-return, disabled: true} + - {name: empty-block, disabled: true} + - {name: enforce-switch-style, disabled: true} + - {name: flag-parameter, disabled: true} + - {name: function-length, disabled: true} + - {name: function-result-limit, disabled: true} + - {name: import-shadowing, disabled: true} + - {name: line-length-limit, disabled: true} + - {name: max-control-nesting, disabled: true} + - {name: max-public-structs, disabled: true} + - {name: redundant-import-alias, disabled: true} + - {name: unsecure-url-scheme, disabled: true} + - {name: unused-parameter, disabled: true} + - {name: unused-receiver, disabled: true} + - {name: use-waitgroup-go, disabled: true} + - {name: var-naming, disabled: true} + staticcheck: + checks: + - all + - -SA1019 + misspell: + locale: US + errorlint: + errorf: true + asserts: true + comparison: true + exclusions: + generated: lax + paths: + - ^\.cursor/ + - ^build/ + - ^vendor/ + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + +run: + timeout: 5m diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9e8e120 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# router – skupper-router wrapper for Eclipse ioFog / Datasance PoT + +BINARY := router +BINARY_PATH := bin/$(BINARY) +LDFLAGS := -trimpath -ldflags="-s -w" + +GOBIN ?= $(shell go env GOBIN) +ifeq ($(GOBIN),) +GOBIN := $(shell go env GOPATH)/bin +endif + +export PATH := $(GOBIN):$(PATH) + +GOLANGCI_LINT_VERSION ?= v2.12.2 +GOLANGCI_LINT := $(GOBIN)/golangci-lint + +GOVULNCHECK_VERSION ?= v1.1.4 +GOSEC_SCOPE := ./... +# Legacy skupper AMQP + container paths: K8s secret names (G101), AMQP type coercion (G115/G109), +# skrouterd subprocess (G204), env/volume config paths (G304), container file modes (G301/G306), tar utils (G305/G110). +GOSEC_EXCLUDE := G101,G109,G110,G115,G204,G301,G304,G305,G306 + +.PHONY: all build test lint lint-fix install-lint fmt fmt-check vulncheck security-code clean + +all: build test + +build: + @mkdir -p bin + go build $(LDFLAGS) -o $(BINARY_PATH) . + +test: + go test ./... + +$(GOLANGCI_LINT): + @echo "Installing golangci-lint $(GOLANGCI_LINT_VERSION) -> $(GOBIN)..." + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ + | sh -s -- -b $(GOBIN) $(GOLANGCI_LINT_VERSION) + +install-lint: $(GOLANGCI_LINT) + @$(GOLANGCI_LINT) version + +lint: $(GOLANGCI_LINT) fmt-check + @echo "Running golangci-lint $(GOLANGCI_LINT_VERSION)..." + @$(GOLANGCI_LINT) run --config .golangci.yaml + +lint-fix: $(GOLANGCI_LINT) + @echo "Running golangci-lint $(GOLANGCI_LINT_VERSION) with --fix..." + @$(GOLANGCI_LINT) run --config .golangci.yaml --fix + +fmt: + go fmt ./... + +fmt-check: + @test -z "$$(gofmt -l .)" || (echo "Run 'make fmt' or 'gofmt -w .'"; gofmt -l .; exit 1) + +vulncheck: + @echo "Running govulncheck..." + @if ! command -v govulncheck >/dev/null 2>&1; then \ + echo "Installing govulncheck..."; \ + go install golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION); \ + fi + @chmod +x scripts/vulncheck.sh + @scripts/vulncheck.sh + @echo "Verifying module integrity..." + @go mod verify + +security-code: + @echo "Running Go static security analysis..." + @if ! command -v gosec >/dev/null 2>&1; then \ + echo "Installing gosec..."; \ + go install github.com/securego/gosec/v2/cmd/gosec@latest; \ + fi + @gosec -exclude-generated -exclude-dir=build -exclude-dir=bin -exclude=$(GOSEC_EXCLUDE) $(GOSEC_SCOPE) + +clean: + rm -rf bin/ diff --git a/scripts/vulncheck.sh b/scripts/vulncheck.sh new file mode 100755 index 0000000..2d0d198 --- /dev/null +++ b/scripts/vulncheck.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Run govulncheck on router module code paths. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +govulncheck -format=text ./... From 1620ccdd5f0afdca3d19e543694530ad1583e75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:41:23 +0300 Subject: [PATCH 14/22] Rename shared packages to match Go naming conventions. Move internal/utils to internal/routerutil and internal/resources/types to internal/resources/skuppertypes. --- .../{types => skuppertypes}/types.go | 32 +++++----- internal/{utils => routerutil}/command.go | 12 ++-- .../{utils => routerutil}/command_test.go | 7 ++- internal/{utils => routerutil}/files.go | 2 +- internal/{utils => routerutil}/files_test.go | 2 +- internal/{utils => routerutil}/retry.go | 10 +-- internal/{utils => routerutil}/retry_test.go | 61 +++++++----------- internal/{utils => routerutil}/tarball.go | 14 +++-- .../{utils => routerutil}/tarball_test.go | 2 +- internal/{utils => routerutil}/tcp.go | 12 ++-- internal/{utils => routerutil}/tcp_test.go | 28 ++++----- internal/{utils => routerutil}/tlscfg/tls.go | 0 internal/{utils => routerutil}/utils.go | 16 +++-- internal/{utils => routerutil}/utils_test.go | 7 +-- .../validator/simple_validator.go | 59 +++++++++--------- .../validator/simple_validator_test.go | 62 +++++++------------ internal/{utils => routerutil}/version.go | 12 ++-- .../{utils => routerutil}/version_test.go | 4 +- 18 files changed, 156 insertions(+), 186 deletions(-) rename internal/resources/{types => skuppertypes}/types.go (95%) rename internal/{utils => routerutil}/command.go (69%) rename internal/{utils => routerutil}/command_test.go (80%) rename internal/{utils => routerutil}/files.go (97%) rename internal/{utils => routerutil}/files_test.go (99%) rename internal/{utils => routerutil}/retry.go (95%) rename internal/{utils => routerutil}/retry_test.go (89%) rename internal/{utils => routerutil}/tarball.go (96%) rename internal/{utils => routerutil}/tarball_test.go (99%) rename internal/{utils => routerutil}/tcp.go (59%) rename internal/{utils => routerutil}/tcp_test.go (62%) rename internal/{utils => routerutil}/tlscfg/tls.go (100%) rename internal/{utils => routerutil}/utils.go (92%) rename internal/{utils => routerutil}/utils_test.go (99%) rename internal/{utils => routerutil}/validator/simple_validator.go (70%) rename internal/{utils => routerutil}/validator/simple_validator_test.go (90%) rename internal/{utils => routerutil}/version.go (90%) rename internal/{utils => routerutil}/version_test.go (98%) diff --git a/internal/resources/types/types.go b/internal/resources/skuppertypes/types.go similarity index 95% rename from internal/resources/types/types.go rename to internal/resources/skuppertypes/types.go index b0e402f..3462e8c 100644 --- a/internal/resources/types/types.go +++ b/internal/resources/skuppertypes/types.go @@ -12,14 +12,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package types +package skuppertypes import ( "time" ) const ( - ENV_PLATFORM = "SKUPPER_PLATFORM" + EnvPlatform = "SKUPPER_PLATFORM" EnvSSLProfilePath = "SSL_PROFILE_PATH" ) @@ -115,9 +115,9 @@ const ( TargetServiceQualifier string = BaseQualifier + "/target" HeadlessQualifier string = BaseQualifier + "/headless" IngressModeQualifier string = BaseQualifier + "/ingress" - CpuRequestAnnotation string = BaseQualifier + "/cpu-request" + CPURequestAnnotation string = BaseQualifier + "/cpu-request" MemoryRequestAnnotation string = BaseQualifier + "/memory-request" - CpuLimitAnnotation string = BaseQualifier + "/cpu-limit" + CPULimitAnnotation string = BaseQualifier + "/cpu-limit" MemoryLimitAnnotation string = BaseQualifier + "/memory-limit" AffinityAnnotation string = BaseQualifier + "/affinity" AntiAffinityAnnotation string = BaseQualifier + "/anti-affinity" @@ -141,7 +141,7 @@ const ( TypeTokenRequestQualifier string = BaseQualifier + "/type=connection-token-request" TokenGeneratedBy string = BaseQualifier + "/generated-by" SiteVersion string = BaseQualifier + "/site-version" - SiteId string = BaseQualifier + "/site-id" + SiteID string = BaseQualifier + "/site-id" TokenCost string = BaseQualifier + "/cost" TokenTemplate string = BaseQualifier + "/token-template" UpdatedAnnotation string = InternalQualifier + "/updated" @@ -155,7 +155,7 @@ const ( ClaimExpiration string = BaseQualifier + "/claim-expiration" ClaimsRemaining string = BaseQualifier + "/claims-remaining" ClaimsMade string = BaseQualifier + "/claims-made" - ClaimUrlAnnotationKey string = BaseQualifier + "/url" + ClaimURLAnnotationKey string = BaseQualifier + "/url" ClaimPasswordDataKey string = "password" ClaimCaCertDataKey string = "ca.crt" ClaimRequestSelector string = SkupperTypeQualifier + "=" + TypeClaimRequest @@ -163,8 +163,8 @@ const ( StatusAnnotationKey string = InternalQualifier + "/status" GatewayQualifier string = InternalQualifier + "/gateway" IngressOnlyQualifier string = BaseQualifier + "/ingress-only" - TlsCertQualifier string = BaseQualifier + "/tls-cert" - TlsTrustQualifier string = BaseQualifier + "/tls-trust" + TLSCertQualifier string = BaseQualifier + "/tls-cert" + TLSTrustQualifier string = BaseQualifier + "/tls-trust" ) // Console and vFlow Collector constants @@ -219,7 +219,7 @@ const ( type PrometheusAuthMode string const ( - PrometheusAuthModeTls PrometheusAuthMode = "tls" + PrometheusAuthModeTLS PrometheusAuthMode = "tls" PrometheusAuthModeBasic PrometheusAuthMode = "basic" PrometheusAuthModeUnsecured PrometheusAuthMode = "unsecured" ) @@ -234,7 +234,7 @@ const ( // Assembly constants const ( - AmqpDefaultPort int32 = 5672 + AMQPDefaultPort int32 = 5672 AmqpsDefaultPort int32 = 5671 EdgeRole string = "edge" EdgeRouteName string = "skupper-edge" @@ -299,7 +299,7 @@ type Listener struct { Host string `json:"host,omitempty"` Port int32 `json:"port"` RouteContainer bool `json:"routeContainer,omitempty"` - Http bool `json:"http,omitempty"` + HTTP bool `json:"http,omitempty"` Cost int32 `json:"cost,omitempty"` SslProfile string `json:"sslProfile,omitempty"` SaslMechanisms string `json:"saslMechanisms,omitempty"` @@ -346,7 +346,7 @@ type Credential struct { Name string Subject string Hosts []string - ConnectJson bool + ConnectJSON bool Post bool Data map[string][]byte Simple bool `default:"false"` @@ -380,9 +380,9 @@ type Tuning struct { NodeSelector string Affinity string AntiAffinity string - Cpu string + CPU string Memory string - CpuLimit string + CPULimit string MemoryLimit string } @@ -395,13 +395,13 @@ type RouterOptions struct { IngressHost string ServiceAnnotations map[string]string PodAnnotations map[string]string - LoadBalancerIp string + LoadBalancerIP string DisableMutualTLS bool } type LinkStatus struct { Name string - Url string + URL string Cost int Connected bool Configured bool diff --git a/internal/utils/command.go b/internal/routerutil/command.go similarity index 69% rename from internal/utils/command.go rename to internal/routerutil/command.go index 8f5e3e9..9f39ce6 100644 --- a/internal/utils/command.go +++ b/internal/routerutil/command.go @@ -1,4 +1,4 @@ -package utils +package routerutil import ( "bytes" @@ -13,17 +13,17 @@ const ( func PrettyPrintCommand(command string, args []string) string { var lineLength int buf := new(bytes.Buffer) - buf.WriteString(command) + _, _ = buf.WriteString(command) lineLength = len(command) for i, arg := range args { - buf.WriteString(" ") - buf.WriteString(arg) + _, _ = buf.WriteString(" ") + _, _ = buf.WriteString(arg) lineLength += len(arg) + 1 if lineLength > maxLineLength && i < len(args)-1 { - buf.WriteString(newLinePrefix) + _, _ = buf.WriteString(newLinePrefix) lineLength = len(indent) } } - buf.WriteString("\n") + _, _ = buf.WriteString("\n") return buf.String() } diff --git a/internal/utils/command_test.go b/internal/routerutil/command_test.go similarity index 80% rename from internal/utils/command_test.go rename to internal/routerutil/command_test.go index 78ef1bd..ab44fd6 100644 --- a/internal/utils/command_test.go +++ b/internal/routerutil/command_test.go @@ -1,8 +1,9 @@ -package utils +package routerutil_test import ( "testing" + routerutil "github.com/eclipse-iofog/router/internal/routerutil" "gotest.tools/v3/assert" ) @@ -25,8 +26,8 @@ var ( ) func TestPrettyPrintCommand(t *testing.T) { - output := PrettyPrintCommand(commandArgs[0], commandArgs[1:]) + output := routerutil.PrettyPrintCommand(commandArgs[0], commandArgs[1:]) assert.Equal(t, expectedOutput, output) - shortCommand := PrettyPrintCommand("command", []string{"arg1", "arg2", "arg3"}) + shortCommand := routerutil.PrettyPrintCommand("command", []string{"arg1", "arg2", "arg3"}) assert.Equal(t, "command arg1 arg2 arg3\n", shortCommand) } diff --git a/internal/utils/files.go b/internal/routerutil/files.go similarity index 97% rename from internal/utils/files.go rename to internal/routerutil/files.go index 0be97bb..1937750 100644 --- a/internal/utils/files.go +++ b/internal/routerutil/files.go @@ -1,4 +1,4 @@ -package utils +package routerutil import ( "fmt" diff --git a/internal/utils/files_test.go b/internal/routerutil/files_test.go similarity index 99% rename from internal/utils/files_test.go rename to internal/routerutil/files_test.go index e25be98..d1708f3 100644 --- a/internal/utils/files_test.go +++ b/internal/routerutil/files_test.go @@ -1,4 +1,4 @@ -package utils +package routerutil import ( "os" diff --git a/internal/utils/retry.go b/internal/routerutil/retry.go similarity index 95% rename from internal/utils/retry.go rename to internal/routerutil/retry.go index ba24718..9788fb5 100644 --- a/internal/utils/retry.go +++ b/internal/routerutil/retry.go @@ -12,10 +12,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package routerutil import ( "context" + "errors" "fmt" "time" ) @@ -79,14 +80,13 @@ func RetryError(interval time.Duration, maxRetries int, f CheckedFunc) error { } type Result struct { - Value interface{} + Value any Error error } type ResultFunc func() Result -func TryUntil(maxWindowTime time.Duration, f ResultFunc) (interface{}, error) { - +func TryUntil(maxWindowTime time.Duration, f ResultFunc) (any, error) { result := make(chan Result, 1) go func() { @@ -94,7 +94,7 @@ func TryUntil(maxWindowTime time.Duration, f ResultFunc) (interface{}, error) { }() select { case <-time.After(maxWindowTime): - return nil, fmt.Errorf("timed out") + return nil, errors.New("timed out") case result := <-result: return result.Value, result.Error } diff --git a/internal/utils/retry_test.go b/internal/routerutil/retry_test.go similarity index 89% rename from internal/utils/retry_test.go rename to internal/routerutil/retry_test.go index e4cc08c..181ff34 100644 --- a/internal/utils/retry_test.go +++ b/internal/routerutil/retry_test.go @@ -1,7 +1,8 @@ -package utils +package routerutil import ( "context" + "errors" "fmt" "testing" "time" @@ -47,7 +48,6 @@ type RetryTestItem struct { } func TestRetry(t *testing.T) { - testTable := []RetryTestItem{ { // #1 okOnTry: 1, @@ -57,22 +57,22 @@ func TestRetry(t *testing.T) { expectedResponse: nil, }, { // #2 okOnTry: 1, - err: fmt.Errorf("app error"), + err: errors.New("app error"), maxRetries: 3, expectedTries: 1, - expectedResponse: fmt.Errorf("app error"), + expectedResponse: errors.New("app error"), }, { // #3, #7 okOnTry: 0, err: nil, maxRetries: 3, expectedTries: 4, - expectedResponse: fmt.Errorf("still failing after 3 retries"), + expectedResponse: errors.New("still failing after 3 retries"), }, { // #4 okOnTry: 0, - err: fmt.Errorf("app error"), + err: errors.New("app error"), maxRetries: 3, expectedTries: 1, - expectedResponse: fmt.Errorf("app error"), + expectedResponse: errors.New("app error"), }, { // #3, #1 okOnTry: 2, err: nil, @@ -81,17 +81,17 @@ func TestRetry(t *testing.T) { expectedResponse: nil, }, { // #3, #2 okOnTry: 2, - err: fmt.Errorf("app error"), + err: errors.New("app error"), maxRetries: 3, expectedTries: 2, - expectedResponse: fmt.Errorf("app error"), + expectedResponse: errors.New("app error"), errorOnTry: 2, }, { // #3, #4 okOnTry: 0, - err: fmt.Errorf("app error"), + err: errors.New("app error"), maxRetries: 3, expectedTries: 2, - expectedResponse: fmt.Errorf("app error"), + expectedResponse: errors.New("app error"), errorOnTry: 2, }, { // #3, #5 okOnTry: 4, @@ -101,17 +101,17 @@ func TestRetry(t *testing.T) { expectedResponse: nil, }, { // #3, #6 okOnTry: 4, - err: fmt.Errorf("app error"), + err: errors.New("app error"), maxRetries: 3, expectedTries: 4, - expectedResponse: fmt.Errorf("app error"), + expectedResponse: errors.New("app error"), errorOnTry: 4, }, { // #3, #8 okOnTry: 0, - err: fmt.Errorf("app error"), + err: errors.New("app error"), maxRetries: 3, expectedTries: 4, - expectedResponse: fmt.Errorf("app error"), + expectedResponse: errors.New("app error"), errorOnTry: 4, }, { okOnTry: 1, @@ -127,14 +127,12 @@ func TestRetry(t *testing.T) { expectedResponse: fmt.Errorf("maxRetries (%d) should be > 0", 0), }, } - for _, item := range testTable { name := fmt.Sprintf("okOnTry:%v err:%v expectedTries:%v maxRetries:%v errorOnTry:%v nilOnTry: %v", item.okOnTry, item.err, item.expectedTries, item.maxRetries, item.errorOnTry, item.nilOnTry) var currentTry int t.Run(name, func(t *testing.T) { - retryErr := Retry(time.Microsecond, item.maxRetries, func() (ok bool, err error) { currentTry++ if currentTry > item.maxRetries+1 { @@ -158,8 +156,7 @@ func TestRetry(t *testing.T) { err = nil } - return - + return ok, err }) if item.expectedResponse != nil { @@ -179,10 +176,8 @@ func TestRetry(t *testing.T) { if currentTry != item.expectedTries { t.Errorf("%v != %v", currentTry, item.expectedTries) } - }) } - } type TestRetryErrorItem struct { @@ -216,7 +211,6 @@ func TestRetryError(t *testing.T) { expectSuccess: false, }, } - for _, item := range testTable { name := fmt.Sprintf("workOnTry: %v expectedTries: %v maxRetries: %v expectSuccess: %v", item.workOnTry, item.expectedTries, item.maxRetries, item.expectSuccess) @@ -228,7 +222,7 @@ func TestRetryError(t *testing.T) { if currentTry >= item.workOnTry { return nil } - return fmt.Errorf("Still not working") + return errors.New("still not working") }) if item.expectSuccess != (resp == nil) { @@ -238,16 +232,14 @@ func TestRetryError(t *testing.T) { if item.expectedTries != currentTry { t.Errorf("Returned in %d tries", currentTry) } - }) } - } type TestTryUntilItem struct { workOnSecond time.Duration funcError error - funcValue interface{} + funcValue any maxDuration time.Duration expectTimeout bool } @@ -270,7 +262,7 @@ func TestTryUntil(t *testing.T) { }, { workOnSecond: 100 * time.Millisecond, - funcError: fmt.Errorf("function is not working"), + funcError: errors.New("function is not working"), funcValue: nil, maxDuration: 5 * time.Second, expectTimeout: false, @@ -283,7 +275,6 @@ func TestTryUntil(t *testing.T) { expectTimeout: true, }, } - for _, item := range testTable { name := fmt.Sprintf("workOnSecond: %v maxDuration: %v expectTimeout: %v", item.workOnSecond, item.maxDuration, item.expectTimeout) @@ -298,24 +289,22 @@ func TestTryUntil(t *testing.T) { } }) - fmt.Printf("result: %v", resp) + _, _ = fmt.Printf("result: %v", resp) fmt.Println() if item.expectTimeout && err.Error() != "timed out" { - t.Errorf("It was expected a timeout but it did not happen") + t.Error("It was expected a timeout but it did not happen") } if item.funcValue != nil && resp == nil { - t.Errorf("It was expected to receive a value") + t.Error("It was expected to receive a value") } - if !item.expectTimeout && item.funcError != err { + if !item.expectTimeout && !errors.Is(item.funcError, err) { t.Errorf("Received wrong error: %s", err) } - }) } - } type TestRetryErrorWithContextItem struct { @@ -356,7 +345,6 @@ func TestRetryErrorWithContext(t *testing.T) { expectedError: "context deadline exceeded", }, } - for _, item := range testTable { item := item @@ -374,7 +362,7 @@ func TestRetryErrorWithContext(t *testing.T) { if currentTry == item.workOnTry { return nil } - return fmt.Errorf("Still not working") + return errors.New("still not working") }) elapsed := time.Since(start) @@ -403,5 +391,4 @@ func TestRetryErrorWithContext(t *testing.T) { } }) } - } diff --git a/internal/utils/tarball.go b/internal/routerutil/tarball.go similarity index 96% rename from internal/utils/tarball.go rename to internal/routerutil/tarball.go index b9efd96..415a070 100644 --- a/internal/utils/tarball.go +++ b/internal/routerutil/tarball.go @@ -1,10 +1,10 @@ -package utils +package routerutil import ( "archive/tar" "bytes" "compress/gzip" - "fmt" + "errors" "io" "os" "path" @@ -115,12 +115,14 @@ func (t *Tarball) addFiles(dir string) error { return err } if entry.IsDir() { - t.tw.WriteHeader(&tar.Header{ + if err := t.tw.WriteHeader(&tar.Header{ Name: path.Join(innerDir, entry.Name()) + "/", Mode: int64(fileStat.Mode()), Typeflag: tar.TypeDir, ModTime: fileStat.ModTime(), - }) + }); err != nil { + return err + } err = t.addFiles(path.Join(dir, entry.Name())) if err != nil { return err @@ -178,7 +180,7 @@ func (t *Tarball) AddFileData(fileName string, mode int64, mod time.Time, data [ func (t *Tarball) validateOutputPathoutputPath(outputPath string) error { // Validating outputPath if outputPath == "" { - return fmt.Errorf("outputPath is empty") + return errors.New("outputPath is empty") } outputPathStat, err := os.Stat(outputPath) if err != nil { @@ -187,7 +189,7 @@ func (t *Tarball) validateOutputPathoutputPath(outputPath string) error { return err } } else if !outputPathStat.Mode().IsDir() { - return fmt.Errorf("outputPath is not a directory") + return errors.New("outputPath is not a directory") } return nil } diff --git a/internal/utils/tarball_test.go b/internal/routerutil/tarball_test.go similarity index 99% rename from internal/utils/tarball_test.go rename to internal/routerutil/tarball_test.go index f90f720..445ab85 100644 --- a/internal/utils/tarball_test.go +++ b/internal/routerutil/tarball_test.go @@ -1,4 +1,4 @@ -package utils +package routerutil import ( "fmt" diff --git a/internal/utils/tcp.go b/internal/routerutil/tcp.go similarity index 59% rename from internal/utils/tcp.go rename to internal/routerutil/tcp.go index 7ecd768..c9edcd5 100644 --- a/internal/utils/tcp.go +++ b/internal/routerutil/tcp.go @@ -1,12 +1,12 @@ -package utils +package routerutil import ( - "fmt" + "errors" "net" "strconv" ) -func TcpPortInUse(host string, port int) bool { +func TCPPortInUse(host string, port int) bool { address := net.JoinHostPort(host, strconv.Itoa(port)) listener, err := net.Listen("tcp", address) if err != nil { @@ -18,11 +18,11 @@ func TcpPortInUse(host string, port int) bool { return false } -func TcpPortNextFree(startPort int) (int, error) { +func TCPPortNextFree(startPort int) (int, error) { for port := startPort; port <= 65535; port++ { - if !TcpPortInUse("", port) { + if !TCPPortInUse("", port) { return port, nil } } - return 0, fmt.Errorf("no available ports found") + return 0, errors.New("no available ports found") } diff --git a/internal/utils/tcp_test.go b/internal/routerutil/tcp_test.go similarity index 62% rename from internal/utils/tcp_test.go rename to internal/routerutil/tcp_test.go index 2fb0db8..28eca97 100644 --- a/internal/utils/tcp_test.go +++ b/internal/routerutil/tcp_test.go @@ -1,4 +1,4 @@ -package utils +package routerutil import ( "context" @@ -10,45 +10,45 @@ import ( "gotest.tools/v3/assert" ) -func TestTcpPortNextFree(t *testing.T) { - minPort, err := TcpPortNextFree(1024) +func TestTCPPortNextFree(t *testing.T) { + minPort, err := TCPPortNextFree(1024) assert.Assert(t, err, "no available tcp ports found") ctx, cancel := context.WithCancel(context.Background()) // listening on minPort to validate if it reports as in use - wg := listenTcpPort(ctx, minPort) + wg := listenTCPPort(ctx, minPort) // waiting on port to be bound wg.Wait() - // assert TcpPortNextFree shows a different port - newMinPort, err := TcpPortNextFree(minPort) + // assert TCPPortNextFree shows a different port + newMinPort, err := TCPPortNextFree(minPort) assert.Assert(t, err, "no more available tcp ports found") assert.Assert(t, newMinPort > minPort, "expected next free port available to be higher than %d but got %d", minPort, newMinPort) cancel() } -func TestTcpPortInUse(t *testing.T) { - minPort, err := TcpPortNextFree(1024) +func TestTCPPortInUse(t *testing.T) { + minPort, err := TCPPortNextFree(1024) assert.Assert(t, err, "no available tcp ports found") ctx := context.Background() // listening on minPort to validate if it reports as in use - wg := listenTcpPort(ctx, minPort) + wg := listenTCPPort(ctx, minPort) // waiting on port to be bound wg.Wait() - // assert TcpPortInUse reports port as being used - assert.Assert(t, TcpPortInUse("", minPort), "%d expected to be in use", minPort) + // assert TCPPortInUse reports port as being used + assert.Assert(t, TCPPortInUse("", minPort), "%d expected to be in use", minPort) // getting an extra port - nextMinPort, err := TcpPortNextFree(minPort) + nextMinPort, err := TCPPortNextFree(minPort) assert.Assert(t, err, "no more available tcp ports found") - assert.Assert(t, !TcpPortInUse("", nextMinPort), "tcp port %d expected to be available", nextMinPort) + assert.Assert(t, !TCPPortInUse("", nextMinPort), "tcp port %d expected to be available", nextMinPort) } -func listenTcpPort(ctx context.Context, port int) *sync.WaitGroup { +func listenTCPPort(ctx context.Context, port int) *sync.WaitGroup { wg := new(sync.WaitGroup) wg.Add(1) go func() { diff --git a/internal/utils/tlscfg/tls.go b/internal/routerutil/tlscfg/tls.go similarity index 100% rename from internal/utils/tlscfg/tls.go rename to internal/routerutil/tlscfg/tls.go diff --git a/internal/utils/utils.go b/internal/routerutil/utils.go similarity index 92% rename from internal/utils/utils.go rename to internal/routerutil/utils.go index bc7658f..f8feca7 100644 --- a/internal/utils/utils.go +++ b/internal/routerutil/utils.go @@ -12,10 +12,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package routerutil import ( "crypto/rand" + "errors" "io" "os" "os/user" @@ -25,12 +26,12 @@ import ( const alphanumerics = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" -func RandomId(length int) string { +func RandomID(length int) string { buffer := make([]byte, length) - rand.Read(buffer) - max := len(alphanumerics) + _, _ = rand.Read(buffer) + alphabetLen := len(alphanumerics) for i := range buffer { - buffer[i] = alphanumerics[int(buffer[i])%max] + buffer[i] = alphanumerics[int(buffer[i])%alphabetLen] } return string(buffer) } @@ -110,7 +111,7 @@ func IsDirEmpty(name string) (bool, error) { _, err = file.Readdir(1) - if err == io.EOF { + if errors.Is(err, io.EOF) { return true, nil } @@ -131,6 +132,9 @@ func StringSlicesEqual(a, b []string) bool { // DefaultStr returns the first non-empty string func DefaultStr(values ...string) string { + if len(values) == 0 { + return "" + } if len(values) == 1 { return values[0] } diff --git a/internal/utils/utils_test.go b/internal/routerutil/utils_test.go similarity index 99% rename from internal/utils/utils_test.go rename to internal/routerutil/utils_test.go index 610af60..dbd10ed 100644 --- a/internal/utils/utils_test.go +++ b/internal/routerutil/utils_test.go @@ -1,9 +1,10 @@ -package utils +package routerutil import ( - "gotest.tools/v3/assert" "reflect" "testing" + + "gotest.tools/v3/assert" ) func TestStringifySelector(t *testing.T) { @@ -46,7 +47,6 @@ func TestSliceEquals(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - expectedResult := test.result actualResult := StringSlicesEqual(test.sliceA, test.sliceB) assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) @@ -70,7 +70,6 @@ func TestGetOrDefault(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - expectedResult := test.result actualResult := GetOrDefault(test.value, test.defaultValue) assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) diff --git a/internal/utils/validator/simple_validator.go b/internal/routerutil/validator/simple_validator.go similarity index 70% rename from internal/utils/validator/simple_validator.go rename to internal/routerutil/validator/simple_validator.go index d4acbfe..f9de641 100644 --- a/internal/utils/validator/simple_validator.go +++ b/internal/routerutil/validator/simple_validator.go @@ -1,6 +1,7 @@ package validator import ( + "errors" "fmt" "regexp" "strings" @@ -8,51 +9,51 @@ import ( ) type Validator interface { - Evaluate(value interface{}) (bool, error) + Evaluate(value any) (bool, error) } // -type stringValidator struct { +type StringValidator struct { Expression *regexp.Regexp } -func NewStringValidator() *stringValidator { - return &stringValidator{ +func NewStringValidator() *StringValidator { + return &StringValidator{ Expression: regexp.MustCompile(`^\S*$`), } } -func NewHostStringValidator() *stringValidator { - return &stringValidator{ +func NewHostStringValidator() *StringValidator { + return &StringValidator{ Expression: regexp.MustCompile(`^[a-z0-9]+([-.]{1}[a-z0-9]+)*$`), } } -func NewResourceStringValidator() *stringValidator { - return &stringValidator{ +func NewResourceStringValidator() *StringValidator { + return &StringValidator{ Expression: regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$`), } } // TBD what are valid characters for selector field -func NewSelectorStringValidator() *stringValidator { - return &stringValidator{ +func NewSelectorStringValidator() *StringValidator { + return &StringValidator{ Expression: regexp.MustCompile(`^[A-Za-z0-9=:./-]+$`), } } -func NewFilePathStringValidator() *stringValidator { - return &stringValidator{ +func NewFilePathStringValidator() *StringValidator { + return &StringValidator{ Expression: regexp.MustCompile(`^[A-Za-z0-9./~-]+$`), } } -func (s stringValidator) Evaluate(value interface{}) (bool, error) { +func (s StringValidator) Evaluate(value any) (bool, error) { v, ok := value.(string) if !ok { - return false, fmt.Errorf("value is not a string") + return false, errors.New("value is not a string") } if s.Expression.MatchString(v) { @@ -76,17 +77,16 @@ func NewNumberValidator() *NumberValidator { } } -func (i NumberValidator) Evaluate(value interface{}) (bool, error) { - +func (i NumberValidator) Evaluate(value any) (bool, error) { v, ok := value.(int) if !ok { - return false, fmt.Errorf("value is not an integer") + return false, errors.New("value is not an integer") } if i.PositiveInt { if v < 0 { - return false, fmt.Errorf("value is not positive") + return false, errors.New("value is not positive") } if v > 0 { return true, nil @@ -95,13 +95,13 @@ func (i NumberValidator) Evaluate(value interface{}) (bool, error) { if i.IncludeZero { return true, nil } - return false, fmt.Errorf("value 0 is not allowed") + return false, errors.New("value 0 is not allowed") } } return true, nil } -/// +// type OptionValidator struct { AllowedOptions []string @@ -113,16 +113,15 @@ func NewOptionValidator(validOptions []string) *OptionValidator { } } -func (i OptionValidator) Evaluate(value interface{}) (bool, error) { - +func (i OptionValidator) Evaluate(value any) (bool, error) { v, ok := value.(string) if !ok { - return false, fmt.Errorf("value is not a string") + return false, errors.New("value is not a string") } if v == "" { - return false, fmt.Errorf("value must not be empty") + return false, errors.New("value must not be empty") } valueFound := false @@ -155,7 +154,6 @@ func NewExpirationInSecondsValidator() *DurationValidator { } func (i DurationValidator) Evaluate(value time.Duration) (bool, error) { - if value < i.MinDuration { return false, fmt.Errorf("duration must not be less than %v; got %v", i.MinDuration, value) } @@ -171,7 +169,7 @@ type WorkloadValidator struct { func NewWorkloadStringValidator(validOptions []string) *WorkloadValidator { re, err := regexp.Compile("^[A-Za-z0-9._-]+$") if err != nil { - fmt.Printf("Error compiling regex: %v", err) + _, _ = fmt.Printf("Error compiling regex: %v", err) return nil } return &WorkloadValidator{ @@ -180,18 +178,17 @@ func NewWorkloadStringValidator(validOptions []string) *WorkloadValidator { } } -func (s WorkloadValidator) Evaluate(value interface{}) (string, string, bool, error) { - +func (s WorkloadValidator) Evaluate(value any) (string, string, bool, error) { v, ok := value.(string) if !ok { - return "", "", false, fmt.Errorf("value is not a string") + return "", "", false, errors.New("value is not a string") } // workload has two parts / resource := strings.Split(v, "/") if len(resource) != 2 { - return "", "", false, fmt.Errorf("workload must include /") + return "", "", false, errors.New("workload must include /") } if s.Expression.MatchString(resource[1]) { @@ -201,7 +198,7 @@ func (s WorkloadValidator) Evaluate(value interface{}) (string, string, bool, er return option, resource[1], true, nil } } - return "", "", false, fmt.Errorf("resource-type does not match expected value: deployment/service/daemonset/statefulset") + return "", "", false, errors.New("resource-type does not match expected value: deployment/service/daemonset/statefulset") } return "", "", false, fmt.Errorf("value does not match this regular expression: %s", s.Expression) } diff --git a/internal/utils/validator/simple_validator_test.go b/internal/routerutil/validator/simple_validator_test.go similarity index 90% rename from internal/utils/validator/simple_validator_test.go rename to internal/routerutil/validator/simple_validator_test.go index f7c35e2..978df3a 100644 --- a/internal/utils/validator/simple_validator_test.go +++ b/internal/routerutil/validator/simple_validator_test.go @@ -10,11 +10,9 @@ import ( ) func TestNewStringValidator(t *testing.T) { - t.Run("Test String Validator constructor", func(t *testing.T) { - validRegexp := regexp.MustCompile(`^\S*$`) - expectedResult := &stringValidator{validRegexp} + expectedResult := &StringValidator{validRegexp} actualResult := NewStringValidator() assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) @@ -23,7 +21,7 @@ func TestNewStringValidator(t *testing.T) { func TestStringValidator_Evaluate(t *testing.T) { type test struct { name string - value interface{} + value any result bool } @@ -38,19 +36,16 @@ func TestStringValidator_Evaluate(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - - stringValidator := NewStringValidator() + validator := NewStringValidator() expectedResult := test.result - actualResult, _ := stringValidator.Evaluate(test.value) + actualResult, _ := validator.Evaluate(test.value) assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) } } func TestNewNumberValidator(t *testing.T) { - t.Run("Test Positive Int Validator constructor", func(t *testing.T) { - expectedResult := &NumberValidator{PositiveInt: true, IncludeZero: true} actualResult := NewNumberValidator() assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) @@ -60,7 +55,7 @@ func TestNewNumberValidator(t *testing.T) { func TestIntegerValidator_Evaluate(t *testing.T) { type test struct { name string - value interface{} + value any result bool } @@ -75,7 +70,6 @@ func TestIntegerValidator_Evaluate(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - numberValidator := NewNumberValidator() expectedResult := test.result @@ -99,7 +93,6 @@ func TestTimeoutInSecondsValidator_Evaluate(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - numberValidator := NewTimeoutInSecondsValidator() expectedResult := test.result @@ -110,9 +103,7 @@ func TestTimeoutInSecondsValidator_Evaluate(t *testing.T) { } func TestNewOptionValidator(t *testing.T) { - t.Run("Test Option Validator constructor", func(t *testing.T) { - expectedResult := &OptionValidator{ AllowedOptions: []string{"a", "b"}, } @@ -124,7 +115,7 @@ func TestNewOptionValidator(t *testing.T) { func TestOptionValidator_Evaluate(t *testing.T) { type test struct { name string - value interface{} + value any result bool } @@ -136,7 +127,6 @@ func TestOptionValidator_Evaluate(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - optionValidator := NewOptionValidator([]string{"a", "b"}) expectedResult := test.result actualResult, _ := optionValidator.Evaluate(test.value) @@ -146,11 +136,9 @@ func TestOptionValidator_Evaluate(t *testing.T) { } func TestNewResourceStringValidator(t *testing.T) { - t.Run("Test New Resource String Validator constructor", func(t *testing.T) { - validRegexp := regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$`) - expectedResult := &stringValidator{validRegexp} + expectedResult := &StringValidator{validRegexp} actualResult := NewResourceStringValidator() assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) @@ -159,7 +147,7 @@ func TestNewResourceStringValidator(t *testing.T) { func TestNewResourceStringValidator_Evaluate(t *testing.T) { type test struct { name string - value interface{} + value any result bool } @@ -175,21 +163,18 @@ func TestNewResourceStringValidator_Evaluate(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - - stringValidator := NewResourceStringValidator() + validator := NewResourceStringValidator() expectedResult := test.result - actualResult, _ := stringValidator.Evaluate(test.value) + actualResult, _ := validator.Evaluate(test.value) assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) } } func TestNewSelectorStringValidator(t *testing.T) { - t.Run("Test New Selector String Validator constructor", func(t *testing.T) { - validRegexp := regexp.MustCompile("^[A-Za-z0-9=:./-]+$") - expectedResult := &stringValidator{validRegexp} + expectedResult := &StringValidator{validRegexp} actualResult := NewSelectorStringValidator() assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) @@ -198,7 +183,7 @@ func TestNewSelectorStringValidator(t *testing.T) { func TestNewSelectorStringValidator_Evaluate(t *testing.T) { type test struct { name string - value interface{} + value any result bool } @@ -217,20 +202,18 @@ func TestNewSelectorStringValidator_Evaluate(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - stringValidator := NewSelectorStringValidator() + validator := NewSelectorStringValidator() expectedResult := test.result - actualResult, _ := stringValidator.Evaluate(test.value) + actualResult, _ := validator.Evaluate(test.value) assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) } } func TestNewFilePathStringValidator(t *testing.T) { - t.Run("Test New File Path String Validator constructor", func(t *testing.T) { - validRegexp := regexp.MustCompile("^[A-Za-z0-9./~-]+$") - expectedResult := &stringValidator{validRegexp} + expectedResult := &StringValidator{validRegexp} actualResult := NewFilePathStringValidator() assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) @@ -239,7 +222,7 @@ func TestNewFilePathStringValidator(t *testing.T) { func TestNewFilePathStringValidator_Evaluate(t *testing.T) { type test struct { name string - value interface{} + value any result bool } @@ -259,18 +242,16 @@ func TestNewFilePathStringValidator_Evaluate(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - stringValidator := NewFilePathStringValidator() + validator := NewFilePathStringValidator() expectedResult := test.result - actualResult, _ := stringValidator.Evaluate(test.value) + actualResult, _ := validator.Evaluate(test.value) assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) } } func TestNewWorkloadStringValidator(t *testing.T) { - t.Run("Test New Workload String Validator constructor", func(t *testing.T) { - validRegexp := regexp.MustCompile("^[A-Za-z0-9._-]+$") expectedResult := &WorkloadValidator{validRegexp, []string{"a", "b"}} actualResult := NewWorkloadStringValidator([]string{"a", "b"}) @@ -280,7 +261,7 @@ func TestNewWorkloadStringValidator(t *testing.T) { func TestWorkloadStringValidator_Evaluate(t *testing.T) { type test struct { name string - value interface{} + value any result bool } @@ -300,10 +281,9 @@ func TestWorkloadStringValidator_Evaluate(t *testing.T) { for _, test := range testTable { t.Run(test.name, func(t *testing.T) { - - stringValidator := NewWorkloadStringValidator([]string{"a", "b"}) + validator := NewWorkloadStringValidator([]string{"a", "b"}) expectedResult := test.result - _, _, actualResult, _ := stringValidator.Evaluate(test.value) + _, _, actualResult, _ := validator.Evaluate(test.value) assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) }) } diff --git a/internal/utils/version.go b/internal/routerutil/version.go similarity index 90% rename from internal/utils/version.go rename to internal/routerutil/version.go index d4a8186..aadec34 100644 --- a/internal/utils/version.go +++ b/internal/routerutil/version.go @@ -1,4 +1,4 @@ -package utils +package routerutil import ( "regexp" @@ -44,7 +44,7 @@ func (a *Version) MoreRecentThan(b Version) bool { } else if a.Minor < b.Minor { return false } - //a.Minor == b.Minor, so look at Patch + // a.Minor == b.Minor, so look at Patch return a.Patch > b.Patch } @@ -60,7 +60,7 @@ func (a *Version) LessRecentThan(b Version) bool { } else if a.Minor > b.Minor { return false } - //a.Minor == b.Minor, so look at Patch + // a.Minor == b.Minor, so look at Patch return a.Patch < b.Patch } @@ -68,8 +68,8 @@ func (a *Version) Equivalent(b Version) bool { return a.Major == b.Major && a.Minor == b.Minor && a.Patch == b.Patch } -func (v *Version) IsUndefined() bool { - return v.Major == 0 && v.Minor == 0 && v.Patch == 0 && v.Qualifier == "" +func (a *Version) IsUndefined() bool { + return a.Major == 0 && a.Minor == 0 && a.Patch == 0 && a.Qualifier == "" } func EquivalentVersion(a string, b string) bool { @@ -91,7 +91,7 @@ func MoreRecentThanVersion(a string, b string) bool { } func IsValidFor(actual string, minimum string) bool { - if actual == "" { //assume pre 0.5 + if actual == "" { // assume pre 0.5 return false } va := ParseVersion(actual) diff --git a/internal/utils/version_test.go b/internal/routerutil/version_test.go similarity index 98% rename from internal/utils/version_test.go rename to internal/routerutil/version_test.go index e3f0258..a24f2f9 100644 --- a/internal/utils/version_test.go +++ b/internal/routerutil/version_test.go @@ -1,4 +1,4 @@ -package utils +package routerutil import ( "reflect" @@ -26,7 +26,7 @@ func TestParseVersion(t *testing.T) { } for _, test := range tests { if actual := ParseVersion(test.input); !reflect.DeepEqual(actual, test.expected) { - t.Errorf("Expected %q for %s, got %q", test.expected, test.input, actual) + t.Errorf("Expected %#v for %s, got %#v", test.expected, test.input, actual) } } } From 0e02e27757a7a6e32cc859f5fa4df2454dcbf26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:41:32 +0300 Subject: [PATCH 15/22] Fix linter findings in the qdr AMQP management client. Use wrapped errors, check AMQP accept/reject/close calls, replace interface{} with any, and apply idiomatic Go naming for TCP/HTTP/ID symbols. --- internal/qdr/amqp_mgmt.go | 425 +++++++++++++++++---------------- internal/qdr/messaging.go | 57 +++-- internal/qdr/qdr.go | 372 ++++++++++++++--------------- internal/qdr/request.go | 67 +++--- internal/qdr/router_logging.go | 12 +- 5 files changed, 464 insertions(+), 469 deletions(-) diff --git a/internal/qdr/amqp_mgmt.go b/internal/qdr/amqp_mgmt.go index f3ec918..862c80b 100644 --- a/internal/qdr/amqp_mgmt.go +++ b/internal/qdr/amqp_mgmt.go @@ -3,6 +3,7 @@ package qdr import ( "context" "encoding/json" + "errors" "fmt" "log" "os" @@ -11,13 +12,13 @@ import ( "time" "github.com/eclipse-iofog/router/internal/config" - "github.com/eclipse-iofog/router/internal/resources/types" - "github.com/eclipse-iofog/router/internal/utils" + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" + utils "github.com/eclipse-iofog/router/internal/routerutil" amqp "github.com/interconnectedcloud/go-amqp" ) type RouterNode struct { - Id string `json:"id"` + ID string `json:"id"` Name string `json:"name"` NextHop string `json:"nextHop"` Address string `json:"address"` @@ -47,7 +48,7 @@ type Agent struct { } type Router struct { - Id string + ID string Address string Edge bool Site SiteMetadata @@ -56,7 +57,7 @@ type Router struct { } type SiteMetadata struct { - Id string `json:"id,omitempty"` + ID string `json:"id,omitempty"` Version string `json:"version,omitempty"` Platform string `json:"platform,omitempty"` } @@ -67,14 +68,14 @@ func GetSiteMetadata(metadata string) SiteMetadata { if err != nil { log.Printf("Assuming old format for router metadata %s: %s", metadata, err) // assume old format, where metadata just holds site id - result.Id = metadata + result.ID = metadata } return result } -func getSiteMetadataString(siteId string, version string) string { +func getSiteMetadataString(siteID string, version string) string { siteDetails := SiteMetadata{ - Id: siteId, + ID: siteID, Version: version, Platform: string(config.GetPlatform()), } @@ -86,22 +87,20 @@ type recordType interface { toRecord() Record } -type Record map[string]interface{} +type Record map[string]any func (r Record) AsString(field string) string { if value, ok := r[field].(string); ok { return value - } else { - return "" } + return "" } func (r Record) AsBool(field string) bool { if value, ok := r[field].(bool); ok { return value - } else { - return false } + return false } func (r Record) AsInt(field string) int { @@ -115,20 +114,19 @@ func (r Record) AsUint64(field string) uint64 { } func (r Record) AsRecord(field string) Record { - if value, ok := r[field].(map[string]interface{}); ok { + if value, ok := r[field].(map[string]any); ok { return value - } else { - return nil } + return nil } -func asTcpEndpoint(record Record) TcpEndpoint { - endpoint := TcpEndpoint{ +func asTCPEndpoint(record Record) TCPEndpoint { + endpoint := TCPEndpoint{ Name: record.AsString("name"), Host: record.AsString("host"), Port: record.AsString("port"), Address: record.AsString("address"), - SiteId: record.AsString("siteId"), + SiteID: record.AsString("siteID"), SslProfile: record.AsString("sslProfile"), ProcessID: record.AsString("processId"), } @@ -153,7 +151,7 @@ func asConnection(record Record) Connection { func asRouterNode(record Record) RouterNode { return RouterNode{ - Id: record.AsString("id"), + ID: record.AsString("id"), Name: record.AsString("name"), Address: record.AsString("address"), NextHop: record.AsString("nextHop"), @@ -162,35 +160,31 @@ func asRouterNode(record Record) RouterNode { func asRouter(record Record) *Router { r := Router{ - Id: record.AsString("id"), + ID: record.AsString("id"), Site: GetSiteMetadata(record.AsString("metadata")), Version: record.AsString("version"), } - if record.AsString("mode") == "edge" { - r.Edge = true - } else { - r.Edge = false - } - r.Address = GetRouterAgentAddress(r.Id, r.Edge) + r.Edge = record.AsString("mode") == "edge" + r.Address = GetRouterAgentAddress(r.ID, r.Edge) return &r } -func (node *RouterNode) AsRouter() *Router { +func (r *RouterNode) AsRouter() *Router { return &Router{ - Id: node.Id, - // SiteId ??? - Address: node.Address, - Edge: false, /*RouterNode is always an interior*/ + ID: r.ID, + // SiteID ??? + Address: r.Address, + Edge: false, // RouterNode is always an interior } } type AgentPool struct { url string - config TlsConfigRetriever + config TLSConfigRetriever pool chan *Agent } -func NewAgentPool(url string, config TlsConfigRetriever) *AgentPool { +func NewAgentPool(url string, config TLSConfigRetriever) *AgentPool { return &AgentPool{ url: url, config: config, @@ -214,12 +208,12 @@ func (p *AgentPool) Put(a *Agent) { select { case p.pool <- a: default: - a.Close() + _ = a.Close() } } } -func Connect(url string, config TlsConfigRetriever) (*Agent, error) { +func Connect(url string, config TLSConfigRetriever) (*Agent, error) { factory := ConnectionFactory{ url: url, config: config, @@ -230,26 +224,29 @@ func Connect(url string, config TlsConfigRetriever) (*Agent, error) { func newAgent(factory *ConnectionFactory) (*Agent, error) { client, err := factory.Connect() if err != nil { - return nil, fmt.Errorf("Failed to create connection: %s", err) + return nil, fmt.Errorf("failed to create connection: %w", err) + } + connection, ok := client.(*AMQPConnection) + if !ok { + return nil, fmt.Errorf("unexpected connection type %T", client) } - connection := client.(*AmqpConnection) receiver, err := connection.session.NewReceiver( amqp.LinkSourceAddress(""), amqp.LinkAddressDynamic(), amqp.LinkCredit(10), ) if err != nil { - return nil, fmt.Errorf("Failed to create receiver: %s", err) + return nil, fmt.Errorf("failed to create receiver: %w", err) } sender, err := connection.session.NewSender( amqp.LinkTargetAddress("$management"), ) if err != nil { - return nil, fmt.Errorf("Failed to create sender: %s", err) + return nil, fmt.Errorf("failed to create sender: %w", err) } anonymous, err := connection.session.NewSender() if err != nil { - return nil, fmt.Errorf("Failed to create anonymous sender: %s", err) + return nil, fmt.Errorf("failed to create anonymous sender: %w", err) } a := &Agent{ connection: connection.client, @@ -260,7 +257,7 @@ func newAgent(factory *ConnectionFactory) (*Agent, error) { } a.local, err = a.GetLocalRouter() if err != nil { - return a, fmt.Errorf("Failed to lookup local router details: %s", err) + return a, fmt.Errorf("failed to lookup local router details: %w", err) } return a, nil } @@ -281,26 +278,25 @@ func isOk(code int) bool { return code >= 200 && code < 300 } -func cleanup(input interface{}) interface{} { - switch input.(type) { - case map[interface{}]interface{}: - m := make(map[string]interface{}) - for k, v := range input.(map[interface{}]interface{}) { +func cleanup(input any) any { + switch input := input.(type) { + case map[any]any: + m := make(map[string]any) + for k, v := range input { m[k.(string)] = cleanup(v) } return m - case map[string]interface{}: - m := input.(map[string]interface{}) - for k, v := range m { - m[k] = cleanup(v) + case map[string]any: + for k, v := range input { + input[k] = cleanup(v) } - return m + return input default: return input } } -func makeRecord(fields []string, values []interface{}) Record { +func makeRecord(fields []string, values []any) Record { record := Record{} for i, name := range fields { record[name] = cleanup(values[i]) @@ -308,7 +304,7 @@ func makeRecord(fields []string, values []interface{}) Record { return record } -func stringify(items []interface{}) []string { +func stringify(items []any) []string { s := make([]string, len(items)) for i := range items { s[i] = fmt.Sprintf("%v", items[i]) @@ -319,20 +315,18 @@ func stringify(items []interface{}) []string { func GetRouterAgentAddress(id string, edge bool) string { if edge { return "amqp:/_edge/" + id + "/$management" - } else { - return "amqp:/_topo/0/" + id + "/$management" } + return "amqp:/_topo/0/" + id + "/$management" } func GetRouterAddress(id string, edge bool) string { if edge { return "amqp:/_edge/" + id - } else { - return "amqp:/_topo/0/" + id } + return "amqp:/_topo/0/" + id } -func (a *Agent) request(operation string, typename string, name string, attributes map[string]interface{}) error { +func (a *Agent) request(operation string, typename string, name string, attributes map[string]any) error { ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) defer cancel() @@ -341,7 +335,7 @@ func (a *Agent) request(operation string, typename string, name string, attribut properties.ReplyTo = a.receiver.Address() properties.CorrelationID = uint64(1) request.Properties = &properties - request.ApplicationProperties = make(map[string]interface{}) + request.ApplicationProperties = make(map[string]any) request.ApplicationProperties["operation"] = operation request.ApplicationProperties["type"] = typename request.ApplicationProperties["name"] = name @@ -350,18 +344,18 @@ func (a *Agent) request(operation string, typename string, name string, attribut } if err := a.sender.Send(ctx, &request); err != nil { - a.Close() - return fmt.Errorf("Could not send request: %s", err) + _ = a.Close() + return fmt.Errorf("could not send request: %w", err) } response, err := a.receiver.Receive(ctx) if err != nil { - a.Close() - return fmt.Errorf("Failed to receive response: %s", err) + _ = a.Close() + return fmt.Errorf("failed to receive response: %w", err) } - response.Accept() + _ = response.Accept() if status, ok := AsInt(response.ApplicationProperties["statusCode"]); !ok && !isOk(status) { - return fmt.Errorf("Query failed with: %s", response.ApplicationProperties["statusDescription"]) + return fmt.Errorf("query failed with: %s", response.ApplicationProperties["statusDescription"]) } return nil } @@ -380,7 +374,7 @@ func (a *Agent) Update(typename string, name string, entity recordType) error { func (a *Agent) Delete(typename string, name string) error { if name == "" { - return fmt.Errorf("Cannot delete entity of type %s with no name", typename) + return fmt.Errorf("cannot delete entity of type %s with no name", typename) } log.Println("DELETE", typename, name) return a.request("DELETE", typename, name, nil) @@ -398,51 +392,51 @@ func (a *Agent) QueryRouterNode(typename string, attributes []string, node *Rout return a.QueryByAgentAddress(typename, attributes, address) } -func AsInt(value interface{}) (int, bool) { - switch value.(type) { +func AsInt(value any) (int, bool) { + switch value := value.(type) { case uint8: - return int(value.(uint8)), true + return int(value), true case uint16: - return int(value.(uint16)), true + return int(value), true case uint32: - return int(value.(uint32)), true + return int(value), true case uint64: - return int(value.(uint64)), true + return int(value), true case int8: - return int(value.(int8)), true + return int(value), true case int16: - return int(value.(int16)), true + return int(value), true case int32: - return int(value.(int32)), true + return int(value), true case int64: - return int(value.(int64)), true + return int(value), true case int: - return value.(int), true + return value, true default: return 0, false } } -func AsUint64(value interface{}) (uint64, bool) { - switch value.(type) { +func AsUint64(value any) (uint64, bool) { + switch value := value.(type) { case uint8: - return uint64(value.(uint8)), true + return uint64(value), true case uint16: - return uint64(value.(uint16)), true + return uint64(value), true case uint32: - return uint64(value.(uint32)), true + return uint64(value), true case uint64: - return value.(uint64), true + return value, true case int8: - return uint64(value.(int8)), true + return uint64(value), true case int16: - return uint64(value.(int16)), true + return uint64(value), true case int32: - return uint64(value.(int32)), true + return uint64(value), true case int64: - return uint64(value.(int64)), true + return uint64(value), true case int: - return uint64(value.(int)), true + return uint64(value), true default: return 0, false } @@ -457,10 +451,10 @@ func (a *Agent) QueryByAgentAddress(typename string, attributes []string, agent properties.ReplyTo = a.receiver.Address() properties.CorrelationID = uint64(1) request.Properties = &properties - request.ApplicationProperties = make(map[string]interface{}) + request.ApplicationProperties = make(map[string]any) request.ApplicationProperties["operation"] = "QUERY" request.ApplicationProperties["entityType"] = typename - var body = make(map[string]interface{}) + var body = make(map[string]any) body["attributeNames"] = attributes request.Value = body @@ -472,32 +466,40 @@ func (a *Agent) QueryByAgentAddress(typename string, attributes []string, agent err = a.anonymous.Send(ctx, &request) } if err != nil { - a.Close() - return nil, fmt.Errorf("Could not send request: %s", err) + _ = a.Close() + return nil, fmt.Errorf("could not send request: %w", err) } response, err := a.receiver.Receive(ctx) if err != nil { - a.Close() - return nil, fmt.Errorf("Failed to receive response: %s", err) + _ = a.Close() + return nil, fmt.Errorf("failed to receive response: %w", err) } - response.Accept() + _ = response.Accept() if status, ok := AsInt(response.ApplicationProperties["statusCode"]); ok && isOk(status) { - if top, ok := response.Value.(map[string]interface{}); ok { + if top, ok := response.Value.(map[string]any); ok { records := []Record{} - fields := stringify(top["attributeNames"].([]interface{})) - results := top["results"].([]interface{}) - for _, r := range results { - o := r.([]interface{}) - records = append(records, makeRecord(fields, o)) + attrNames, ok := top["attributeNames"].([]any) + if !ok { + return nil, fmt.Errorf("bad response attribute names: %v", top["attributeNames"]) + } + fields := stringify(attrNames) + results, ok := top["results"].([]any) + if !ok { + return nil, fmt.Errorf("bad response results: %v", top["results"]) + } + for _, row := range results { + rowValues, ok := row.([]any) + if !ok { + return nil, fmt.Errorf("bad response row: %v", row) + } + records = append(records, makeRecord(fields, rowValues)) } return records, nil - } else { - return nil, fmt.Errorf("Bad response: %s", response.Value) } - } else { - return nil, fmt.Errorf("Query failed with: %s", response.ApplicationProperties["statusDescription"]) + return nil, fmt.Errorf("bad response: %s", response.Value) } + return nil, fmt.Errorf("query failed with: %s", response.ApplicationProperties["statusDescription"]) } type Query struct { @@ -541,7 +543,7 @@ func queryAllAgentsForAllTypes(typenames []string, agents []string) []Query { } func (a *Agent) BatchQuery(queries []Query) ([][]Record, error) { - fmt.Printf("BatchQuery(%v)\n", queries) + _, _ = fmt.Printf("BatchQuery(%v)\n", queries) ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) defer cancel() @@ -552,10 +554,10 @@ func (a *Agent) BatchQuery(queries []Query) ([][]Record, error) { properties.ReplyTo = a.receiver.Address() properties.CorrelationID = uint64(i) request.Properties = &properties - request.ApplicationProperties = make(map[string]interface{}) + request.ApplicationProperties = make(map[string]any) request.ApplicationProperties["operation"] = "QUERY" request.ApplicationProperties["entityType"] = q.typename - var body = make(map[string]interface{}) + var body = make(map[string]any) body["attributeNames"] = q.attributes request.Value = body @@ -567,43 +569,56 @@ func (a *Agent) BatchQuery(queries []Query) ([][]Record, error) { err = a.anonymous.Send(ctx, &request) } if err != nil { - a.Close() - return nil, fmt.Errorf("Could not send request: %s", err) + _ = a.Close() + return nil, fmt.Errorf("could not send request: %w", err) } } errors := []string{} for i := 0; i < len(queries); i++ { - fmt.Printf("Waiting for response %d of %d\n", (i + 1), len(queries)) + _, _ = fmt.Printf("Waiting for response %d of %d\n", (i + 1), len(queries)) response, err := a.receiver.Receive(ctx) if err != nil { - a.Close() - return nil, fmt.Errorf("Failed to receive response: %s", err) + _ = a.Close() + return nil, fmt.Errorf("failed to receive response: %w", err) } - response.Accept() + _ = response.Accept() responseIndex, ok := response.Properties.CorrelationID.(uint64) if !ok { errors = append(errors, fmt.Sprintf("Could not get correct correlation id from response: %#v (%T)", response.Properties.CorrelationID, response.Properties.CorrelationID)) } else { if status, ok := AsInt(response.ApplicationProperties["statusCode"]); ok && isOk(status) { - if top, ok := response.Value.(map[string]interface{}); ok { + if top, ok := response.Value.(map[string]any); ok { records := []Record{} - fields := stringify(top["attributeNames"].([]interface{})) - results := top["results"].([]interface{}) - for _, r := range results { - o := r.([]interface{}) - records = append(records, makeRecord(fields, o)) + attrNames, ok := top["attributeNames"].([]any) + if !ok { + errors = append(errors, fmt.Sprintf("bad response attribute names: %v", top["attributeNames"])) + continue + } + fields := stringify(attrNames) + results, ok := top["results"].([]any) + if !ok { + errors = append(errors, fmt.Sprintf("bad response results: %v", top["results"])) + continue + } + for _, row := range results { + rowValues, ok := row.([]any) + if !ok { + errors = append(errors, fmt.Sprintf("bad response row: %v", row)) + continue + } + records = append(records, makeRecord(fields, rowValues)) } batchResults[responseIndex] = records } else { - errors = append(errors, fmt.Sprintf("Bad response: %s", response.Value)) + errors = append(errors, fmt.Sprintf("bad response: %s", response.Value)) } } else { - errors = append(errors, fmt.Sprintf("Query failed with: %s", response.ApplicationProperties["statusDescription"])) + errors = append(errors, fmt.Sprintf("query failed with: %s", response.ApplicationProperties["statusDescription"])) } } } if len(errors) > 0 { - return nil, fmt.Errorf(strings.Join(errors, ", ")) + return nil, fmt.Errorf("%s", strings.Join(errors, ", ")) } return batchResults, nil } @@ -614,14 +629,14 @@ func (a *Agent) GetInteriorNodes() ([]RouterNode, error) { if a.isEdgeRouter() { address, err = a.getInteriorAddressForUplink() if err != nil { - return nil, fmt.Errorf("Could not determine interior agent address for edge router: %s", err) + return nil, fmt.Errorf("could not determine interior agent address for edge router: %w", err) } } records, err := a.QueryByAgentAddress("io.skupper.router.router.node", []string{}, address) if err != nil { return nil, err } - fmt.Printf("Interior nodes are %v\n", records) + _, _ = fmt.Printf("Interior nodes are %v\n", records) nodes := make([]RouterNode, len(records)) for i, r := range records { nodes[i] = asRouterNode(r) @@ -656,15 +671,15 @@ func getAddressesFor(routers []Router) []string { func getBridgeServerAddressesFor(routers []Router) []string { agents := make([]string, len(routers)) for i, r := range routers { - agents[i] = r.Id + "/bridge-server/$management" + agents[i] = r.ID + "/bridge-server/$management" } return agents } -func GetRoutersForSite(routers []Router, siteId string) []Router { +func GetRoutersForSite(routers []Router, siteID string) []Router { list := []Router{} for _, r := range routers { - if r.Site.Id == siteId { + if r.Site.ID == siteID { list = append(list, r) } } @@ -685,10 +700,8 @@ func (a *Agent) GetAllRouters() ([]Router, error) { if err != nil { return nil, err } - for _, e := range edges { - routers = append(routers, e) - } - err = a.getSiteIds(routers) + routers = append(routers, edges...) + err = a.getSiteIDs(routers) if err != nil { return nil, err } @@ -698,16 +711,14 @@ func (a *Agent) GetAllRouters() ([]Router, error) { continue } // podman svc - if strings.HasPrefix(edgeId, r.Site.Id+"-") { - return true - } else if strings.HasSuffix(edgeId, "-"+r.Site.Id) { + if strings.HasPrefix(edgeId, r.Site.ID+"-") || strings.HasSuffix(edgeId, "-"+r.Site.ID) { return true } } return false } for _, r := range routers { - if !r.Edge || !isSvcRouter(r.Site.Id) { + if !r.Edge || !isSvcRouter(r.Site.ID) { routersFiltered = append(routersFiltered, r) } } @@ -733,7 +744,7 @@ func (a *Agent) getConnectionsForAll(agents []string) ([]Connection, error) { return connections, nil } -func (a *Agent) getSiteIds(routers []Router) error { +func (a *Agent) getSiteIDs(routers []Router) error { results, err := a.BatchQuery(queryAllAgents("io.skupper.router.router", getAddressesFor(routers))) if err != nil { return err @@ -742,7 +753,7 @@ func (a *Agent) getSiteIds(routers []Router) error { if len(records) == 1 { routers[i].Site = GetSiteMetadata(records[0].AsString("metadata")) } else { - return fmt.Errorf("Unexpected number of router records: %d", len(records)) + return fmt.Errorf("unexpected number of router records: %d", len(records)) } } return nil @@ -774,12 +785,12 @@ func getBridgeTypes() []string { } } -type TcpEndpointFilter func(*TcpEndpoint) bool +type TCPEndpointFilter func(*TCPEndpoint) bool -func asTcpEndpoints(records []Record, filter TcpEndpointFilter) []TcpEndpoint { - endpoints := []TcpEndpoint{} +func asTCPEndpoints(records []Record, filter TCPEndpointFilter) []TCPEndpoint { + endpoints := []TCPEndpoint{} for _, record := range records { - endpoint := asTcpEndpoint(record) + endpoint := asTCPEndpoint(record) if filter == nil || filter(&endpoint) { endpoints = append(endpoints, endpoint) } @@ -787,23 +798,21 @@ func asTcpEndpoints(records []Record, filter TcpEndpointFilter) []TcpEndpoint { return endpoints } -func (a *Agent) getLocalTcpEndpoints(typename string, filter TcpEndpointFilter) ([]TcpEndpoint, error) { +func (a *Agent) getLocalTCPEndpoints(typename string, filter TCPEndpointFilter) ([]TCPEndpoint, error) { results, err := a.Query(typename, []string{}) if err != nil { return nil, err } - records := asTcpEndpoints(results, filter) + records := asTCPEndpoints(results, filter) return records, nil } func (a *Agent) GetConnectorByName(name string) (*Connector, error) { - results, err := a.Query("io.skupper.router.connector", []string{}) if err != nil { return nil, err } for _, record := range results { - result := asConnector(record) if result.Name == name { @@ -815,13 +824,11 @@ func (a *Agent) GetConnectorByName(name string) (*Connector, error) { } func (a *Agent) GetSslProfileByName(name string) (*SslProfile, error) { - results, err := a.Query("io.skupper.router.sslProfile", []string{}) if err != nil { return nil, err } for _, record := range results { - result := asSslProfile(record) if result.Name == name { @@ -846,12 +853,12 @@ func (a *Agent) GetSslProfiles() (map[string]SslProfile, error) { return profiles, nil } -func (a *Agent) GetLocalTcpListeners(filter TcpEndpointFilter) ([]TcpEndpoint, error) { - return a.getLocalTcpEndpoints("io.skupper.router.tcpListener", filter) +func (a *Agent) GetLocalTCPListeners(filter TCPEndpointFilter) ([]TCPEndpoint, error) { + return a.getLocalTCPEndpoints("io.skupper.router.tcpListener", filter) } -func (a *Agent) GetLocalTcpConnectors(filter TcpEndpointFilter) ([]TcpEndpoint, error) { - return a.getLocalTcpEndpoints("io.skupper.router.tcpConnector", filter) +func (a *Agent) GetLocalTCPConnectors(filter TCPEndpointFilter) ([]TCPEndpoint, error) { + return a.getLocalTCPEndpoints("io.skupper.router.tcpConnector", filter) } func (a *Agent) GetLocalBridgeConfig() (*BridgeConfig, error) { @@ -862,7 +869,7 @@ func (a *Agent) GetLocalBridgeConfig() (*BridgeConfig, error) { return nil, err } for _, record := range results { - config.AddTcpConnector(asTcpEndpoint(record)) + config.AddTCPConnector(asTCPEndpoint(record)) } results, err = a.Query("io.skupper.router.tcpListener", []string{}) @@ -870,31 +877,31 @@ func (a *Agent) GetLocalBridgeConfig() (*BridgeConfig, error) { return nil, err } for _, record := range results { - config.AddTcpListener(asTcpEndpoint(record)) + config.AddTCPListener(asTCPEndpoint(record)) } return &config, nil } func (a *Agent) UpdateLocalBridgeConfig(changes *BridgeConfigDifference) error { - for _, deleted := range changes.TcpConnectors.Deleted { + for _, deleted := range changes.TCPConnectors.Deleted { if err := a.Delete("io.skupper.router.tcpConnector", deleted); err != nil { - return fmt.Errorf("Error deleting tcp connectors: %s", err) + return fmt.Errorf("error deleting tcp connectors: %w", err) } } - for _, deleted := range changes.TcpListeners.Deleted { + for _, deleted := range changes.TCPListeners.Deleted { if err := a.Delete("io.skupper.router.tcpListener", deleted); err != nil { - return fmt.Errorf("Error deleting tcp listeners: %s", err) + return fmt.Errorf("error deleting tcp listeners: %w", err) } } - for _, added := range changes.TcpConnectors.Added { + for _, added := range changes.TCPConnectors.Added { if err := a.Create("io.skupper.router.tcpConnector", added.Name, added); err != nil { - return fmt.Errorf("Error adding tcp connectors: %s", err) + return fmt.Errorf("error adding tcp connectors: %w", err) } } - for _, added := range changes.TcpListeners.Added { + for _, added := range changes.TCPListeners.Added { if err := a.Create("io.skupper.router.tcpListener", added.Name, added); err != nil { - return fmt.Errorf("Error adding tcp listeners: %s", err) + return fmt.Errorf("error adding tcp listeners: %w", err) } } return nil @@ -911,14 +918,14 @@ func (a *Agent) GetBridges(routers []Router) ([]BridgeConfig, error) { return nil, err } for _, record := range results { - config.AddTcpConnector(asTcpEndpoint(record)) + config.AddTCPConnector(asTCPEndpoint(record)) } results, err = a.QueryByAgentAddress("io.skupper.router.tcpListener", []string{}, agent) if err != nil { return nil, err } for _, record := range results { - config.AddTcpListener(asTcpEndpoint(record)) + config.AddTCPListener(asTCPEndpoint(record)) } configs = append(configs, config) @@ -931,7 +938,7 @@ const ( DirectionOut string = "out" ) -type TcpConnection struct { +type TCPConnection struct { Name string `json:"name"` Host string `json:"host"` Address string `json:"address"` @@ -943,27 +950,27 @@ type TcpConnection struct { LastOut uint64 `json:"lastOutSeconds"` } -func getTcpConnectionsFromRecords(records []Record) ([]TcpConnection, error) { - conns := []TcpConnection{} +func getTCPConnectionsFromRecords(records []Record) ([]TCPConnection, error) { + conns := []TCPConnection{} for _, record := range records { - var conn TcpConnection + var conn TCPConnection if err := convert(record, &conn); err != nil { - return conns, fmt.Errorf("Failed to convert to TcpConnection: %s", err) + return conns, fmt.Errorf("failed to convert to TCPConnection: %w", err) } conns = append(conns, conn) } return conns, nil } -func (a *Agent) GetTcpConnections(routers []Router) ([][]TcpConnection, error) { +func (a *Agent) GetTCPConnections(routers []Router) ([][]TCPConnection, error) { queries := queryAllAgents("io.skupper.router.tcpConnection", getAddressesFor(routers)) results, err := a.BatchQuery(queries) if err != nil { return nil, err } - converted := [][]TcpConnection{} + converted := [][]TCPConnection{} for _, records := range results { - conns, err := getTcpConnectionsFromRecords(records) + conns, err := getTCPConnectionsFromRecords(records) if err != nil { return converted, err } @@ -972,12 +979,12 @@ func (a *Agent) GetTcpConnections(routers []Router) ([][]TcpConnection, error) { return converted, nil } -func (a *Agent) GetLocalTcpConnections() ([]TcpConnection, error) { +func (a *Agent) GetLocalTCPConnections() ([]TCPConnection, error) { records, err := a.Query("io.skupper.router.tcpConnection", []string{}) if err != nil { return nil, err } - return getTcpConnectionsFromRecords(records) + return getTCPConnectionsFromRecords(records) } func (a *Agent) getAllEdgeRouters(agents []string) ([]Router, error) { @@ -990,7 +997,7 @@ func (a *Agent) getAllEdgeRouters(agents []string) ([]Router, error) { for _, c := range connections { if c.Role == "edge" && c.Dir == DirectionIn { router := Router{ - Id: c.Container, + ID: c.Container, Edge: true, Address: GetRouterAddress(c.Container, true), } @@ -1009,7 +1016,7 @@ func (a *Agent) getEdgeRouters(agent string) ([]Router, error) { for _, c := range connections { if c.Role == "edge" && c.Dir == DirectionIn { router := Router{ - Id: c.Container, + ID: c.Container, Edge: true, Address: GetRouterAddress(c.Container, true), } @@ -1028,14 +1035,14 @@ func (a *Agent) GetLocalGateways() ([]Router, error) { for _, c := range connections { if c.Role == "edge" && c.Dir == DirectionIn && isGateway(c.Container) { router := Router{ - Id: c.Container, + ID: c.Container, Edge: true, Address: GetRouterAddress(c.Container, true), } gateways = append(gateways, router) } } - err = a.getSiteIds(gateways) + err = a.getSiteIDs(gateways) return gateways, err } @@ -1046,9 +1053,8 @@ func (a *Agent) GetLocalRouter() (*Router, error) { } if len(records) == 1 { return asRouter(records[0]), nil - } else { - return nil, fmt.Errorf("Unexpected number of router records: %d", len(records)) } + return nil, fmt.Errorf("unexpected number of router records: %d", len(records)) } func (a *Agent) isEdgeRouter() bool { @@ -1069,7 +1075,7 @@ func GetInteriorAddressForUplink(connections []Connection) (string, error) { return GetRouterAgentAddress(c.Container, false), nil } } - return "", fmt.Errorf("Could not find uplink connection") + return "", errors.New("could not find uplink connection") } type ConnectorStatus struct { @@ -1121,8 +1127,8 @@ func asListener(record Record) Listener { AuthenticatePeer: record.AsBool("authenticatePeer"), SaslMechanisms: record.AsString("saslMechanisms"), RouteContainer: record.AsBool("routeContainer"), - Http: record.AsBool("http"), - HttpRootDir: record.AsString("httpRootDir"), + HTTP: record.AsBool("http"), + HTTPRootDir: record.AsString("httpRootDir"), Websockets: record.AsBool("websockets"), Healthz: record.AsBool("healthz"), Metrics: record.AsBool("metrics"), @@ -1144,18 +1150,17 @@ func asSslProfile(record Record) SslProfile { func (a *Agent) UpdateConnectorConfig(changes *ConnectorDifference) error { for _, deleted := range changes.Deleted { if err := a.Delete("io.skupper.router.connector", deleted.Name); err != nil { - return fmt.Errorf("Error deleting connectors: %s", err) + return fmt.Errorf("error deleting connectors: %w", err) } } for _, added := range changes.Added { - if len(added.Host) == 0 { - return fmt.Errorf("No host specified while creating a connector") + return errors.New("no host specified while creating a connector") } if len(added.Port) == 0 { - return fmt.Errorf("No port specified while creating a connector") + return errors.New("no port specified while creating a connector") } if len(added.SslProfile) > 0 { @@ -1187,9 +1192,8 @@ func (a *Agent) UpdateConnectorConfig(changes *ConnectorDifference) error { } if err := a.Create("io.skupper.router.connector", added.Name, added); err != nil { - return fmt.Errorf("Error adding connectors: %s", err) + return fmt.Errorf("error adding connectors: %w", err) } - } return nil @@ -1224,15 +1228,14 @@ func (a *Agent) GetLocalConnectors() (map[string]Connector, error) { func (a *Agent) UpdateListenerConfig(changes *ListenerDifference) error { for _, deleted := range changes.Deleted { if err := a.Delete("io.skupper.router.listener", deleted.Name); err != nil { - return fmt.Errorf("Error deleting listeners: %s", err) + return fmt.Errorf("error deleting listeners: %w", err) } } for _, added := range changes.Added { if err := a.Create("io.skupper.router.listener", added.Name, added); err != nil { - return fmt.Errorf("Error adding listeners: %s", err) + return fmt.Errorf("error adding listeners: %w", err) } - } return nil @@ -1261,7 +1264,7 @@ func (a *Agent) Request(request *Request) (*Response, error) { Subject: request.Type, ReplyTo: a.receiver.Address(), }, - ApplicationProperties: map[string]interface{}{}, + ApplicationProperties: map[string]any{}, Value: nil, } if request.Body != "" { @@ -1274,15 +1277,15 @@ func (a *Agent) Request(request *Request) (*Response, error) { err := a.anonymous.Send(ctx, &requestMsg) if err != nil { - a.Close() - return nil, fmt.Errorf("Could not send %s request: %s", request.Type, err) + _ = a.Close() + return nil, fmt.Errorf("could not send %s request: %w", request.Type, err) } responseMsg, err := a.receiver.Receive(ctx) if err != nil { - a.Close() - return nil, fmt.Errorf("Failed to receive response: %s", err) + _ = a.Close() + return nil, fmt.Errorf("failed to receive response: %w", err) } - responseMsg.Accept() + _ = responseMsg.Accept() response := Response{ Type: responseMsg.Properties.Subject, @@ -1303,19 +1306,18 @@ func (a *Agent) Request(request *Request) (*Response, error) { } func (r *Router) IsGateway() bool { - return isGateway(r.Id) + return isGateway(r.ID) } -func isGateway(routerId string) bool { - return strings.HasPrefix(routerId, "skupper-gateway-") +func isGateway(routerID string) bool { + return strings.HasPrefix(routerID, "skupper-gateway-") } func GetSiteNameForGateway(gateway *Router) string { - return strings.TrimPrefix(gateway.Id, "skupper-gateway-") + return strings.TrimPrefix(gateway.ID, "skupper-gateway-") } func (a *Agent) CreateSslProfile(profile SslProfile) error { - result, err := a.GetSslProfileByName(profile.Name) if err != nil { return err @@ -1327,14 +1329,13 @@ func (a *Agent) CreateSslProfile(profile SslProfile) error { } if err := a.Create("io.skupper.router.sslProfile", profile.Name, profile); err != nil { - return fmt.Errorf("Error adding SSL Profile: %s", err) + return fmt.Errorf("error adding SSL Profile: %w", err) } return nil } func (a *Agent) ReloadSslProfile(name string) error { - profile, err := a.GetSslProfileByName(name) if err != nil { return err @@ -1342,21 +1343,21 @@ func (a *Agent) ReloadSslProfile(name string) error { // A profile is expected to be returned if profile == nil { - return fmt.Errorf("No SSL Profile with name %s found", name) + return fmt.Errorf("no SSL Profile with name %s found", name) } if err := a.Update("io.skupper.router.sslProfile", profile.Name, profile); err != nil { - return fmt.Errorf("Error updating SSL Profile: %s", err) + return fmt.Errorf("error updating SSL Profile: %w", err) } return nil } -func ConnectedSitesInfo(selfId string, routers []Router) types.TransportConnectedSites { +func ConnectedSitesInfo(selfID string, routers []Router) types.TransportConnectedSites { var connectedSites types.TransportConnectedSites var self *Router for _, r := range routers { - if r.Site.Id == selfId { + if r.Site.ID == selfID { self = &r break } @@ -1368,8 +1369,8 @@ func ConnectedSitesInfo(selfId string, routers []Router) types.TransportConnecte if r.Edge && len(r.ConnectedTo) > 1 { connectedSites.Warnings = append(connectedSites.Warnings, "There are edge uplinks to distinct networks, please verify topology (connected counts may not be accurate).") } - if utils.StringSliceContains(r.ConnectedTo, self.Id) { - connectedSites.Direct += 1 + if utils.StringSliceContains(r.ConnectedTo, self.ID) { + connectedSites.Direct++ } } connectedSites.Total = len(routers) - 1 diff --git a/internal/qdr/messaging.go b/internal/qdr/messaging.go index ec20ef5..4457901 100644 --- a/internal/qdr/messaging.go +++ b/internal/qdr/messaging.go @@ -9,79 +9,78 @@ import ( "github.com/eclipse-iofog/router/internal/messaging" ) -type TlsConfigRetriever interface { +type TLSConfigRetriever interface { GetTlsConfig() (*tls.Config, error) } type ConnectionFactory struct { url string - config TlsConfigRetriever + config TLSConfigRetriever } func (f *ConnectionFactory) Connect() (messaging.Connection, error) { if f.config == nil { return dial(f.url, amqp.ConnMaxFrameSize(4294967295)) - } else { - tlsConfig, err := f.config.GetTlsConfig() - if err != nil { - return nil, err - } - return dial(f.url, amqp.ConnSASLExternal(), amqp.ConnMaxFrameSize(4294967295), amqp.ConnTLSConfig(tlsConfig)) } + tlsConfig, err := f.config.GetTlsConfig() + if err != nil { + return nil, err + } + return dial(f.url, amqp.ConnSASLExternal(), amqp.ConnMaxFrameSize(4294967295), amqp.ConnTLSConfig(tlsConfig)) } -func dial(addr string, opts ...amqp.ConnOption) (*AmqpConnection, error) { +func dial(addr string, opts ...amqp.ConnOption) (*AMQPConnection, error) { client, err := amqp.Dial(addr, opts...) if err != nil { return nil, err } session, err := client.NewSession() if err != nil { - client.Close() + _ = client.Close() return nil, err } - return &AmqpConnection{client: client, session: session}, nil + return &AMQPConnection{client: client, session: session}, nil } -func (f *ConnectionFactory) Url() string { +func (f *ConnectionFactory) URL() string { return f.url } -func NewConnectionFactory(url string, config TlsConfigRetriever) *ConnectionFactory { +func NewConnectionFactory(url string, config TLSConfigRetriever) *ConnectionFactory { return &ConnectionFactory{ url: url, config: config, } } -type AmqpConnection struct { +type AMQPConnection struct { client *amqp.Client session *amqp.Session } -type AmqpSender struct { - connection *AmqpConnection +type AMQPSender struct { + connection *AMQPConnection sender *amqp.Sender } -type AmqpReceiver struct { - connection *AmqpConnection +type AMQPReceiver struct { + connection *AMQPConnection receiver *amqp.Receiver } -func (c *AmqpConnection) Close() { - c.client.Close() +func (c *AMQPConnection) Close() { + _ = c.client.Close() } -func (c *AmqpConnection) Sender(address string) (messaging.Sender, error) { +func (c *AMQPConnection) Sender(address string) (messaging.Sender, error) { sender, err := c.session.NewSender(amqp.LinkTargetAddress(address)) if err != nil { return nil, err } - return &AmqpSender{connection: c, sender: sender}, nil + return &AMQPSender{connection: c, sender: sender}, nil } -func (c *AmqpConnection) Receiver(address string, credit uint32) (messaging.Receiver, error) { +func (c *AMQPConnection) Receiver(address string, credit uint32) (messaging.Receiver, error) { receiver, err := c.session.NewReceiver( amqp.LinkSourceAddress(address), amqp.LinkCredit(credit), @@ -89,25 +88,25 @@ func (c *AmqpConnection) Receiver(address string, credit uint32) (messaging.Rece if err != nil { return nil, err } - return &AmqpReceiver{connection: c, receiver: receiver}, nil + return &AMQPReceiver{connection: c, receiver: receiver}, nil } -func (s *AmqpSender) Send(msg *amqp.Message) error { +func (s *AMQPSender) Send(msg *amqp.Message) error { return s.sender.Send(context.Background(), msg) } -func (s *AmqpSender) Close() error { +func (s *AMQPSender) Close() error { return s.sender.Close(context.Background()) } -func (s *AmqpReceiver) Receive() (*amqp.Message, error) { +func (s *AMQPReceiver) Receive() (*amqp.Message, error) { return s.receiver.Receive(context.Background()) } -func (s *AmqpReceiver) Accept(msg *amqp.Message) error { +func (s *AMQPReceiver) Accept(msg *amqp.Message) error { return msg.Accept() } -func (s *AmqpReceiver) Close() error { +func (s *AMQPReceiver) Close() error { return s.receiver.Close(context.Background()) } diff --git a/internal/qdr/qdr.go b/internal/qdr/qdr.go index d9e7c9b..659b6e8 100644 --- a/internal/qdr/qdr.go +++ b/internal/qdr/qdr.go @@ -5,12 +5,12 @@ import ( "fmt" "log" "net" - path_ "path" + "path" "reflect" "strconv" "strings" - "github.com/eclipse-iofog/router/internal/resources/types" + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" ) type RouterConfig struct { @@ -30,19 +30,19 @@ type RouterConfigHandler interface { RemoveRouterConfig() error } -type TcpEndpointMap map[string]TcpEndpoint +type TCPEndpointMap map[string]TCPEndpoint type BridgeConfig struct { - TcpListeners TcpEndpointMap - TcpConnectors TcpEndpointMap + TCPListeners TCPEndpointMap + TCPConnectors TCPEndpointMap } -func InitialConfig(id string, siteId string, version string, edge bool, helloAge int) RouterConfig { +func InitialConfig(id string, siteID string, version string, edge bool, helloAge int) RouterConfig { config := RouterConfig{ Metadata: RouterMetadata{ - Id: id, + ID: id, HelloMaxAgeSeconds: strconv.Itoa(helloAge), - Metadata: getSiteMetadataString(siteId, version), + Metadata: getSiteMetadataString(siteID, version), }, Addresses: map[string]Address{}, SslProfiles: map[string]SslProfile{}, @@ -50,8 +50,8 @@ func InitialConfig(id string, siteId string, version string, edge bool, helloAge Connectors: map[string]Connector{}, LogConfig: map[string]LogConfig{}, Bridges: BridgeConfig{ - TcpListeners: map[string]TcpEndpoint{}, - TcpConnectors: map[string]TcpEndpoint{}, + TCPListeners: map[string]TCPEndpoint{}, + TCPConnectors: map[string]TCPEndpoint{}, }, } if edge { @@ -66,8 +66,8 @@ func (r *RouterConfig) AddHealthAndMetricsListener(port int32) { r.AddListener(Listener{ Port: port, Role: "normal", - Http: true, - HttpRootDir: "disabled", + HTTP: true, + HTTPRootDir: "disabled", Websockets: false, Healthz: true, Metrics: true, @@ -76,18 +76,18 @@ func (r *RouterConfig) AddHealthAndMetricsListener(port int32) { func NewBridgeConfig() BridgeConfig { return BridgeConfig{ - TcpListeners: map[string]TcpEndpoint{}, - TcpConnectors: map[string]TcpEndpoint{}, + TCPListeners: map[string]TCPEndpoint{}, + TCPConnectors: map[string]TCPEndpoint{}, } } func NewBridgeConfigCopy(src BridgeConfig) BridgeConfig { newBridges := NewBridgeConfig() - for k, v := range src.TcpListeners { - newBridges.TcpListeners[k] = v + for k, v := range src.TCPListeners { + newBridges.TCPListeners[k] = v } - for k, v := range src.TcpConnectors { - newBridges.TcpConnectors[k] = v + for k, v := range src.TCPConnectors { + newBridges.TCPConnectors[k] = v } return newBridges } @@ -108,9 +108,8 @@ func (r *RouterConfig) RemoveListener(name string) (bool, Listener) { if ok { delete(r.Listeners, name) return true, c - } else { - return false, Listener{} } + return false, Listener{} } func (r *RouterConfig) AddConnector(c Connector) bool { @@ -126,9 +125,8 @@ func (r *RouterConfig) RemoveConnector(name string) (bool, Connector) { if ok { delete(r.Connectors, name) return true, c - } else { - return false, Connector{} } + return false, Connector{} } func (r *RouterConfig) IsEdge() bool { @@ -137,14 +135,14 @@ func (r *RouterConfig) IsEdge() bool { // ConfigureSslProfile builds an SslProfile with file paths under the given base path. // For the default SSL profile directory, use config.GetSSLProfilePath(). -func ConfigureSslProfile(name string, path string, clientAuth bool) SslProfile { +func ConfigureSslProfile(name string, basePath string, clientAuth bool) SslProfile { profile := SslProfile{ Name: name, - CaCertFile: path_.Join(path, name, "ca.crt"), + CaCertFile: path.Join(basePath, name, "ca.crt"), } if clientAuth { - profile.CertFile = path_.Join(path, name, "tls.crt") - profile.PrivateKeyFile = path_.Join(path, name, "tls.key") + profile.CertFile = path.Join(basePath, name, "tls.crt") + profile.PrivateKeyFile = path.Join(basePath, name, "tls.key") } return profile } @@ -162,9 +160,8 @@ func (r *RouterConfig) RemoveSslProfile(name string) bool { if ok { delete(r.SslProfiles, name) return true - } else { - return false } + return false } func (r *RouterConfig) RemoveUnreferencedSslProfiles() bool { @@ -183,17 +180,17 @@ func (r *RouterConfig) UnreferencedSslProfiles() map[string]SslProfile { for _, profile := range r.SslProfiles { results[profile.Name] = profile } - //remove any that are referenced + // remove any that are referenced for _, o := range r.Listeners { delete(results, o.SslProfile) } for _, o := range r.Connectors { delete(results, o.SslProfile) } - for _, o := range r.Bridges.TcpListeners { + for _, o := range r.Bridges.TCPListeners { delete(results, o.SslProfile) } - for _, o := range r.Bridges.TcpConnectors { + for _, o := range r.Bridges.TCPConnectors { delete(results, o.SslProfile) } @@ -204,29 +201,28 @@ func (r *RouterConfig) AddAddress(a Address) { r.Addresses[a.Prefix] = a } -func (r *RouterConfig) AddTcpConnector(e TcpEndpoint) { - r.Bridges.AddTcpConnector(e) +func (r *RouterConfig) AddTCPConnector(e TCPEndpoint) { + r.Bridges.AddTCPConnector(e) } -func (r *RouterConfig) RemoveTcpConnector(name string) (bool, TcpEndpoint) { - return r.Bridges.RemoveTcpConnector(name) +func (r *RouterConfig) RemoveTCPConnector(name string) (bool, TCPEndpoint) { + return r.Bridges.RemoveTCPConnector(name) } -func (r *RouterConfig) AddTcpListener(e TcpEndpoint) { - r.Bridges.AddTcpListener(e) +func (r *RouterConfig) AddTCPListener(e TCPEndpoint) { + r.Bridges.AddTCPListener(e) } -func (r *RouterConfig) RemoveTcpListener(name string) (bool, TcpEndpoint) { - return r.Bridges.RemoveTcpListener(name) +func (r *RouterConfig) RemoveTCPListener(name string) (bool, TCPEndpoint) { + return r.Bridges.RemoveTCPListener(name) } func (r *RouterConfig) UpdateBridgeConfig(desired BridgeConfig) bool { if reflect.DeepEqual(r.Bridges, desired) { return false - } else { - r.Bridges = desired - return true } + r.Bridges = desired + return true } func (r *RouterConfig) GetSiteMetadata() SiteMetadata { @@ -234,41 +230,39 @@ func (r *RouterConfig) GetSiteMetadata() SiteMetadata { } func (r *RouterConfig) SetSiteMetadata(site *SiteMetadata) { - r.Metadata.Metadata = getSiteMetadataString(site.Id, site.Version) + r.Metadata.Metadata = getSiteMetadataString(site.ID, site.Version) } -func (bc *BridgeConfig) AddTcpConnector(e TcpEndpoint) { - bc.TcpConnectors[e.Name] = e +func (bc *BridgeConfig) AddTCPConnector(e TCPEndpoint) { + bc.TCPConnectors[e.Name] = e } -func (bc *BridgeConfig) RemoveTcpConnector(name string) (bool, TcpEndpoint) { - tc, ok := bc.TcpConnectors[name] +func (bc *BridgeConfig) RemoveTCPConnector(name string) (bool, TCPEndpoint) { + tc, ok := bc.TCPConnectors[name] if ok { - delete(bc.TcpConnectors, name) + delete(bc.TCPConnectors, name) return true, tc - } else { - return false, TcpEndpoint{} } + return false, TCPEndpoint{} } -func (bc *BridgeConfig) AddTcpListener(e TcpEndpoint) { - bc.TcpListeners[e.Name] = e +func (bc *BridgeConfig) AddTCPListener(e TCPEndpoint) { + bc.TCPListeners[e.Name] = e } -func (bc *BridgeConfig) RemoveTcpListener(name string) (bool, TcpEndpoint) { - tc, ok := bc.TcpListeners[name] +func (bc *BridgeConfig) RemoveTCPListener(name string) (bool, TCPEndpoint) { + tc, ok := bc.TCPListeners[name] if ok { - delete(bc.TcpListeners, name) + delete(bc.TCPListeners, name) return true, tc - } else { - return false, TcpEndpoint{} } + return false, TCPEndpoint{} } -func GetTcpConnectors(bridges []BridgeConfig) []TcpEndpoint { - connectors := []TcpEndpoint{} +func GetTCPConnectors(bridges []BridgeConfig) []TCPEndpoint { + connectors := []TCPEndpoint{} for _, bridge := range bridges { - for _, connector := range bridge.TcpConnectors { + for _, connector := range bridge.TCPConnectors { connectors = append(connectors, connector) } } @@ -300,12 +294,11 @@ func (r *RouterConfig) SetLogLevel(module string, level string) bool { func (r *RouterConfig) SetLogLevels(levels map[string]string) bool { keys := map[string]bool{} - for k, _ := range levels { + for k := range levels { if k == "" { - keys["DEFAULT"] = true - } else { - keys[k] = true + k = "DEFAULT" } + keys[k] = true } changed := false for name, level := range levels { @@ -313,7 +306,7 @@ func (r *RouterConfig) SetLogLevels(levels map[string]string) bool { changed = true } } - for key, _ := range r.LogConfig { + for key := range r.LogConfig { if _, ok := keys[key]; !ok { delete(r.LogConfig, key) changed = true @@ -326,9 +319,9 @@ type Role string const ( RoleInterRouter Role = "inter-router" - RoleEdge = "edge" - RoleNormal = "normal" - RoleDefault = "" + RoleEdge Role = "edge" + RoleNormal Role = "normal" + RoleDefault Role = "" ) func asRole(name string) Role { @@ -345,9 +338,10 @@ func asRole(name string) Role { } func GetRole(name string) Role { - if name == "edge" { + switch name { + case "edge": return RoleEdge - } else if name == "normal" { + case "normal": return RoleNormal } return RoleInterRouter @@ -357,11 +351,11 @@ type Mode string const ( ModeInterior Mode = "interior" - ModeEdge = "edge" + ModeEdge Mode = "edge" ) type RouterMetadata struct { - Id string `json:"id,omitempty"` + ID string `json:"id,omitempty"` Mode Mode `json:"mode,omitempty"` HelloMaxAgeSeconds string `json:"helloMaxAgeSeconds,omitempty"` DataConnectionCount string `json:"dataConnectionCount,omitempty"` @@ -403,13 +397,13 @@ type Listener struct { Host string `json:"host,omitempty" yaml:"host,omitempty"` Port int32 `json:"port" yaml:"port,omitempty"` RouteContainer bool `json:"routeContainer,omitempty" yaml:"route-container,omitempty"` - Http bool `json:"http,omitempty" yaml:"http,omitempty"` + HTTP bool `json:"http,omitempty" yaml:"http,omitempty"` Cost int32 `json:"cost,omitempty" yaml:"cost,omitempty"` SslProfile string `json:"sslProfile,omitempty" yaml:"ssl-profile,omitempty"` SaslMechanisms string `json:"saslMechanisms,omitempty" yaml:"sasl-mechanisms,omitempty"` AuthenticatePeer bool `json:"authenticatePeer,omitempty" yaml:"authenticate-peer,omitempty"` LinkCapacity int32 `json:"linkCapacity,omitempty" yaml:"link-capacity,omitempty"` - HttpRootDir string `json:"httpRootDir,omitempty" yaml:"http-rootdir,omitempty"` + HTTPRootDir string `json:"httpRootDir,omitempty" yaml:"http-rootdir,omitempty"` Websockets bool `json:"websockets,omitempty" yaml:"web-sockets,omitempty"` Healthz bool `json:"healthz,omitempty" yaml:"healthz,omitempty"` Metrics bool `json:"metrics,omitempty" yaml:"metrics,omitempty"` @@ -447,11 +441,11 @@ func (listener Listener) toRecord() Record { if listener.RouteContainer { record["routeContainer"] = listener.RouteContainer } - if listener.Http { - record["http"] = listener.Http + if listener.HTTP { + record["http"] = listener.HTTP } - if len(listener.HttpRootDir) > 0 { - record["httpRootDir"] = listener.HttpRootDir + if len(listener.HTTPRootDir) > 0 { + record["httpRootDir"] = listener.HTTPRootDir } if listener.Websockets { record["websockets"] = listener.Websockets @@ -465,12 +459,12 @@ func (listener Listener) toRecord() Record { return record } -func (l *Listener) SetMaxFrameSize(value int) { - l.MaxFrameSize = value +func (listener *Listener) SetMaxFrameSize(value int) { + listener.MaxFrameSize = value } -func (l *Listener) SetMaxSessionFrames(value int) { - l.MaxSessionFrames = value +func (listener *Listener) SetMaxSessionFrames(value int) { + listener.MaxSessionFrames = value } type Connector struct { @@ -508,20 +502,20 @@ func (connector Connector) toRecord() Record { return record } -func (c *Connector) SetMaxFrameSize(value int) { - c.MaxFrameSize = value +func (connector *Connector) SetMaxFrameSize(value int) { + connector.MaxFrameSize = value } -func (c *Connector) SetMaxSessionFrames(value int) { - c.MaxSessionFrames = value +func (connector *Connector) SetMaxSessionFrames(value int) { + connector.MaxSessionFrames = value } type Distribution string const ( DistributionBalanced Distribution = "balanced" - DistributionMulticast = "multicast" - DistributionClosest = "closest" + DistributionMulticast Distribution = "multicast" + DistributionClosest Distribution = "closest" ) type Address struct { @@ -529,18 +523,18 @@ type Address struct { Distribution string `json:"distribution,omitempty"` } -type TcpEndpoint struct { +type TCPEndpoint struct { Name string `json:"name,omitempty"` Host string `json:"host,omitempty"` Port string `json:"port,omitempty"` Address string `json:"address,omitempty"` - SiteId string `json:"siteId,omitempty"` + SiteID string `json:"siteID,omitempty"` SslProfile string `json:"sslProfile,omitempty"` VerifyHostname *bool `json:"verifyHostname,omitempty"` ProcessID string `json:"processId,omitempty"` } -func (e TcpEndpoint) toRecord() Record { +func (e TCPEndpoint) toRecord() Record { result := make(map[string]any) if e.Name != "" { result["name"] = e.Name @@ -554,8 +548,8 @@ func (e TcpEndpoint) toRecord() Record { if e.Address != "" { result["address"] = e.Address } - if e.SiteId != "" { - result["siteId"] = e.SiteId + if e.SiteID != "" { + result["siteID"] = e.SiteID } if e.SslProfile != "" { result["sslProfile"] = e.SslProfile @@ -578,7 +572,7 @@ type SiteConfig struct { Version string `json:"version,omitempty"` } -func convert(from interface{}, to interface{}) error { +func convert(from any, to any) error { data, err := json.Marshal(from) if err != nil { return err @@ -611,92 +605,92 @@ func UnmarshalRouterConfig(config string) (RouterConfig, error) { Connectors: map[string]Connector{}, LogConfig: map[string]LogConfig{}, Bridges: BridgeConfig{ - TcpListeners: map[string]TcpEndpoint{}, - TcpConnectors: map[string]TcpEndpoint{}, + TCPListeners: map[string]TCPEndpoint{}, + TCPConnectors: map[string]TCPEndpoint{}, }, } - var obj interface{} + var obj any err := json.Unmarshal([]byte(config), &obj) if err != nil { return result, err } - elements, ok := obj.([]interface{}) + elements, ok := obj.([]any) if !ok { - return result, fmt.Errorf("Invalid JSON for router configuration, expected array at top level got %#v", obj) + return result, fmt.Errorf("invalid JSON for router configuration, expected array at top level got %#v", obj) } for _, e := range elements { - element, ok := e.([]interface{}) + element, ok := e.([]any) if !ok || len(element) != 2 { - return result, fmt.Errorf("Invalid JSON for router configuration, expected array with type and value got %#v", e) + return result, fmt.Errorf("invalid JSON for router configuration, expected array with type and value got %#v", e) } entityType, ok := element[0].(string) if !ok { - return result, fmt.Errorf("Invalid JSON for router configuration, expected entity type as string got %#v", element[0]) + return result, fmt.Errorf("invalid JSON for router configuration, expected entity type as string got %#v", element[0]) } switch entityType { case "router": metadata := RouterMetadata{} err = convert(element[1], &metadata) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } result.Metadata = metadata case "address": address := Address{} err = convert(element[1], &address) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } result.Addresses[address.Prefix] = address case "connector": connector := Connector{} err = convert(element[1], &connector) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } result.Connectors[connector.Name] = connector case "listener": listener := Listener{} err = convert(element[1], &listener) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } result.Listeners[listener.Name] = listener case "sslProfile": sslProfile := SslProfile{} err = convert(element[1], &sslProfile) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } result.SslProfiles[sslProfile.Name] = sslProfile case "log": logConfig := LogConfig{} err = convert(element[1], &logConfig) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } result.LogConfig[logConfig.Module] = logConfig case "site": siteConfig := &SiteConfig{} err = convert(element[1], siteConfig) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } result.SiteConfig = siteConfig case "tcpConnector": - connector := TcpEndpoint{} + connector := TCPEndpoint{} err = convert(element[1], &connector) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } - result.Bridges.TcpConnectors[connector.Name] = connector + result.Bridges.TCPConnectors[connector.Name] = connector case "tcpListener": - listener := TcpEndpoint{} + listener := TCPEndpoint{} err = convert(element[1], &listener) if err != nil { - return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + return result, fmt.Errorf("invalid %s element got %#v", entityType, element[1]) } - result.Bridges.TcpListeners[listener.Name] = listener + result.Bridges.TCPListeners[listener.Name] = listener default: } } @@ -704,63 +698,63 @@ func UnmarshalRouterConfig(config string) (RouterConfig, error) { } func MarshalRouterConfig(config RouterConfig) (string, error) { - elements := [][]interface{}{} - tuple := []interface{}{ + elements := [][]any{} + tuple := []any{ "router", config.Metadata, } elements = append(elements, tuple) for _, e := range config.SslProfiles { - tuple := []interface{}{ + tuple := []any{ "sslProfile", e, } elements = append(elements, tuple) } for _, e := range config.Connectors { - tuple := []interface{}{ + tuple := []any{ "connector", e, } elements = append(elements, tuple) } for _, e := range config.Listeners { - tuple := []interface{}{ + tuple := []any{ "listener", e, } elements = append(elements, tuple) } for _, e := range config.Addresses { - tuple := []interface{}{ + tuple := []any{ "address", e, } elements = append(elements, tuple) } - for _, e := range config.Bridges.TcpConnectors { - tuple := []interface{}{ + for _, e := range config.Bridges.TCPConnectors { + tuple := []any{ "tcpConnector", e, } elements = append(elements, tuple) } - for _, e := range config.Bridges.TcpListeners { - tuple := []interface{}{ + for _, e := range config.Bridges.TCPListeners { + tuple := []any{ "tcpListener", e, } elements = append(elements, tuple) } for _, e := range config.LogConfig { - tuple := []interface{}{ + tuple := []any{ "log", e, } elements = append(elements, tuple) } if config.SiteConfig != nil { - tuple := []interface{}{ + tuple := []any{ "site", *config.SiteConfig, } @@ -781,11 +775,11 @@ func AsConfigMapData(config string) map[string]string { func (r *RouterConfig) AsConfigMapData() (map[string]string, error) { result := map[string]string{} - marshalled, err := MarshalRouterConfig(*r) + marshaled, err := MarshalRouterConfig(*r) if err != nil { return result, err } - result[types.TransportConfigFile] = marshalled + result[types.TransportConfigFile] = marshaled return result, nil } @@ -805,8 +799,8 @@ func FilterListeners(in map[string]Listener, predicate ListenerPredicate) map[st return results } -func (config *RouterConfig) GetMatchingListeners(predicate ListenerPredicate) map[string]Listener { - return FilterListeners(config.Listeners, predicate) +func (r *RouterConfig) GetMatchingListeners(predicate ListenerPredicate) map[string]Listener { + return FilterListeners(r.Listeners, predicate) } type ConnectorDifference struct { @@ -815,14 +809,14 @@ type ConnectorDifference struct { AddedSslProfiles map[string]SslProfile } -type TcpEndpointDifference struct { +type TCPEndpointDifference struct { Deleted []string - Added []TcpEndpoint + Added []TCPEndpoint } type BridgeConfigDifference struct { - TcpListeners TcpEndpointDifference - TcpConnectors TcpEndpointDifference + TCPListeners TCPEndpointDifference + TCPConnectors TCPEndpointDifference AddedSslProfiles []string DeletedSSlProfiles []string } @@ -833,37 +827,39 @@ func isAddrAny(host string) bool { } func equivalentHost(a string, b string) bool { - if a == b { + switch a { + case b: return true - } else if a == "" { + case "": return isAddrAny(b) - } else if b == "" { - return isAddrAny(a) - } else { + default: + if b == "" { + return isAddrAny(a) + } return false } } -func (a TcpEndpoint) equivalentVerifyHostname(b TcpEndpoint) bool { - if a.VerifyHostname == nil { - return b.VerifyHostname == nil || *b.VerifyHostname == true +func (e TCPEndpoint) equivalentVerifyHostname(b TCPEndpoint) bool { + if e.VerifyHostname == nil { + return b.VerifyHostname == nil || *b.VerifyHostname } if b.VerifyHostname == nil { - return a.VerifyHostname == nil || *a.VerifyHostname == true + return e.VerifyHostname == nil || *e.VerifyHostname } - return *a.VerifyHostname == *b.VerifyHostname + return *e.VerifyHostname == *b.VerifyHostname } -func (a TcpEndpoint) Equivalent(b TcpEndpoint) bool { - if !equivalentHost(a.Host, b.Host) || a.Port != b.Port || a.Address != b.Address || - a.SiteId != b.SiteId || a.ProcessID != b.ProcessID || !a.equivalentVerifyHostname(b) { +func (e TCPEndpoint) Equivalent(b TCPEndpoint) bool { + if !equivalentHost(e.Host, b.Host) || e.Port != b.Port || e.Address != b.Address || + e.SiteID != b.SiteID || e.ProcessID != b.ProcessID || !e.equivalentVerifyHostname(b) { return false } return true } -func (a TcpEndpointMap) Difference(b TcpEndpointMap) TcpEndpointDifference { - result := TcpEndpointDifference{} +func (a TCPEndpointMap) Difference(b TCPEndpointMap) TCPEndpointDifference { + result := TCPEndpointDifference{} for key, v1 := range b { v2, ok := a[key] if !ok { @@ -882,13 +878,13 @@ func (a TcpEndpointMap) Difference(b TcpEndpointMap) TcpEndpointDifference { return result } -func (a *BridgeConfig) Difference(b *BridgeConfig) *BridgeConfigDifference { +func (bc *BridgeConfig) Difference(b *BridgeConfig) *BridgeConfigDifference { result := BridgeConfigDifference{ - TcpConnectors: a.TcpConnectors.Difference(b.TcpConnectors), - TcpListeners: a.TcpListeners.Difference(b.TcpListeners), + TCPConnectors: bc.TCPConnectors.Difference(b.TCPConnectors), + TCPListeners: bc.TCPListeners.Difference(b.TCPListeners), } - result.AddedSslProfiles, result.DeletedSSlProfiles = getSslProfilesDifference(a, b) + result.AddedSslProfiles, result.DeletedSSlProfiles = getSslProfilesDifference(bc, b) return &result } @@ -903,21 +899,21 @@ func getSslProfilesDifference(before *BridgeConfig, desired *BridgeConfig) (Adde originalSslConfig := make(map[string]string) newSslConfig := make(map[string]string) - for _, tcpConnector := range before.TcpConnectors { + for _, tcpConnector := range before.TCPConnectors { originalSslConfig[tcpConnector.SslProfile] = tcpConnector.SslProfile } - for _, tcpListener := range before.TcpListeners { + for _, tcpListener := range before.TCPListeners { originalSslConfig[tcpListener.SslProfile] = tcpListener.SslProfile } - for _, tcpConnector := range desired.TcpConnectors { + for _, tcpConnector := range desired.TCPConnectors { newSslConfig[tcpConnector.SslProfile] = tcpConnector.SslProfile } - for _, tcpListener := range desired.TcpListeners { + for _, tcpListener := range desired.TCPListeners { newSslConfig[tcpListener.SslProfile] = tcpListener.SslProfile } - //Auto-generated Skupper certs will be deleted if they are not used in the desired configuration + // Auto-generated Skupper certs will be deleted if they are not used in the desired configuration for key, name := range originalSslConfig { _, ok := newSslConfig[key] @@ -926,7 +922,7 @@ func getSslProfilesDifference(before *BridgeConfig, desired *BridgeConfig) (Adde } } - //New profiles associated with http or tcp connectors/listeners will be created in the router + // New profiles associated with http or tcp connectors/listeners will be created in the router for key, name := range newSslConfig { _, ok := originalSslConfig[key] @@ -942,17 +938,17 @@ func isGeneratedBySkupper(name string) bool { return strings.HasPrefix(name, types.SkupperServiceCertPrefix) && name != types.ServiceClientSecret } -func (a *TcpEndpointDifference) Empty() bool { +func (a *TCPEndpointDifference) Empty() bool { return len(a.Deleted) == 0 && len(a.Added) == 0 } func (a *BridgeConfigDifference) Empty() bool { - return a.TcpConnectors.Empty() && a.TcpListeners.Empty() + return a.TCPConnectors.Empty() && a.TCPListeners.Empty() } func (a *BridgeConfigDifference) Print() { - log.Printf("TcpConnectors added=%v, deleted=%v", a.TcpConnectors.Added, a.TcpConnectors.Deleted) - log.Printf("TcpListeners added=%v, deleted=%v", a.TcpListeners.Added, a.TcpListeners.Deleted) + log.Printf("TCPConnectors added=%v, deleted=%v", a.TCPConnectors.Added, a.TCPConnectors.Deleted) + log.Printf("TCPListeners added=%v, deleted=%v", a.TCPListeners.Added, a.TCPListeners.Deleted) log.Printf("SslProfiles added=%v, deleted=%v", a.AddedSslProfiles, a.DeletedSSlProfiles) } @@ -990,24 +986,24 @@ type ListenerDifference struct { Added []Listener } -func (desired Listener) Equivalent(actual Listener) bool { - return desired.Name == actual.Name && - desired.Role == actual.Role && - desired.Host == actual.Host && - desired.Port == actual.Port && - desired.RouteContainer == actual.RouteContainer && - desired.Http == actual.Http && - desired.SslProfile == actual.SslProfile && - desired.SaslMechanisms == actual.SaslMechanisms && - desired.AuthenticatePeer == actual.AuthenticatePeer && - (desired.Cost == 0 || desired.Cost == actual.Cost) && - (desired.MaxFrameSize == 0 || desired.MaxFrameSize == actual.MaxFrameSize) && - (desired.MaxSessionFrames == 0 || desired.MaxSessionFrames == actual.MaxSessionFrames) && - (desired.LinkCapacity == 0 || desired.LinkCapacity == actual.LinkCapacity) && - (desired.HttpRootDir == "" || desired.HttpRootDir == actual.HttpRootDir) - //Skip check for Websockets, Healthz and Metrics as they are - //always coming back as true at present and are not used where - //this method is required at present. +func (listener Listener) Equivalent(actual Listener) bool { + return listener.Name == actual.Name && + listener.Role == actual.Role && + listener.Host == actual.Host && + listener.Port == actual.Port && + listener.RouteContainer == actual.RouteContainer && + listener.HTTP == actual.HTTP && + listener.SslProfile == actual.SslProfile && + listener.SaslMechanisms == actual.SaslMechanisms && + listener.AuthenticatePeer == actual.AuthenticatePeer && + (listener.Cost == 0 || listener.Cost == actual.Cost) && + (listener.MaxFrameSize == 0 || listener.MaxFrameSize == actual.MaxFrameSize) && + (listener.MaxSessionFrames == 0 || listener.MaxSessionFrames == actual.MaxSessionFrames) && + (listener.LinkCapacity == 0 || listener.LinkCapacity == actual.LinkCapacity) && + (listener.HTTPRootDir == "" || listener.HTTPRootDir == actual.HTTPRootDir) + // Skip check for Websockets, Healthz and Metrics as they are + // always coming back as true at present and are not used where + // this method is required at present. } func ListenersDifference(actual map[string]Listener, desired map[string]Listener) *ListenerDifference { @@ -1036,8 +1032,8 @@ func (a *ListenerDifference) Empty() bool { return len(a.Deleted) == 0 && len(a.Added) == 0 } -// func GetRouterConfigForHeadlessProxy(definition types.ServiceInterface, siteId string, version string, namespace string, profilePath string) (string, error) { -// config := InitialConfig("${HOSTNAME}-"+siteId, siteId, version, true, 3) +// func GetRouterConfigForHeadlessProxy(definition types.ServiceInterface, siteID string, version string, namespace string, profilePath string) (string, error) { +// config := InitialConfig("${HOSTNAME}-"+siteID, siteID, version, true, 3) // // add edge-connector // config.AddSslProfile(ConfigureSslProfile(types.InterRouterProfile, profilePath, true)) // config.AddConnector(Connector{ @@ -1069,12 +1065,12 @@ func (a *ListenerDifference) Empty() bool { // // in the originating site, just have egress bindings // switch definition.Protocol { // case "tcp": -// config.AddTcpConnector(TcpEndpoint{ +// config.AddTCPConnector(TCPEndpoint{ // Name: name, // Host: host, // Port: strconv.Itoa(ePort), // Address: address, -// SiteId: siteId, +// SiteID: siteID, // }) // default: // } @@ -1083,11 +1079,11 @@ func (a *ListenerDifference) Empty() bool { // // in all other sites, just have ingress bindings // switch definition.Protocol { // case "tcp": -// config.AddTcpListener(TcpEndpoint{ +// config.AddTCPListener(TCPEndpoint{ // Name: name, // Port: strconv.Itoa(iPort), // Address: address, -// SiteId: siteId, +// SiteID: siteID, // }) // default: // } diff --git a/internal/qdr/request.go b/internal/qdr/request.go index 6ef44bb..0e9e90d 100644 --- a/internal/qdr/request.go +++ b/internal/qdr/request.go @@ -15,14 +15,14 @@ type Request struct { Address string Type string Version string - Properties map[string]interface{} + Properties map[string]any Body string } type Response struct { Type string Version string - Properties map[string]interface{} + Properties map[string]any Body string } @@ -45,18 +45,18 @@ func NewRequestServer(address string, handler RequestResponse, pool *AgentPool) func (s *RequestServer) Run(ctx context.Context) error { agent, err := s.pool.Get() if err != nil { - return fmt.Errorf("Could not get management agent: %s", err) + return fmt.Errorf("could not get management agent: %w", err) } defer agent.Close() receiver, err := agent.newReceiver(s.address) if err != nil { - return fmt.Errorf("Could not open receiver for %s: %s", s.address, err) + return fmt.Errorf("could not open receiver for %s: %w", s.address, err) } for { err = s.serve(ctx, receiver, agent.anonymous) if err != nil { - return fmt.Errorf("Error handling request for %s: %s", s.address, err) + return fmt.Errorf("error handling request for %s: %w", s.address, err) } } } @@ -65,13 +65,13 @@ func (s *RequestServer) serve(ctx context.Context, receiver *amqp.Receiver, send for { requestMsg, err := receiver.Receive(ctx) if err != nil { - return fmt.Errorf("Failed reading request from %s: %s", s.address, err.Error()) + return fmt.Errorf("failed reading request from %s: %s", s.address, err.Error()) } request := Request{ Address: requestMsg.Properties.To, Type: requestMsg.Properties.Subject, - Properties: map[string]interface{}{}, + Properties: map[string]any{}, } for k, v := range requestMsg.ApplicationProperties { if k == VersionProperty { @@ -88,38 +88,37 @@ func (s *RequestServer) serve(ctx context.Context, receiver *amqp.Receiver, send response, err := s.handler.Request(&request) if err != nil { - requestMsg.Reject(&amqp.Error{ + _ = requestMsg.Reject(&amqp.Error{ Condition: amqp.ErrorInternalError, Description: err.Error(), }) return err - } else { - requestMsg.Accept() - responseMsg := amqp.Message{ - Properties: &amqp.MessageProperties{ - To: requestMsg.Properties.ReplyTo, - Subject: response.Type, - }, - ApplicationProperties: map[string]interface{}{}, - Value: response.Body, - } - correlationId, ok := AsUint64(requestMsg.Properties.CorrelationID) - if !ok { - responseMsg.Properties.CorrelationID = correlationId - } - for k, v := range response.Properties { - responseMsg.ApplicationProperties[k] = v - } - responseMsg.ApplicationProperties[VersionProperty] = response.Version + } + _ = requestMsg.Accept() + responseMsg := amqp.Message{ + Properties: &amqp.MessageProperties{ + To: requestMsg.Properties.ReplyTo, + Subject: response.Type, + }, + ApplicationProperties: map[string]any{}, + Value: response.Body, + } + correlationID, ok := AsUint64(requestMsg.Properties.CorrelationID) + if !ok { + responseMsg.Properties.CorrelationID = correlationID + } + for k, v := range response.Properties { + responseMsg.ApplicationProperties[k] = v + } + responseMsg.ApplicationProperties[VersionProperty] = response.Version - err = sender.Send(ctx, &responseMsg) - if err != nil { - requestMsg.Reject(&amqp.Error{ - Condition: amqp.ErrorInternalError, - Description: "Could not send response: " + err.Error(), - }) - return fmt.Errorf("Could not send response: %s", err) - } + err = sender.Send(ctx, &responseMsg) + if err != nil { + _ = requestMsg.Reject(&amqp.Error{ + Condition: amqp.ErrorInternalError, + Description: "Could not send response: " + err.Error(), + }) + return fmt.Errorf("could not send response: %w", err) } } } diff --git a/internal/qdr/router_logging.go b/internal/qdr/router_logging.go index 120d479..a7a72a9 100644 --- a/internal/qdr/router_logging.go +++ b/internal/qdr/router_logging.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/eclipse-iofog/router/internal/resources/types" + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" ) func RouterLogConfigToString(config []types.RouterLogConfig) string { @@ -71,8 +71,8 @@ func ParseRouterLogConfig(config string) ([]types.RouterLogConfig, error) { return parsed, nil } -var LoggingModules []string = []string{ - "", /*implies DEFAULT*/ +var LoggingModules = []string{ + "", // implies DEFAULT "ROUTER", "ROUTER_CORE", "ROUTER_HELLO", @@ -93,7 +93,7 @@ var LoggingModules []string = []string{ "HTTP_ADAPTOR", "DEFAULT", } -var LoggingLevels []string = []string{ +var LoggingLevels = []string{ "trace", "debug", "info", @@ -116,7 +116,7 @@ func checkLoggingModule(mod string) error { return nil } } - return fmt.Errorf("Invalid logging module for router: %s", mod) + return fmt.Errorf("invalid logging module for router: %s", mod) } func checkLoggingLevel(level string) error { @@ -125,5 +125,5 @@ func checkLoggingLevel(level string) error { return nil } } - return fmt.Errorf("Invalid logging level for router: %s", level) + return fmt.Errorf("invalid logging level for router: %s", level) } From 3f315fd285a706c5d1588c567585bb3a826e69cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:41:39 +0300 Subject: [PATCH 16/22] Fix linter findings in config, router startup, and file watchers. Return errors from Kubernetes and pot run paths instead of log.Fatal in helpers, update imports for renamed packages, and rename WatchSSLProfileDir to SSLProfileDir. --- internal/config/env.go | 2 +- internal/config/env_test.go | 10 ++--- internal/config/platform.go | 23 +++------- internal/config/platform_test.go | 10 ++--- internal/exec/exec.go | 21 ++++----- internal/router/router.go | 74 ++++++++++++++++---------------- internal/watch/config.go | 6 +-- internal/watch/ssl.go | 6 +-- internal/watch/ssl_test.go | 6 +-- main.go | 44 +++++++++++-------- 10 files changed, 99 insertions(+), 103 deletions(-) diff --git a/internal/config/env.go b/internal/config/env.go index 4a263ab..639e66b 100644 --- a/internal/config/env.go +++ b/internal/config/env.go @@ -3,7 +3,7 @@ package config import ( "os" - "github.com/eclipse-iofog/router/internal/resources/types" + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" ) const ( diff --git a/internal/config/env_test.go b/internal/config/env_test.go index 3907b02..29d7d28 100644 --- a/internal/config/env_test.go +++ b/internal/config/env_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/eclipse-iofog/router/internal/resources/types" + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" ) func TestGetConfigPath(t *testing.T) { @@ -12,14 +12,14 @@ func TestGetConfigPath(t *testing.T) { defer func() { _ = os.Unsetenv(key) }() // Default when unset - os.Unsetenv(key) + _ = os.Unsetenv(key) if got := GetConfigPath(); got != DefaultConfigPath { t.Errorf("GetConfigPath() with unset env = %q, want %q", got, DefaultConfigPath) } // Uses env when set want := "/custom/skrouterd.json" - os.Setenv(key, want) + _ = os.Setenv(key, want) if got := GetConfigPath(); got != want { t.Errorf("GetConfigPath() with env set = %q, want %q", got, want) } @@ -30,14 +30,14 @@ func TestGetSSLProfilePath(t *testing.T) { defer func() { _ = os.Unsetenv(key) }() // Default when unset - os.Unsetenv(key) + _ = os.Unsetenv(key) if got := GetSSLProfilePath(); got != DefaultSSLProfilePath { t.Errorf("GetSSLProfilePath() with unset env = %q, want %q", got, DefaultSSLProfilePath) } // Uses env when set want := "/custom/certs" - os.Setenv(key, want) + _ = os.Setenv(key, want) if got := GetSSLProfilePath(); got != want { t.Errorf("GetSSLProfilePath() with env set = %q, want %q", got, want) } diff --git a/internal/config/platform.go b/internal/config/platform.go index 491a67b..74c2e17 100644 --- a/internal/config/platform.go +++ b/internal/config/platform.go @@ -5,8 +5,8 @@ import ( "slices" "strings" - "github.com/eclipse-iofog/router/internal/resources/types" - "github.com/eclipse-iofog/router/internal/utils" + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" + utils "github.com/eclipse-iofog/router/internal/routerutil" "k8s.io/utils/ptr" ) @@ -46,21 +46,12 @@ func GetPlatform() types.Platform { } if platform == "" { platform = types.Platform(utils.DefaultStr(Platform, - os.Getenv(types.ENV_PLATFORM), + os.Getenv(types.EnvPlatform), string(types.PlatformKubernetes))) } switch platform { - case types.PlatformPodman: - configuredPlatform = &platform - case types.PlatformDocker: - configuredPlatform = &platform - case types.PlatformLinux: - configuredPlatform = &platform - case types.PlatformPot: - configuredPlatform = &platform - case types.PlatformIoFog: - configuredPlatform = &platform - case types.PlatformKubernetes: + case types.PlatformPodman, types.PlatformDocker, types.PlatformLinux, + types.PlatformPot, types.PlatformIoFog, types.PlatformKubernetes: configuredPlatform = &platform default: configuredPlatform = ptr.To(types.PlatformKubernetes) @@ -72,11 +63,11 @@ func GetPlatform() types.Platform { // (router config from ConfigMap). Default is pot (config from iofog SDK). // SKUPPER_PLATFORM=pot or iofog both use SDK LocalAPI v3 mode. func IsKubernetesRouterMode() bool { - return os.Getenv(types.ENV_PLATFORM) == string(types.PlatformKubernetes) + return os.Getenv(types.EnvPlatform) == string(types.PlatformKubernetes) } // IsPotRouterMode returns true when SKUPPER_PLATFORM is unset, "pot", or "iofog". func IsPotRouterMode() bool { - platform := os.Getenv(types.ENV_PLATFORM) + platform := os.Getenv(types.EnvPlatform) return platform == "" || platform == string(types.PlatformPot) || platform == string(types.PlatformIoFog) } diff --git a/internal/config/platform_test.go b/internal/config/platform_test.go index 79d0d2e..35c106a 100644 --- a/internal/config/platform_test.go +++ b/internal/config/platform_test.go @@ -4,11 +4,11 @@ import ( "os" "testing" - "github.com/eclipse-iofog/router/internal/resources/types" + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" ) func TestIsKubernetesRouterMode(t *testing.T) { - key := types.ENV_PLATFORM + key := types.EnvPlatform defer func() { _ = os.Unsetenv(key) }() tests := []struct { @@ -37,7 +37,7 @@ func TestIsKubernetesRouterMode(t *testing.T) { } func TestIsPotRouterMode(t *testing.T) { - key := types.ENV_PLATFORM + key := types.EnvPlatform defer func() { _ = os.Unsetenv(key) }() tests := []struct { @@ -66,13 +66,13 @@ func TestIsPotRouterMode(t *testing.T) { } func TestGetPlatformAcceptsIoFog(t *testing.T) { - key := types.ENV_PLATFORM + key := types.EnvPlatform defer func() { _ = os.Unsetenv(key) ClearPlatform() }() - os.Setenv(key, "iofog") + _ = os.Setenv(key, "iofog") ClearPlatform() if got := GetPlatform(); got != types.PlatformIoFog { t.Errorf("GetPlatform() with iofog = %q, want %q", got, types.PlatformIoFog) diff --git a/internal/exec/exec.go b/internal/exec/exec.go index ed6d2dd..149a351 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -3,45 +3,46 @@ package exec import ( "bufio" "fmt" - "log" "os" "os/exec" ) func Run(ch chan<- error, command string, args []string, env []string) { - // log.Printf("Running command: %s with args: %v and env vars: %v", command, args, env) - cmd := exec.Command(command, args...) cmd.Env = append(os.Environ(), env...) outReader, err := cmd.StdoutPipe() if err != nil { - log.Fatal(err) + ch <- err + return } outScanner := bufio.NewScanner(outReader) go func() { for outScanner.Scan() { - fmt.Println(outScanner.Text()) + _, _ = fmt.Println(outScanner.Text()) } }() errReader, err := cmd.StderrPipe() if err != nil { - log.Fatal(err) + ch <- err + return } errScanner := bufio.NewScanner(errReader) go func() { for errScanner.Scan() { - fmt.Println(errScanner.Text()) + _, _ = fmt.Println(errScanner.Text()) } }() if err := cmd.Start(); err != nil { - log.Fatal(err) + ch <- err + return } if err := cmd.Wait(); err != nil { - log.Fatal(err) + ch <- err + return } - ch <- err + ch <- nil } diff --git a/internal/router/router.go b/internal/router/router.go index b75b01a..e40c1f2 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -28,58 +28,58 @@ type Router struct { Config *Config } -func (router *Router) UpdateRouter(newConfig *Config) error { - log.Printf("DEBUG: Starting router configuration update") +func (r *Router) UpdateRouter(newConfig *Config) error { + log.Print("DEBUG: Starting router configuration update") // Create agent pool and get client - log.Printf("DEBUG: Creating agent pool") + log.Print("DEBUG: Creating agent pool") agentPool := qdr.NewAgentPool("amqp://localhost:5672", nil) client, err := agentPool.Get() if err != nil { log.Printf("ERROR: Failed to get client from pool: %v", err) - return fmt.Errorf("failed to get client from pool: %v", err) + return fmt.Errorf("failed to get client from pool: %w", err) } // Get current bridge configuration - log.Printf("DEBUG: Getting current bridge configuration") + log.Print("DEBUG: Getting current bridge configuration") currentBridgeConfig, err := client.GetLocalBridgeConfig() if err != nil { log.Printf("ERROR: Failed to get current bridge config: %v", err) - return fmt.Errorf("failed to get current bridge config: %v", err) + return fmt.Errorf("failed to get current bridge config: %w", err) } // Calculate differences using qdr's built-in Difference method - log.Printf("DEBUG: Calculating bridge configuration differences") + log.Print("DEBUG: Calculating bridge configuration differences") changes := currentBridgeConfig.Difference(&newConfig.Bridges) log.Printf("DEBUG: Bridge config changes: %+v", changes) // Update via AMQP management using qdr's built-in function - log.Printf("DEBUG: Updating bridge configuration") + log.Print("DEBUG: Updating bridge configuration") if err := client.UpdateLocalBridgeConfig(changes); err != nil { log.Printf("ERROR: Failed to update bridge config: %v", err) - return fmt.Errorf("failed to update bridge config: %v", err) + return fmt.Errorf("failed to update bridge config: %w", err) } // Update the configuration file (skip on Kubernetes; config is read-only from ConfigMap) if !config.IsKubernetesRouterMode() { - log.Printf("DEBUG: Updating router configuration file") - configJSON := router.GetRouterConfig() + log.Print("DEBUG: Updating router configuration file") + configJSON := r.GetRouterConfig() configPath := config.GetConfigPath() if err := os.WriteFile(configPath, []byte(configJSON), 0644); err != nil { log.Printf("ERROR: Failed to write router configuration: %v", err) - return fmt.Errorf("failed to write router configuration: %v", err) + return fmt.Errorf("failed to write router configuration: %w", err) } } // Update the in-memory configuration - router.Config = newConfig + r.Config = newConfig // Return client to the pool instead of closing it if client != nil { agentPool.Put(client) } - log.Printf("DEBUG: Router configuration update completed successfully") + log.Print("DEBUG: Router configuration update completed successfully") return nil } @@ -116,19 +116,19 @@ func (r *Router) OnSSLProfilesFromDisk(profiles map[string]qdr.SslProfile) { } } -func (router *Router) GetRouterConfig() string { - config := router.Config - configElements := [][]interface{}{} +func (r *Router) GetRouterConfig() string { + config := r.Config + configElements := [][]any{} // Add router metadata - configElements = append(configElements, []interface{}{ + configElements = append(configElements, []any{ "router", config.Metadata, }) // Add SSL profiles (file paths are already absolute) for _, profile := range config.SslProfiles { - configElements = append(configElements, []interface{}{ + configElements = append(configElements, []any{ "sslProfile", profile, }) @@ -136,7 +136,7 @@ func (router *Router) GetRouterConfig() string { // Add listeners for _, listener := range config.Listeners { - configElements = append(configElements, []interface{}{ + configElements = append(configElements, []any{ "listener", listener, }) @@ -144,23 +144,23 @@ func (router *Router) GetRouterConfig() string { // Add connectors for _, connector := range config.Connectors { - configElements = append(configElements, []interface{}{ + configElements = append(configElements, []any{ "connector", connector, }) } // Add TCP listeners - for _, listener := range config.Bridges.TcpListeners { - configElements = append(configElements, []interface{}{ + for _, listener := range config.Bridges.TCPListeners { + configElements = append(configElements, []any{ "tcpListener", listener, }) } // Add TCP connectors - for _, connector := range config.Bridges.TcpConnectors { - configElements = append(configElements, []interface{}{ + for _, connector := range config.Bridges.TCPConnectors { + configElements = append(configElements, []any{ "tcpConnector", connector, }) @@ -168,7 +168,7 @@ func (router *Router) GetRouterConfig() string { // Add addresses for _, address := range config.Addresses { - configElements = append(configElements, []interface{}{ + configElements = append(configElements, []any{ "address", address, }) @@ -176,7 +176,7 @@ func (router *Router) GetRouterConfig() string { // Add log configs for _, logConfig := range config.LogConfig { - configElements = append(configElements, []interface{}{ + configElements = append(configElements, []any{ "log", logConfig, }) @@ -184,7 +184,7 @@ func (router *Router) GetRouterConfig() string { // Add site config if present if config.SiteConfig != nil { - configElements = append(configElements, []interface{}{ + configElements = append(configElements, []any{ "site", *config.SiteConfig, }) @@ -200,26 +200,26 @@ func (router *Router) GetRouterConfig() string { return string(data) } -func (router *Router) StartRouter(ch chan<- error) { - log.Printf("DEBUG: Starting router with configuration") +func (r *Router) StartRouter(ch chan<- error) { + log.Print("DEBUG: Starting router with configuration") configPath := config.GetConfigPath() // On Pot we create and write initial config; on Kubernetes config is already mounted at QDROUTERD_CONF if !config.IsKubernetesRouterMode() { - log.Printf("DEBUG: Creating initial router configuration") - configJSON := router.GetRouterConfig() + log.Print("DEBUG: Creating initial router configuration") + configJSON := r.GetRouterConfig() - log.Printf("DEBUG: Ensuring configuration directory exists") + log.Print("DEBUG: Ensuring configuration directory exists") if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { log.Printf("ERROR: Failed to create configuration directory: %v", err) - ch <- fmt.Errorf("failed to create configuration directory: %v", err) + ch <- fmt.Errorf("failed to create configuration directory: %w", err) return } log.Printf("DEBUG: Writing initial configuration to %s", configPath) if err := os.WriteFile(configPath, []byte(configJSON), 0644); err != nil { log.Printf("ERROR: Failed to write initial configuration: %v", err) - ch <- fmt.Errorf("failed to write initial configuration: %v", err) + ch <- fmt.Errorf("failed to write initial configuration: %w", err) return } } @@ -230,7 +230,7 @@ func (router *Router) StartRouter(ch chan<- error) { "QDROUTERD_CONF_TYPE=json", } exitChannel := make(chan error) - log.Printf("DEBUG: Starting router process") + log.Print("DEBUG: Starting router process") go exec.Run(ch, "/home/skrouterd/bin/launch.sh", []string{}, env) // Monitor for configuration updates @@ -239,7 +239,7 @@ func (router *Router) StartRouter(ch chan<- error) { select { case err := <-exitChannel: log.Printf("ERROR: Router process exited with error: %v", err) - ch <- fmt.Errorf("router process exited with error: %v", err) + ch <- fmt.Errorf("router process exited with error: %w", err) return default: // Check for configuration updates from ioFog-agent diff --git a/internal/watch/config.go b/internal/watch/config.go index 41724d4..a4b1c69 100644 --- a/internal/watch/config.go +++ b/internal/watch/config.go @@ -13,11 +13,11 @@ import ( const configDebounceDuration = 500 * time.Millisecond -// WatchConfigFile watches the config file at configPath for changes. On write/create +// ConfigFile watches the config file at configPath for changes. On write/create // (after debounce), it reads the file and calls onUpdate with the content. Loop // prevention is the caller's responsibility: compare content with last applied and -// skip calling UpdateRouter if unchanged. Runs until ctx is cancelled. -func WatchConfigFile(ctx context.Context, configPath string, onUpdate func(configJSON string) error) { +// skip calling UpdateRouter if unchanged. Runs until ctx is canceled. +func ConfigFile(ctx context.Context, configPath string, onUpdate func(configJSON string) error) { watcher, err := fsnotify.NewWatcher() if err != nil { log.Printf("ERROR: Failed to create fsnotify watcher for config file: %v", err) diff --git a/internal/watch/ssl.go b/internal/watch/ssl.go index 337fb2c..6bcd785 100644 --- a/internal/watch/ssl.go +++ b/internal/watch/ssl.go @@ -54,9 +54,9 @@ func ScanSSLProfileDir(basePath string) (map[string]qdr.SslProfile, error) { return profiles, nil } -// WatchSSLProfileDir watches basePath (and subdirs) for changes, debounces events, -// then rescans and calls onUpdate with the new profiles map. Runs until ctx is cancelled. -func WatchSSLProfileDir(ctx context.Context, basePath string, onUpdate func(profiles map[string]qdr.SslProfile)) { +// SSLProfileDir watches basePath (and subdirs) for changes, debounces events, +// then rescans and calls onUpdate with the new profiles map. Runs until ctx is canceled. +func SSLProfileDir(ctx context.Context, basePath string, onUpdate func(profiles map[string]qdr.SslProfile)) { watcher, err := fsnotify.NewWatcher() if err != nil { log.Printf("ERROR: Failed to create fsnotify watcher for SSL profile path: %v", err) diff --git a/internal/watch/ssl_test.go b/internal/watch/ssl_test.go index ccd42f2..cad193a 100644 --- a/internal/watch/ssl_test.go +++ b/internal/watch/ssl_test.go @@ -4,8 +4,6 @@ import ( "os" "path/filepath" "testing" - - "github.com/eclipse-iofog/router/internal/qdr" ) func TestScanSSLProfileDir(t *testing.T) { @@ -47,7 +45,7 @@ func TestScanSSLProfileDir(t *testing.T) { t.Errorf("CaCertFile = %q, want %q", p.CaCertFile, absCa) } if p.CertFile != "" || p.PrivateKeyFile != "" { - t.Errorf("expected no cert/key when only ca.crt present") + t.Error("expected no cert/key when only ca.crt present") } // Subdir with ca.crt, tls.crt, tls.key @@ -105,7 +103,7 @@ func TestScanSSLProfileDir_ProfilesAreQdrSslProfile(t *testing.T) { if err != nil { t.Fatal(err) } - var _ map[string]qdr.SslProfile = profiles + var _ = profiles if len(profiles) != 1 { t.Fatalf("got %d profiles", len(profiles)) } diff --git a/main.go b/main.go index 14ff9ae..4ad85f4 100644 --- a/main.go +++ b/main.go @@ -29,21 +29,25 @@ func init() { Addresses: make(map[string]qdr.Address), LogConfig: make(map[string]qdr.LogConfig), Bridges: qdr.BridgeConfig{ - TcpListeners: make(map[string]qdr.TcpEndpoint), - TcpConnectors: make(map[string]qdr.TcpEndpoint), + TCPListeners: make(map[string]qdr.TCPEndpoint), + TCPConnectors: make(map[string]qdr.TCPEndpoint), }, } } func main() { + var err error if config.IsKubernetesRouterMode() { - runKubernetesMode() - return + err = runKubernetesMode() + } else { + err = runPotMode() + } + if err != nil { + log.Fatal(err) } - runPotMode() } -func runKubernetesMode() { +func runKubernetesMode() error { configPath := config.GetConfigPath() // Config file is volume-mounted by the operator at QDROUTERD_CONF; retry briefly if not yet present. var data []byte @@ -57,11 +61,13 @@ func runKubernetesMode() { time.Sleep(time.Second) continue } - log.Fatalf("Failed to read router config from %s: %v", configPath, err) + log.Printf("Failed to read router config from %s: %v", configPath, err) + return fmt.Errorf("failed to read router config from %s: %w", configPath, err) } qdrConfig, err := qdr.UnmarshalRouterConfig(string(data)) if err != nil { - log.Fatalf("Failed to unmarshal router config: %v", err) + log.Printf("Failed to unmarshal router config: %v", err) + return fmt.Errorf("failed to unmarshal router config: %w", err) } router.Config = &rt.Config{ Metadata: qdrConfig.Metadata, @@ -78,7 +84,7 @@ func runKubernetesMode() { ctx := context.Background() var lastAppliedMu sync.Mutex lastApplied := string(data) - go watch.WatchConfigFile(ctx, configPath, func(configJSON string) error { + go watch.ConfigFile(ctx, configPath, func(configJSON string) error { lastAppliedMu.Lock() same := lastApplied == configJSON lastAppliedMu.Unlock() @@ -109,28 +115,28 @@ func runKubernetesMode() { lastAppliedMu.Unlock() return nil }) - go watch.WatchSSLProfileDir(ctx, config.GetSSLProfilePath(), router.OnSSLProfilesFromDisk) + go watch.SSLProfileDir(ctx, config.GetSSLProfilePath(), router.OnSSLProfilesFromDisk) <-exitChannel - os.Exit(0) + return nil } -func runPotMode() { +func runPotMode() error { ioFogClient, clientError := sdk.NewDefaultIoFogClientV3() if clientError != nil { - log.Fatalln(clientError.Error()) + return clientError } if err := updateConfig(ioFogClient, router.Config); err != nil { - log.Fatalln(err.Error()) + return err } confChannel := ioFogClient.EstablishControlWsConnection(0) exitChannel := make(chan error) go router.StartRouter(exitChannel) ctx := context.Background() - go watch.WatchSSLProfileDir(ctx, config.GetSSLProfilePath(), router.OnSSLProfilesFromDisk) + go watch.SSLProfileDir(ctx, config.GetSSLProfilePath(), router.OnSSLProfilesFromDisk) for { select { case <-exitChannel: - os.Exit(0) + return nil case <-confChannel: newConfig := &rt.Config{ SslProfiles: make(map[string]qdr.SslProfile), @@ -139,8 +145,8 @@ func runPotMode() { Addresses: make(map[string]qdr.Address), LogConfig: make(map[string]qdr.LogConfig), Bridges: qdr.BridgeConfig{ - TcpListeners: make(map[string]qdr.TcpEndpoint), - TcpConnectors: make(map[string]qdr.TcpEndpoint), + TCPListeners: make(map[string]qdr.TCPEndpoint), + TCPConnectors: make(map[string]qdr.TCPEndpoint), }, } if err := updateConfig(ioFogClient, newConfig); err != nil { @@ -154,7 +160,7 @@ func runPotMode() { } } -func updateConfig(ioFogClient *sdk.IoFogClient, config interface{}) error { +func updateConfig(ioFogClient *sdk.IoFogClient, config any) error { const attemptLimit = 5 var lastErr error From a98fbd9d811587e4a97d55f0bfcf631f72ee71d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:44:47 +0300 Subject: [PATCH 17/22] Add develop CI workflows and dual-mirror set-build-env action. Replace legacy push.yaml with lint/test/security checks and docker smoke builds that never push to GHCR. --- .github/actions/set-build-env/action.yml | 49 +++++++++ .github/workflows/ci.yml | 109 ++++++++++++++++++++ .github/workflows/govulncheck.yml | 28 +++++ .github/workflows/push.yaml | 125 ----------------------- 4 files changed, 186 insertions(+), 125 deletions(-) create mode 100644 .github/actions/set-build-env/action.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/govulncheck.yml delete mode 100644 .github/workflows/push.yaml diff --git a/.github/actions/set-build-env/action.yml b/.github/actions/set-build-env/action.yml new file mode 100644 index 0000000..aee2b2b --- /dev/null +++ b/.github/actions/set-build-env/action.yml @@ -0,0 +1,49 @@ +name: Set router build environment +description: Resolves router image registry, container image, OCI source, distribution, and GitHub repo from workflow env or github.repository. + +runs: + using: composite + steps: + - name: Set image registry and OCI source + shell: bash + run: | + REGISTRY="${{ env.IMAGE_REGISTRY }}" + OCI_SOURCE="${{ env.OCI_SOURCE_REPO }}" + ROUTER_IMAGE="${{ env.ROUTER_CONTAINER_IMAGE }}" + if [ -z "$REGISTRY" ] || [ -z "$OCI_SOURCE" ] || [ -z "$ROUTER_IMAGE" ]; then + case "${{ github.repository }}" in + eclipse-iofog/router) + REGISTRY="${REGISTRY:-ghcr.io/eclipse-iofog}" + OCI_SOURCE="${OCI_SOURCE:-https://github.com/eclipse-iofog/router}" + ROUTER_IMAGE="${ROUTER_IMAGE:-ghcr.io/eclipse-iofog/router}" + ;; + *) + REGISTRY="${REGISTRY:-ghcr.io/datasance}" + OCI_SOURCE="${OCI_SOURCE:-https://github.com/Datasance/router}" + ROUTER_IMAGE="${ROUTER_IMAGE:-ghcr.io/datasance/router}" + ;; + esac + fi + echo "IMAGE_REGISTRY=$REGISTRY" >> "${GITHUB_ENV}" + echo "OCI_SOURCE_REPO=$OCI_SOURCE" >> "${GITHUB_ENV}" + echo "ROUTER_CONTAINER_IMAGE=$ROUTER_IMAGE" >> "${GITHUB_ENV}" + + - name: Set router distribution + shell: bash + run: | + DISTRIBUTION="${{ env.ROUTER_DISTRIBUTION }}" + GITHUB_REPO="${{ env.ROUTER_GITHUB_REPO }}" + if [ -z "$DISTRIBUTION" ] || [ -z "$GITHUB_REPO" ]; then + case "${{ github.repository }}" in + eclipse-iofog/router) + DISTRIBUTION="${DISTRIBUTION:-iofog}" + GITHUB_REPO="${GITHUB_REPO:-eclipse-iofog/router}" + ;; + *) + DISTRIBUTION="${DISTRIBUTION:-datasance}" + GITHUB_REPO="${GITHUB_REPO:-Datasance/router}" + ;; + esac + fi + echo "ROUTER_DISTRIBUTION=$DISTRIBUTION" >> "${GITHUB_ENV}" + echo "ROUTER_GITHUB_REPO=$GITHUB_REPO" >> "${GITHUB_ENV}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3ccc109 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,109 @@ +name: CI + +on: + pull_request: + branches: [develop] + paths-ignore: + - README.md + - CHANGELOG.md + - LICENSE + push: + branches: [develop] + paths-ignore: + - README.md + - CHANGELOG.md + - LICENSE + workflow_dispatch: + +permissions: read-all + +env: + GO_VERSION: '1.26.4' + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: go.sum + + - run: go version + + - name: golangci-lint + uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 + with: + version: v2.12.2 + args: --timeout=5m0s --config .golangci.yaml + + - name: Static security analysis + run: make security-code + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: go.sum + + - run: go version + + - name: Run unit tests + run: make test + + - name: Check formatting + run: make fmt-check + + docker-build-smoke: + name: Docker build smoke + runs-on: ubuntu-latest + needs: [lint, test] + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - uses: ./.github/actions/set-build-env + env: + IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY }} + ROUTER_CONTAINER_IMAGE: ${{ vars.ROUTER_CONTAINER_IMAGE }} + OCI_SOURCE_REPO: ${{ vars.OCI_SOURCE_REPO }} + ROUTER_DISTRIBUTION: ${{ vars.ROUTER_DISTRIBUTION }} + ROUTER_GITHUB_REPO: ${{ vars.ROUTER_GITHUB_REPO }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + + - name: Build UBI image (amd64, arm64) + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + file: Dockerfile + platforms: linux/amd64,linux/arm64 + push: false + cache-from: type=gha + cache-to: type=gha,mode=max + tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:ci + + - name: Build edge image (arm/v7, riscv64) + if: hashFiles('Dockerfile.edge') != '' + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + file: Dockerfile.edge + platforms: linux/arm/v7,linux/riscv64 + push: false + cache-from: type=gha + cache-to: type=gha,mode=max + tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:ci-edge diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml new file mode 100644 index 0000000..670a710 --- /dev/null +++ b/.github/workflows/govulncheck.yml @@ -0,0 +1,28 @@ +name: govulncheck + +on: + schedule: + - cron: "0 0 * * 0" + workflow_dispatch: {} + +permissions: read-all + +env: + GO_VERSION: '1.26.4' + +jobs: + govulncheck: + name: govulncheck + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: go.sum + + - name: Run govulncheck + run: make vulncheck diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml deleted file mode 100644 index b1f9cc8..0000000 --- a/.github/workflows/push.yaml +++ /dev/null @@ -1,125 +0,0 @@ -name: CI -on: - push: - branches: - - main - tags: [v*] - paths-ignore: - - README.md - - CHANGELOG.md - - LICENSE - pull_request: - branches: - - main - paths-ignore: - - README.md - - CHANGELOG.md - - LICENSE -env: - IMAGE_NAME: 'router' - ADAPTOR_IMAGE_NAME: 'router-adaptor' - -jobs: - version: - name: Set version - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - VERSION: ${{ steps.tags.outputs.VERSION }} - steps: - - uses: actions/checkout@v4 - - name: Get Previous tag - id: previoustag - uses: "WyriHaximus/github-action-get-previous-tag@v1" - with: - fallback: 0.0.0 - - name: Set image tag - shell: bash - id: tags - run: | - if [[ ${{ github.ref_name }} =~ ^v.* ]] ; then - VERSION=${{ github.ref_name }} - echo "VERSION=${VERSION:1}" >> "${GITHUB_OUTPUT}" - else - VERSION=${{ steps.previoustag.outputs.tag }} - echo "VERSION=${VERSION:1}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" - fi - - build_amd64: - name: Build amd64 - needs: version - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to Github Container Registry - uses: docker/login-action@v3 - with: - registry: "ghcr.io" - username: ${{ github.actor }} - password: ${{ secrets.PAT }} - - name: Build and push amd64 image - uses: docker/build-push-action@v5 - with: - context: . - file: Dockerfile - platforms: linux/amd64 - push: ${{ github.event_name == 'push' }} - tags: | - ghcr.io/datasance/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-amd64 - - build_arm64: - name: Build arm64 - needs: version - runs-on: ubuntu-24.04-arm - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to Github Container Registry - uses: docker/login-action@v3 - with: - registry: "ghcr.io" - username: ${{ github.actor }} - password: ${{ secrets.PAT }} - - name: Build and push arm64 image - uses: docker/build-push-action@v5 - with: - context: . - file: Dockerfile - platforms: linux/arm64 - push: ${{ github.event_name == 'push' }} - tags: | - ghcr.io/datasance/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-arm64 - - merge_manifest: - name: Merge multi-platform manifest - needs: [version, build_amd64, build_arm64] - if: github.event_name == 'push' - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Login to Github Container Registry - uses: docker/login-action@v3 - with: - registry: "ghcr.io" - username: ${{ github.actor }} - password: ${{ secrets.PAT }} - - name: Create and push multi-platform manifest - run: | - docker buildx imagetools create \ - -t ghcr.io/datasance/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.VERSION }} \ - -t ghcr.io/datasance/${{ env.IMAGE_NAME }}:latest \ - -t ghcr.io/datasance/${{ env.IMAGE_NAME }}:main \ - ghcr.io/datasance/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-amd64 \ - ghcr.io/datasance/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-arm64 From dcfe4caa228e9ccee9b9a4151ed17d784f005221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Mon, 15 Jun 2026 23:49:01 +0300 Subject: [PATCH 18/22] Add release workflow for UBI amd64/arm64 publish on v* tags. Quality gates run before GHCR push with OCI labels on the scratch image. --- .github/workflows/release.yml | 96 +++++++++++++++++++++++++++++++++++ Dockerfile | 13 ++++- 2 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cc2fbd2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,96 @@ +name: Release + +on: + push: + tags: + - 'v*' + +env: + GO_VERSION: '1.26.4' + +permissions: read-all + +jobs: + quality: + name: Lint and test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: go.sum + + - run: go version + + - name: golangci-lint + uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 + with: + version: v2.12.2 + args: --timeout=5m0s --config .golangci.yaml + + - name: Static security analysis + run: make security-code + + - name: Run unit tests + run: make test + + - name: Check formatting + run: make fmt-check + + publish-ubi: + name: Publish UBI image (amd64, arm64) + runs-on: ubuntu-latest + needs: [quality] + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - uses: ./.github/actions/set-build-env + env: + IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY }} + ROUTER_CONTAINER_IMAGE: ${{ vars.ROUTER_CONTAINER_IMAGE }} + OCI_SOURCE_REPO: ${{ vars.OCI_SOURCE_REPO }} + ROUTER_DISTRIBUTION: ${{ vars.ROUTER_DISTRIBUTION }} + ROUTER_GITHUB_REPO: ${{ vars.ROUTER_GITHUB_REPO }} + + - name: Resolve semver tag + run: | + SEMVER="${GITHUB_REF_NAME#v}" + echo "SEMVER=${SEMVER}" >> "${GITHUB_ENV}" + REGISTRY_HOST="${ROUTER_CONTAINER_IMAGE%%/*}" + echo "REGISTRY_HOST=${REGISTRY_HOST}" >> "${GITHUB_ENV}" + + - name: Set up QEMU + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + + - name: Log in to container registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.6.0 + with: + registry: ${{ env.REGISTRY_HOST }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Internal staging tag; merged into :semver/:latest/:main by publish-manifest (Plan 6). + - name: Build and push UBI image + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + file: Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + OCI_SOURCE_REPO=${{ env.OCI_SOURCE_REPO }} + OCI_VERSION=${{ env.SEMVER }} + OCI_REVISION=${{ github.sha }} + ROUTER_DISTRIBUTION=${{ env.ROUTER_DISTRIBUTION }} + tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:${{ env.SEMVER }}-ubi diff --git a/Dockerfile b/Dockerfile index b50b541..873d4f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,6 +70,16 @@ RUN microdnf install -y tzdata && microdnf reinstall -y tzdata FROM scratch +ARG OCI_SOURCE_REPO +ARG OCI_VERSION=latest +ARG OCI_REVISION +ARG ROUTER_DISTRIBUTION + +LABEL org.opencontainers.image.source="${OCI_SOURCE_REPO}" \ + org.opencontainers.image.version="${OCI_VERSION}" \ + org.opencontainers.image.revision="${OCI_REVISION}" \ + distribution="${ROUTER_DISTRIBUTION}" + COPY --from=packager /output / COPY --from=packager /etc/yum.repos.d /etc/yum.repos.d @@ -80,8 +90,7 @@ COPY --from=builder /image / WORKDIR /home/skrouterd/bin COPY ./scripts/* /home/skrouterd/bin/ -ARG version=latest -ENV VERSION=${version} +ENV VERSION=${OCI_VERSION} ENV QDROUTERD_HOME=/home/skrouterd COPY LICENSE /licenses/LICENSE From 78f916fc92bfdad6f47c4f07ae92dc6f91beab22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Tue, 16 Jun 2026 21:53:57 +0300 Subject: [PATCH 19/22] Add Dockerfile.edge for arm/v7 and riscv64 Debian builds. Debian Trixie compiles skupper-router 3.5.1 with compile.sh adaptations, debootstrap packager, and scratch runtime aligned with the UBI /image layout. Remove legacy Dockerfile.adaptor. --- Dockerfile.adaptor | 3 -- Dockerfile.edge | 118 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) delete mode 100644 Dockerfile.adaptor create mode 100644 Dockerfile.edge diff --git a/Dockerfile.adaptor b/Dockerfile.adaptor deleted file mode 100644 index c10ba5c..0000000 --- a/Dockerfile.adaptor +++ /dev/null @@ -1,3 +0,0 @@ -FROM quay.io/skupper/kube-adaptor:2.0.1 - -COPY LICENSE /licenses/LICENSE diff --git a/Dockerfile.edge b/Dockerfile.edge new file mode 100644 index 0000000..33e6d4e --- /dev/null +++ b/Dockerfile.edge @@ -0,0 +1,118 @@ +# Plan 5 spike — Debian Trixie compile for linux/arm/v7 and linux/riscv64. +# UBI Dockerfile handles amd64/arm64; this file mirrors /image layout → scratch. + +FROM debian:trixie AS builder + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc g++ make cmake pkg-config \ + libsasl2-dev libssl-dev uuid-dev libffi-dev \ + python3-dev python3-pip python3-venv \ + python3-build python3-setuptools python3-cffi-backend \ + libnghttp2-dev \ + wget ca-certificates tar patch findutils git \ + libtool autoconf automake \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /build +RUN git clone --depth 1 --branch 3.5.1 https://github.com/skupperproject/skupper-router.git . + +ENV PROTON_VERSION=e5d5c2badb964684bf41ba509a110bf06a24712a +ENV PROTON_SOURCE_URL=${PROTON_SOURCE_URL:-https://github.com/apache/qpid-proton/archive/${PROTON_VERSION}.tar.gz} +ENV LWS_VERSION=v4.3.3 +ENV LIBUNWIND_VERSION=v1.8.1 +ENV LWS_SOURCE_URL=${LWS_SOURCE_URL:-https://github.com/warmcat/libwebsockets/archive/refs/tags/${LWS_VERSION}.tar.gz} +ENV LIBUNWIND_SOURCE_URL=${LIBUNWIND_SOURCE_URL:-https://github.com/libunwind/libunwind/archive/refs/tags/${LIBUNWIND_VERSION}.tar.gz} +ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig +ENV PIP_BREAK_SYSTEM_PACKAGES=1 +ENV CFLAGS="-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector-strong" +ENV CXXFLAGS="-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector-strong" +ENV LDFLAGS="-Wl,-z,relro -Wl,-z,now -Wl,--as-needed" + +ARG VERSION=0.0.0 +ENV VERSION=$VERSION +ARG TARGETARCH +ENV PLATFORM=$TARGETARCH + +# compile.sh expects RPM %set_build_flags and RHEL lib64 paths; adapt for Debian. +# Skip requirements-dev.txt (tests/grpcio) and ci_requirements.txt (PyPI pip upgrade); +# Proton Python bindings use apt: python3-build, setuptools, cffi-backend above. +RUN sed -i \ + -e '/^BUILD_FLAGS=/d' \ + -e '/^eval "${BUILD_FLAGS}"/d' \ + -e 's|install/lib64/cmake/Proton|install/lib/cmake/Proton|' \ + -e 's|-DENABLE_PROFILE_GUIDED_OPTIMIZATION=ON|-DENABLE_PROFILE_GUIDED_OPTIMIZATION=OFF|' \ + -e 's|-DCMAKE_INSTALL_PREFIX=/usr|-DCMAKE_C_FLAGS="-Wno-error=format" -DCMAKE_INSTALL_PREFIX=/usr|' \ + -e '/requirements-dev.txt/s/^/# /' \ + -e '/ci_requirements.txt/s/^/# /' \ + -e 's|python3 -m pip install "$(find|python3 -m pip install --ignore-installed "$(find|g' \ + .github/scripts/compile.sh + +RUN .github/scripts/compile.sh + +RUN mkdir -p /image \ + && tar zxpf /qpid-proton-image.tar.gz -C /image \ + && tar zxpf /skupper-router-image.tar.gz -C /image \ + && tar zxpf /libwebsockets-image.tar.gz -C /image + +RUN mkdir /image/licenses && cp ./LICENSE /image/licenses + +FROM debian:trixie AS packager + +RUN apt-get update && apt-get install -y --no-install-recommends debootstrap \ + && rm -rf /var/lib/apt/lists/* + +RUN debootstrap --variant=minbase \ + --include=libsasl2-2,libsasl2-modules,openssl,python3,libnghttp2-14,hostname,iputils-ping,ca-certificates \ + trixie /output http://deb.debian.org/debian + +RUN chroot /output groupadd -g 10000 runner \ + && chroot /output useradd --uid 10000 --gid 10000 --create-home --shell /usr/sbin/nologin runner + +FROM golang:1.26.4-alpine AS go-builder + +ARG TARGETOS +ARG TARGETARCH + +RUN mkdir -p /go/src/github.com/eclipse-iofog/router +WORKDIR /go/src/github.com/eclipse-iofog/router +COPY . /go/src/github.com/eclipse-iofog/router +RUN go fmt ./... +RUN if [ "$TARGETARCH" = "arm" ]; then \ + GOOS=${TARGETOS} GOARCH=arm GOARM=7 go build -trimpath -ldflags="-s -w" -o bin/router .; \ + else \ + GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o bin/router .; \ + fi + +FROM debian:trixie-slim AS tz +RUN apt-get update && apt-get install -y --no-install-recommends tzdata \ + && rm -rf /var/lib/apt/lists/* + +FROM scratch + +ARG OCI_SOURCE_REPO +ARG OCI_VERSION=latest +ARG OCI_REVISION +ARG ROUTER_DISTRIBUTION + +LABEL org.opencontainers.image.source="${OCI_SOURCE_REPO}" \ + org.opencontainers.image.version="${OCI_VERSION}" \ + org.opencontainers.image.revision="${OCI_REVISION}" \ + distribution="${ROUTER_DISTRIBUTION}" + +COPY --from=packager /output / +USER 10000 + +COPY --from=builder /image / + +WORKDIR /home/skrouterd/bin +COPY ./scripts/* /home/skrouterd/bin/ + +ENV VERSION=${OCI_VERSION} +ENV QDROUTERD_HOME=/home/skrouterd + +COPY LICENSE /licenses/LICENSE +COPY --from=go-builder /go/src/github.com/eclipse-iofog/router/bin/router /home/skrouterd/bin/router + +COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo + +CMD ["/home/skrouterd/bin/router"] From dcd1ca1256fd32be1ded31461c37d8c92ac3fcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Tue, 16 Jun 2026 21:57:57 +0300 Subject: [PATCH 20/22] wire 4-platform release manifest for UBI + edge images. Add publish-edge and publish-manifest jobs; harden CI docker smoke with OCI build-args and unconditional Dockerfile.edge build. --- .github/workflows/ci.yml | 11 +++- .github/workflows/release.yml | 99 +++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ccc109..a832c7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,10 +94,14 @@ jobs: push: false cache-from: type=gha cache-to: type=gha,mode=max + build-args: | + OCI_SOURCE_REPO=${{ env.OCI_SOURCE_REPO }} + OCI_VERSION=ci + OCI_REVISION=${{ github.sha }} + ROUTER_DISTRIBUTION=${{ env.ROUTER_DISTRIBUTION }} tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:ci - name: Build edge image (arm/v7, riscv64) - if: hashFiles('Dockerfile.edge') != '' uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . @@ -106,4 +110,9 @@ jobs: push: false cache-from: type=gha cache-to: type=gha,mode=max + build-args: | + OCI_SOURCE_REPO=${{ env.OCI_SOURCE_REPO }} + OCI_VERSION=ci + OCI_REVISION=${{ github.sha }} + ROUTER_DISTRIBUTION=${{ env.ROUTER_DISTRIBUTION }} tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:ci-edge diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc2fbd2..395ce36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,3 +94,102 @@ jobs: OCI_REVISION=${{ github.sha }} ROUTER_DISTRIBUTION=${{ env.ROUTER_DISTRIBUTION }} tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:${{ env.SEMVER }}-ubi + + publish-edge: + name: Publish edge image (arm/v7, riscv64) + runs-on: ubuntu-latest + needs: [quality] + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - uses: ./.github/actions/set-build-env + env: + IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY }} + ROUTER_CONTAINER_IMAGE: ${{ vars.ROUTER_CONTAINER_IMAGE }} + OCI_SOURCE_REPO: ${{ vars.OCI_SOURCE_REPO }} + ROUTER_DISTRIBUTION: ${{ vars.ROUTER_DISTRIBUTION }} + ROUTER_GITHUB_REPO: ${{ vars.ROUTER_GITHUB_REPO }} + + - name: Resolve semver tag + run: | + SEMVER="${GITHUB_REF_NAME#v}" + echo "SEMVER=${SEMVER}" >> "${GITHUB_ENV}" + REGISTRY_HOST="${ROUTER_CONTAINER_IMAGE%%/*}" + echo "REGISTRY_HOST=${REGISTRY_HOST}" >> "${GITHUB_ENV}" + + - name: Set up QEMU + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + + - name: Log in to container registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.6.0 + with: + registry: ${{ env.REGISTRY_HOST }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Internal staging tag; merged into :semver/:latest/:main by publish-manifest. + - name: Build and push edge image + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + file: Dockerfile.edge + platforms: linux/arm/v7,linux/riscv64 + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + OCI_SOURCE_REPO=${{ env.OCI_SOURCE_REPO }} + OCI_VERSION=${{ env.SEMVER }} + OCI_REVISION=${{ github.sha }} + ROUTER_DISTRIBUTION=${{ env.ROUTER_DISTRIBUTION }} + tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:${{ env.SEMVER }}-edge + + publish-manifest: + name: Publish 4-platform manifest + runs-on: ubuntu-latest + needs: [publish-ubi, publish-edge] + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - uses: ./.github/actions/set-build-env + env: + IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY }} + ROUTER_CONTAINER_IMAGE: ${{ vars.ROUTER_CONTAINER_IMAGE }} + OCI_SOURCE_REPO: ${{ vars.OCI_SOURCE_REPO }} + ROUTER_DISTRIBUTION: ${{ vars.ROUTER_DISTRIBUTION }} + ROUTER_GITHUB_REPO: ${{ vars.ROUTER_GITHUB_REPO }} + + - name: Resolve semver tag + run: | + SEMVER="${GITHUB_REF_NAME#v}" + echo "SEMVER=${SEMVER}" >> "${GITHUB_ENV}" + REGISTRY_HOST="${ROUTER_CONTAINER_IMAGE%%/*}" + echo "REGISTRY_HOST=${REGISTRY_HOST}" >> "${GITHUB_ENV}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + + - name: Log in to container registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.6.0 + with: + registry: ${{ env.REGISTRY_HOST }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create and push multi-arch manifest + run: | + docker buildx imagetools create \ + -t "${ROUTER_CONTAINER_IMAGE}:${SEMVER}" \ + -t "${ROUTER_CONTAINER_IMAGE}:latest" \ + -t "${ROUTER_CONTAINER_IMAGE}:main" \ + "${ROUTER_CONTAINER_IMAGE}:${SEMVER}-ubi" \ + "${ROUTER_CONTAINER_IMAGE}:${SEMVER}-edge" From 6c647064bdaa7d04a64692a95436478326d9dac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 17 Jun 2026 11:54:38 +0300 Subject: [PATCH 21/22] =?UTF-8?q?Slim=20edge=20scratch=20images=20to=20133?= =?UTF-8?q?=E2=80=93145=20MB;=20verify=20arm/v7=20and=20riscv64=20smoke.?= =?UTF-8?q?=20Replace=20debootstrap=20minbase=20with=20dpkg-deb=20runtime?= =?UTF-8?q?=20extract;=20add=20libpython3.13=20for=20skrouterd.=20Include?= =?UTF-8?q?=20k8s-mode=20smoke=20fixture.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 75 +++++++++++++++++++++++++++------------ Dockerfile.edge | 32 ++++++++++++----- test/smoke-skrouterd.json | 4 +++ 3 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 test/smoke-skrouterd.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a832c7a..be69775 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,10 +64,26 @@ jobs: - name: Check formatting run: make fmt-check - docker-build-smoke: - name: Docker build smoke + docker-smoke: + name: Docker smoke (${{ matrix.slug }}) runs-on: ubuntu-latest needs: [lint, test] + strategy: + fail-fast: false + matrix: + include: + - slug: amd64 + platform: linux/amd64 + dockerfile: Dockerfile + - slug: arm64 + platform: linux/arm64 + dockerfile: Dockerfile + - slug: armv7 + platform: linux/arm/v7 + dockerfile: Dockerfile.edge + - slug: riscv64 + platform: linux/riscv64 + dockerfile: Dockerfile.edge steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -85,34 +101,47 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - - name: Build UBI image (amd64, arm64) + - name: Build and load image uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . - file: Dockerfile - platforms: linux/amd64,linux/arm64 + file: ${{ matrix.dockerfile }} + platforms: ${{ matrix.platform }} + load: true push: false - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=router-${{ matrix.slug }} + cache-to: type=gha,mode=max,scope=router-${{ matrix.slug }} build-args: | OCI_SOURCE_REPO=${{ env.OCI_SOURCE_REPO }} OCI_VERSION=ci OCI_REVISION=${{ github.sha }} ROUTER_DISTRIBUTION=${{ env.ROUTER_DISTRIBUTION }} - tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:ci + tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:ci-smoke-${{ matrix.slug }} - - name: Build edge image (arm/v7, riscv64) - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 - with: - context: . - file: Dockerfile.edge - platforms: linux/arm/v7,linux/riscv64 - push: false - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - OCI_SOURCE_REPO=${{ env.OCI_SOURCE_REPO }} - OCI_VERSION=ci - OCI_REVISION=${{ github.sha }} - ROUTER_DISTRIBUTION=${{ env.ROUTER_DISTRIBUTION }} - tags: ${{ env.ROUTER_CONTAINER_IMAGE }}:ci-edge + - name: Runtime smoke (router → launch.sh → skrouterd) + env: + IMAGE: ${{ env.ROUTER_CONTAINER_IMAGE }}:ci-smoke-${{ matrix.slug }} + PLATFORM: ${{ matrix.platform }} + run: | + set -euo pipefail + docker run -d --name router-smoke --platform "${PLATFORM}" \ + -e SKUPPER_PLATFORM=kubernetes \ + -v "${GITHUB_WORKSPACE}/test/smoke-skrouterd.json:/tmp/skrouterd.json:ro" \ + "${IMAGE}" + cleanup() { docker rm -f router-smoke >/dev/null 2>&1 || true; } + trap cleanup EXIT + for _ in $(seq 1 30); do + if docker logs router-smoke 2>&1 | grep -q "Listening on 0.0.0.0:5672"; then + echo "Runtime smoke passed: skrouterd listening on :5672" + exit 0 + fi + if ! docker ps -q -f name=router-smoke | grep -q .; then + echo "Container exited before skrouterd became ready" + docker logs router-smoke 2>&1 || true + exit 1 + fi + sleep 2 + done + echo "Timed out waiting for skrouterd to listen on :5672" + docker logs router-smoke 2>&1 || true + exit 1 diff --git a/Dockerfile.edge b/Dockerfile.edge index 33e6d4e..bd8f483 100644 --- a/Dockerfile.edge +++ b/Dockerfile.edge @@ -58,15 +58,29 @@ RUN mkdir /image/licenses && cp ./LICENSE /image/licenses FROM debian:trixie AS packager -RUN apt-get update && apt-get install -y --no-install-recommends debootstrap \ - && rm -rf /var/lib/apt/lists/* - -RUN debootstrap --variant=minbase \ - --include=libsasl2-2,libsasl2-modules,openssl,python3,libnghttp2-14,hostname,iputils-ping,ca-certificates \ - trixie /output http://deb.debian.org/debian - -RUN chroot /output groupadd -g 10000 runner \ - && chroot /output useradd --uid 10000 --gid 10000 --create-home --shell /usr/sbin/nologin runner +# UBI installroot analogue: download only runtime .debs + hard deps, extract to /output. +ENV ROOTFS=/output +ENV PACKAGER_PKGS="base-files base-passwd libc6 libgcc-s1 bash ca-certificates coreutils hostname iputils-ping libnghttp2-14 libsasl2-2 libsasl2-modules libpython3.13 openssl python3 login" + +RUN apt-get update \ + && mkdir -p "${ROOTFS}" /tmp/debs \ + && cd /tmp/debs \ + && apt-get download $(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances ${PACKAGER_PKGS} 2>/dev/null | grep '^\w' | sort -u) \ + && for deb in *.deb; do dpkg-deb -x "$deb" "${ROOTFS}"; done \ + && mkdir -p "${ROOTFS}/home/runner" \ + && grep -q '^runner:' "${ROOTFS}/etc/passwd" 2>/dev/null || echo 'runner:x:10000:10000:runner:/home/runner:/usr/sbin/nologin' >> "${ROOTFS}/etc/passwd" \ + && grep -q '^runner:' "${ROOTFS}/etc/group" 2>/dev/null || echo 'runner:x:10000:' >> "${ROOTFS}/etc/group" \ + && rm -rf /tmp/debs /var/lib/apt/lists/* + +RUN rm -rf \ + "${ROOTFS}/usr/share/doc" "${ROOTFS}/usr/share/man" "${ROOTFS}/usr/share/info" \ + "${ROOTFS}/usr/share/locale" "${ROOTFS}/usr/share/debconf" \ + "${ROOTFS}/usr/share/tzdata" "${ROOTFS}/usr/share/zoneinfo" \ + "${ROOTFS}/var/lib/dpkg" "${ROOTFS}/etc/dpkg" \ + "${ROOTFS}/usr/bin/dpkg"* "${ROOTFS}/usr/bin/apt"* \ + "${ROOTFS}/usr/bin/gawk" "${ROOTFS}/usr/bin/mawk" "${ROOTFS}/usr/bin/tar" \ + && find "${ROOTFS}/usr/lib" -name 'libapt*.so*' -delete \ + && find "${ROOTFS}/usr/lib" -path '*/python3*' -type d \( -name test -o -name idlelib -o -name tkinter -o -name ensurepip \) -exec rm -rf {} + 2>/dev/null || true FROM golang:1.26.4-alpine AS go-builder diff --git a/test/smoke-skrouterd.json b/test/smoke-skrouterd.json new file mode 100644 index 0000000..90e3445 --- /dev/null +++ b/test/smoke-skrouterd.json @@ -0,0 +1,4 @@ +[ + ["router", {"id": "smoke-test", "mode": "edge", "helloMaxAgeSeconds": "3"}], + ["listener", {"name": "amqp", "host": "0.0.0.0", "port": 5672, "role": "normal"}] +] From 3889d20ebf8346bd51cb0b530cf798f15903209e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 17 Jun 2026 13:00:33 +0300 Subject: [PATCH 22/22] efresh README, add CHANGELOG v3.8.0, and CONTRIBUTING. Document dual-mirror workflow, GHCR images, 4-platform support, skupper 3.5.1 pin, and contributor git policy. --- .golangci.yaml | 2 -- CHANGELOG.md | 43 ++++++++++++++++++++++++++++ CONTRIBUTING.md | 52 ++++++++++++++++++++++++++++++++++ README.md | 75 +++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 159 insertions(+), 13 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.golangci.yaml b/.golangci.yaml index 1a7ebd7..9cc6077 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -14,7 +14,6 @@ formatters: exclusions: generated: lax paths: - - ^\.cursor/ - ^build/ - ^vendor/ @@ -66,7 +65,6 @@ linters: exclusions: generated: lax paths: - - ^\.cursor/ - ^build/ - ^vendor/ presets: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d3ff6d..dacc578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,49 @@ # Changelog +## [v3.8.0] - 2026-06-17 + +### Wrapper release + +- **v3.8.0** — dual-mirror modernization of the ioFog skupper-router wrapper image. +- Go wrapper binary **`router`** at `/home/skrouterd/bin/router`; supervises **`skrouterd`** via `launch.sh`. +- Canonical Go module: **`github.com/eclipse-iofog/router`** on both remotes. +- Go toolchain **1.26.4**. + +### Embedded skupper-router + +- Pin and compile upstream **skupper-router 3.5.1** (separate from wrapper semver). +- Production images: UBI 9 builder (amd64/arm64) and Debian Trixie builder (arm/v7, riscv64) → **scratch** runtime with identical `/image` layout. +- Dev overlay: `Dockerfile.dev` on `quay.io/skupper/skupper-router:3.5.1`. + +### SDK and LocalAPI + +- **iofog-go-sdk v3.8.0-beta.4** (`github.com/datasance/iofog-go-sdk/v3`) — interim dependency until eclipse-iofog SDK migration. +- Pot/iofog mode: ioFog Agent **LocalAPI v3** config fetch and control websocket updates. +- **`SKUPPER_PLATFORM`**: `pot`, `iofog` (alias), `kubernetes`. + +### CI and release + +- **`ci.yml`** on **`develop`**: lint, test, security analysis, 4-platform docker smoke — **no GHCR push** on branch builds. +- **`release.yml`** on **`v*` tags only**: quality gates, UBI + edge image publish, unified 4-platform manifest (`:semver`, `:latest`, `:main`). +- **`govulncheck.yml`**: scheduled vulnerability scanning. +- Mirror-specific registry via **`set-build-env`** and repository variables; **`GITHUB_TOKEN`** for GHCR (no routine `secrets.PAT`). +- SHA-pinned GitHub Actions and base images. + +### Multi-arch + +- **amd64**, **arm64** (`Dockerfile`), **arm/v7**, **riscv64** (`Dockerfile.edge`) in one manifest. +- **s390x** and **ppc64le** excluded from ioFog release manifest. + +### Deprecations and removals + +- Abandon **`iofog/merge`** long-lived branch in favor of **`develop`** → **`develop`** PR workflow between mirrors. +- Remove legacy **`push.yaml`** CI (replaced by `ci.yml` + `release.yml`). +- Remove Azure Pipelines workflow; GitHub Actions only. +- **`router-adaptor`** image out of scope (not published). +- Per-file copyright headers replaced by root **`NOTICE`** + **`LICENSE`**. + ## [v3.0.0] - 10 May 2022 + * Updated iofog-go-sdk to v3.0.0 ## [v3.0.0-beta1] - 13 August 2021 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3937adf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing + +Thank you for contributing to the ioFog / Datasance **router** wrapper image. + +## Repositories + +| Role | GitHub | Container registry | +|------|--------|------------------| +| Upstream (canonical module path) | [eclipse-iofog/router](https://github.com/eclipse-iofog/router) | `ghcr.io/eclipse-iofog/router` | +| Mirror (primary development remote) | [Datasance/router](https://github.com/Datasance/router) | `ghcr.io/datasance/router` | + +The git tree is identical on both remotes. Product flavor (registry URL, OCI labels) is selected by CI repository variables — not by forked application code. + +## Branch workflow + +| Branch | Purpose | +|--------|---------| +| **`develop`** | Integration branch on **both** remotes | +| **`router/-`** | Feature / plan branches (e.g. `router/07-docs`) | + +Typical flow: + +1. Branch from **`develop`** on **`Datasance/router`**. +2. Open a pull request to **`eclipse-iofog/router`** **`develop`**. +3. Ensure CI is green (lint, test, docker smoke on `develop` pushes and PRs). +4. After merge, release maintainers tag identical **`v*`** semver on both remotes; **`release.yml`** publishes GHCR images. + +Do **not** use the legacy **`iofog/merge`** branch — it is abandoned in favor of the develop workflow above. + +## Development setup + +- **Go 1.26.4** (see `go.mod`). +- `make test`, `make fmt-check`, `make security-code` before pushing. +- Local wrapper overlay: `Dockerfile.dev` (upstream `quay.io/skupper/skupper-router:3.5.1` image). + +Module import path is always **`github.com/eclipse-iofog/router`**, even when cloning the Datasance mirror. + +## Pull requests + +- Target **`develop`**, not `main`. +- Keep changes focused; one active implementation plan per branch when following the modernization wave. +- Do not reintroduce: per-file copyright headers, `push.yaml`, CI push on every branch push, `secrets.PAT` for routine CI, or **`router-adaptor`** publish paths. + +## Releases + +- Wrapper git tags: **`v3.8.0`**, **`v3.8.0-1`**, etc. +- Embedded skupper-router version (**3.5.1**) is documented separately from wrapper semver. +- Images publish only on **`v*` tag push** — not on ordinary `develop` commits. + +## Questions + +Open an issue on the mirror you are developing against, or reach out to the ioFog / Datasance maintainers for release coordination between remotes. diff --git a/README.md b/README.md index af2fd9e..9be4456 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,77 @@ -# iofog-router +# Router -Builds an image of the Apache Qpid Dispatch Router designed for use with Eclipse ioFog and Datasance Pot. The router can run in **Pot** mode (config from iofog agent) or **Kubernetes** mode (config from a volume-mounted file at `QDROUTERD_CONF`). +[![CI](https://github.com/eclipse-iofog/router/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/eclipse-iofog/router/actions/workflows/ci.yml) +[![Release](https://github.com/eclipse-iofog/router/actions/workflows/release.yml/badge.svg)](https://github.com/eclipse-iofog/router/actions/workflows/release.yml) +[![Go](https://img.shields.io/badge/Go-1.26.4-00ADD8?logo=go&logoColor=white)](https://go.dev/) + +Go wrapper image for **[skupper-router](https://github.com/skupperproject/skupper-router)** used by **Eclipse ioFog** and **Datasance PoT** edge fleets. The wrapper supervises embedded **`skrouterd`** with config watch and AMQP hot reload. + +| Component | Version | +|-----------|---------| +| Wrapper release | **v3.8.0** | +| Embedded skupper-router | **3.5.1** (compiled from upstream tag pin) | + +Edgelet workload label: **`iofog-router`**. + +## Container images + +Identical git tree; registry and OCI labels differ by mirror CI variables only. + +| Mirror | Image | +|--------|-------| +| Eclipse ioFog (upstream) | `ghcr.io/eclipse-iofog/router` | +| Datasance (PoT-facing) | `ghcr.io/datasance/router` | + +Release tags: `:semver` (e.g. `:3.8.0`), `:latest`, and `:main` on a unified **4-platform** manifest (`linux/amd64`, `linux/arm64`, `linux/arm/v7`, `linux/riscv64`). + +## Dual-mirror workflow + +1. Develop on **`Datasance/router`** branch **`develop`** (or feature branches `router/-`). +2. Open PR to **`eclipse-iofog/router`** **`develop`**. +3. CI runs on `develop` (lint, test, docker smoke) — **no GHCR push** on branch builds. +4. After merge, tag identical **`v*`** releases on both remotes; **`release.yml`** publishes images. + +See [CONTRIBUTING.md](CONTRIBUTING.md) for branch naming and contributor workflow. + +## Platforms + +| Platform | Dockerfile | Builder → runtime | +|----------|------------|-------------------| +| `linux/amd64`, `linux/arm64` | `Dockerfile` | UBI 9 → scratch | +| `linux/arm/v7`, `linux/riscv64` | `Dockerfile.edge` | Debian Trixie → scratch | + +`s390x` and `ppc64le` are out of scope for the ioFog release manifest. + +Local dev overlay: `Dockerfile.dev` (`quay.io/skupper/skupper-router:3.5.1` + wrapper only). + +## Modes + +The router runs in **Pot** mode (config from ioFog agent LocalAPI v3) or **Kubernetes** mode (config from a volume-mounted file at `QDROUTERD_CONF`). Set `SKUPPER_PLATFORM` accordingly. ## Environment variables | Variable | Default | Description | |----------|---------|-------------| -| `SKUPPER_PLATFORM` | `pot` | Mode: `pot` or `iofog` (alias; config from iofog SDK) or `kubernetes` (config from file at `QDROUTERD_CONF`). | -| `QDROUTERD_CONF` | `/tmp/skrouterd.json` | Path to the router JSON config file. In Kubernetes mode the operator must volume-mount the router ConfigMap at this path. | -| `SSL_PROFILE_PATH` | `/etc/skupper-router-certs` | Directory under which SSL profile certs reside (e.g. `SSL_PROFILE_PATH//ca.crt`, `tls.crt`, `tls.key`). Certs are mounted here in both K8s and Pot. | -| `EDGELET_MICROSERVICE_UID` | *(required in iofog/pot mode)* | Edgelet microservice identity used by the SDK client. | -| `SSL` | `true` | Enables HTTPS/WSS for ioFog Local API in Pot mode. | +| `SKUPPER_PLATFORM` | `pot` | `pot` or `iofog` (alias; config from ioFog SDK) or `kubernetes` (config from file at `QDROUTERD_CONF`). | +| `QDROUTERD_CONF` | `/tmp/skrouterd.json` | Path to the router JSON config. In Kubernetes mode the operator must volume-mount the router ConfigMap at this path. | +| `QDROUTERD_HOME` | `/home/skrouterd` | Skupper home directory (set in image). | +| `SSL_PROFILE_PATH` | `/etc/skupper-router-certs` | Directory for SSL profile certs (`ca.crt`, `tls.crt`, `tls.key` per profile). Watched in both K8s and Pot modes. | +| `EDGELET_MICROSERVICE_UID` | *(required in pot/iofog mode)* | Edgelet microservice identity used by the SDK client. | +| `SSL` | `true` | Enables HTTPS/WSS for ioFog Local API in Pot/iofog mode. | -## Pot mode and ioFog LocalAPI v3 +## Edgelet LocalAPI v1 -In Pot mode, router reads config from ioFog Agent LocalAPI v3 using the SDK (`GET /v1/microservices/config`) and listens for update signals over control websocket (`/v1/microservices/control`). +In PoT/iofog mode, the wrapper reads config from Edgelet LocalAPI v1 using the SDK (`GET /v1/microservices/config`) and listens for update signals over the control websocket (`/v1/microservices/control`). -To work correctly, the container must have ioFog service-account material mounted: +The container must have ioFog service-account material mounted: - token at `/var/run/secrets/edgelet.iofog.org/serviceaccount/token` - CA at `/var/run/secrets/edgelet.iofog.org/serviceaccount/ca.crt` -In Kubernetes mode the router does not use the Kubernetes API; the operator is responsible for mounting the router config at `QDROUTERD_CONF`. Config file changes are watched and applied to the running router via qdr (same as Pot mode). +## Kubernetes mode + +The wrapper does not use the Kubernetes API. The operator mounts router config at `QDROUTERD_CONF`. File changes are watched and applied to the running router via AMQP management (same hot-reload path as Pot mode). + +## License + +Eclipse Public License 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).