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/.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..be69775 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,147 @@ +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-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 + + - 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 and load image + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + file: ${{ matrix.dockerfile }} + platforms: ${{ matrix.platform }} + load: true + push: false + 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-smoke-${{ matrix.slug }} + + - 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/.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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..395ce36 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,195 @@ +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 + + 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" 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 diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..9cc6077 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,77 @@ +# 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: + - ^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: + - ^build/ + - ^vendor/ + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + +run: + timeout: 5m 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/Dockerfile b/Dockerfile index aa8a0e5..873d4f1 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 . @@ -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,15 +90,16 @@ 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 -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.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.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"] diff --git a/Dockerfile.edge b/Dockerfile.edge new file mode 100644 index 0000000..bd8f483 --- /dev/null +++ b/Dockerfile.edge @@ -0,0 +1,132 @@ +# 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 + +# 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 + +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"] 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/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/README.md b/README.md index d34b435..9be4456 100644 --- a/README.md +++ b/README.md @@ -1,13 +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` (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. | +| `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. | + +## Edgelet LocalAPI v1 + +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`). + +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` + +## 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 -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). +Eclipse Public License 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE). 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/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..639e66b 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" + 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 b21b8ee..29d7d28 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" + 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 fd2b5b4..74c2e17 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" + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" + utils "github.com/eclipse-iofog/router/internal/routerutil" "k8s.io/utils/ptr" ) @@ -46,19 +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.PlatformKubernetes: + case types.PlatformPodman, types.PlatformDocker, types.PlatformLinux, + types.PlatformPot, types.PlatformIoFog, types.PlatformKubernetes: configuredPlatform = &platform default: configuredPlatform = ptr.To(types.PlatformKubernetes) @@ -68,6 +61,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) + 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.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 new file mode 100644 index 0000000..35c106a --- /dev/null +++ b/internal/config/platform_test.go @@ -0,0 +1,80 @@ +package config + +import ( + "os" + "testing" + + types "github.com/eclipse-iofog/router/internal/resources/skuppertypes" +) + +func TestIsKubernetesRouterMode(t *testing.T) { + key := types.EnvPlatform + 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.EnvPlatform + 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.EnvPlatform + 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/exec/exec.go b/internal/exec/exec.go index 806c2e0..149a351 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -1,60 +1,48 @@ -/* - * ******************************************************************************* - * * 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 ( "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/qdr/amqp_mgmt.go b/internal/qdr/amqp_mgmt.go index 980ea55..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" @@ -10,14 +11,14 @@ 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" + 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 48ac4db..4457901 100644 --- a/internal/qdr/messaging.go +++ b/internal/qdr/messaging.go @@ -6,82 +6,81 @@ import ( amqp "github.com/interconnectedcloud/go-amqp" - "github.com/datasance/router/internal/messaging" + "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 0fa31a6..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/datasance/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 73a9cb6..a7a72a9 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" + 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) } 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 6b4d1c9..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 @@ -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" @@ -218,7 +219,7 @@ const ( type PrometheusAuthMode string const ( - PrometheusAuthModeTls PrometheusAuthMode = "tls" + PrometheusAuthModeTLS PrometheusAuthMode = "tls" PrometheusAuthModeBasic PrometheusAuthMode = "basic" PrometheusAuthModeUnsecured PrometheusAuthMode = "unsecured" ) @@ -233,7 +234,7 @@ const ( // Assembly constants const ( - AmqpDefaultPort int32 = 5672 + AMQPDefaultPort int32 = 5672 AmqpsDefaultPort int32 = 5671 EdgeRole string = "edge" EdgeRouteName string = "skupper-edge" @@ -298,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"` @@ -345,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"` @@ -379,9 +380,9 @@ type Tuning struct { NodeSelector string Affinity string AntiAffinity string - Cpu string + CPU string Memory string - CpuLimit string + CPULimit string MemoryLimit string } @@ -394,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/router/router.go b/internal/router/router.go index 7e1251c..e40c1f2 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 { @@ -41,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 } @@ -129,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, }) @@ -149,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, }) @@ -157,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, }) @@ -181,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, }) @@ -189,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, }) @@ -197,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, }) @@ -213,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 } } @@ -243,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 @@ -252,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/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) } } } 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 8ba3209..6bcd785 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 @@ -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 24fa533..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/datasance/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 2162854..4ad85f4 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 ( @@ -41,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 @@ -69,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, @@ -90,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() @@ -121,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() { - ioFogClient, clientError := sdk.NewDefaultIoFogClient() +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), @@ -151,12 +145,12 @@ 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 { - 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) @@ -166,17 +160,29 @@ func runPotMode() { } } -func updateConfig(ioFogClient *sdk.IoFogClient, config interface{}) error { - attemptLimit := 5 - var err error +func updateConfig(ioFogClient *sdk.IoFogClient, config any) 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) } 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 }} 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 ./... 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"}] +]