From e96bada228c164f6cb8b96f74db9a34c5952fb22 Mon Sep 17 00:00:00 2001 From: Jeroen Date: Mon, 24 Aug 2026 11:06:28 +0200 Subject: [PATCH 1/2] Move the toolchain to Go 1.27 Follows the 1.26.7 bump in 1.6.1. The go directive in go.mod is what every workflow feeds to setup-go, so this moves the language version and the standard library CI builds against. The Dockerfile builder image moves to golang:1.27.0 to match. golangci-lint moves to v2.13.1. Its modernize linter flagged twelve issues in the test suite. Eleven are embedded fields that can use the promoted-field shorthand in a composite literal. One replaces errors.As with errors.AsType, which the 1.27 standard library adds, so the tests now require 1.27 to build. The linter's own autofix produced invalid code in the MQTT writer tests: it hoisted promoted fields alongside the embedded field they belong to, which the compiler rejects. Those two literals build the base value first instead. A dependency sweep found no updates. Every GitHub Action was checked against its latest release and all were already pinned to the newest version by commit SHA. --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 19 ++++++++++ Dockerfile | 2 +- go.mod | 2 +- internal/adapters/sink/mqtt/writers_test.go | 12 ++++--- internal/adapters/sink/qdb/qdb_test.go | 36 ++++++++----------- .../adapters/source/ducobox/ducobox_test.go | 30 ++++++++-------- .../adapters/source/enphase/token_test.go | 3 +- internal/service/service_test.go | 2 +- 9 files changed, 61 insertions(+), 47 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 964a6d5..2461592 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Run linter uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 with: - version: v2.12.2 + version: v2.13.1 - name: Scan for secrets # gitleaks/gitleaks-action requires a paid license for GitHub diff --git a/CHANGELOG.md b/CHANGELOG.md index c442083..2c27d8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [1.7.0] - 2026-08-24 + +### Changed + +- Go toolchain moves to 1.27.0, from the 1.26 line. The `go` directive in `go.mod` + is what every workflow feeds to `setup-go`, so this sets the language version and + the standard library CI builds against. The Dockerfile builder image moves to + `golang:1.27.0` to match. +- golangci-lint moves from v2.12.2 to v2.13.1. Its `modernize` linter flagged twelve + issues in the test suite, all now fixed: embedded fields written through the + promoted-field shorthand in composite literals, and one `errors.As` call replaced + with `errors.AsType`, which the 1.27 standard library adds. +- Every GitHub Action was checked against its latest release. All were already + pinned to the newest version by commit SHA, so none needed to move. + +### Known limits + +- `errors.AsType` means the test suite no longer builds on Go 1.26. + ## [1.6.1] - 2026-08-24 ### Changed diff --git a/Dockerfile b/Dockerfile index 6f2456e..6417dca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG GIT_SHA="no-sha" -FROM --platform=$BUILDPLATFORM golang:1.26.7 AS build-env +FROM --platform=$BUILDPLATFORM golang:1.27.0 AS build-env WORKDIR /app ARG GIT_SHA ARG TARGETARCH diff --git a/go.mod b/go.mod index 5de171e..38ae7c3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/yottabytesolutions/meterlogger -go 1.26.7 +go 1.27.0 require ( github.com/ClickHouse/clickhouse-go/v2 v2.48.0 diff --git a/internal/adapters/sink/mqtt/writers_test.go b/internal/adapters/sink/mqtt/writers_test.go index 302d793..ff8dcbe 100644 --- a/internal/adapters/sink/mqtt/writers_test.go +++ b/internal/adapters/sink/mqtt/writers_test.go @@ -449,9 +449,10 @@ func TestDucoWriter_Nodes(t *testing.T) { ctx := context.Background() // RF sensor node. - rf := domain.DucoRFSensorStatus{BaseDucoNodeStatus: ducoBase(), Temp: 21.5, Co2: 600, Rh: 55} - rf.Node = 3 - rf.DevType = "SENSO" + rfBase := ducoBase() + rfBase.Node = 3 + rfBase.DevType = "SENSO" + rf := domain.DucoRFSensorStatus{BaseDucoNodeStatus: rfBase, Temp: 21.5, Co2: 600, Rh: 55} if err := w.StoreNodeData(ctx, rf); err != nil { t.Fatalf("StoreNodeData rf: %v", err) } @@ -472,8 +473,9 @@ func TestDucoWriter_Nodes(t *testing.T) { } // Box node and valve node land on their own topics. - boxNode := domain.DucoNodeBoxStatus{BaseDucoNodeStatus: ducoBase(), Trgt: 30, Actl: 20, Temp: 20, Co2: 450, Rh: 40} - boxNode.Node = 1 + boxBase := ducoBase() + boxBase.Node = 1 + boxNode := domain.DucoNodeBoxStatus{BaseDucoNodeStatus: boxBase, Trgt: 30, Actl: 20, Temp: 20, Co2: 450, Rh: 40} if err := w.StoreNodeData(ctx, boxNode); err != nil { t.Fatalf("StoreNodeData box node: %v", err) } diff --git a/internal/adapters/sink/qdb/qdb_test.go b/internal/adapters/sink/qdb/qdb_test.go index 271aa99..cd13081 100644 --- a/internal/adapters/sink/qdb/qdb_test.go +++ b/internal/adapters/sink/qdb/qdb_test.go @@ -664,14 +664,12 @@ func TestDucoQuestDBRepository_StoreNodeData_RFSensor(t *testing.T) { client, sender := newTestDBClient() repo := NewDucoQuestDBRepository(client, "ventilation", testLogger()) node := domain.DucoRFSensorStatus{ - BaseDucoNodeStatus: domain.BaseDucoNodeStatus{ - Node: 3, - DevType: "UCCO2", - Location: "living room", - }, - Co2: 800.0, - Temp: 21.5, - Rh: 55.0, + Node: 3, + DevType: "UCCO2", + Location: "living room", + Co2: 800.0, + Temp: 21.5, + Rh: 55.0, } if err := repo.StoreNodeData(context.Background(), node); err != nil { t.Fatalf("StoreNodeData(RFSensor) unexpected error: %v", err) @@ -691,13 +689,11 @@ func TestDucoQuestDBRepository_StoreNodeData_BoxNode(t *testing.T) { client, sender := newTestDBClient() repo := NewDucoQuestDBRepository(client, "ventilation", testLogger()) node := domain.DucoNodeBoxStatus{ - BaseDucoNodeStatus: domain.BaseDucoNodeStatus{ - Node: 1, - DevType: "BOX", - }, - Trgt: 100, - Actl: 80, - Co2: 600.0, + Node: 1, + DevType: "BOX", + Trgt: 100, + Actl: 80, + Co2: 600.0, } if err := repo.StoreNodeData(context.Background(), node); err != nil { t.Fatalf("StoreNodeData(BoxNode) unexpected error: %v", err) @@ -716,12 +712,10 @@ func TestDucoQuestDBRepository_StoreNodeData_Valve(t *testing.T) { client, sender := newTestDBClient() repo := NewDucoQuestDBRepository(client, "ventilation", testLogger()) node := domain.DucoNodeBoxValveStatus{ - BaseDucoNodeStatus: domain.BaseDucoNodeStatus{ - Node: 2, - DevType: "VLV", - }, - Trgt: 50, - Actl: 45, + Node: 2, + DevType: "VLV", + Trgt: 50, + Actl: 45, } if err := repo.StoreNodeData(context.Background(), node); err != nil { t.Fatalf("StoreNodeData(Valve) unexpected error: %v", err) diff --git a/internal/adapters/source/ducobox/ducobox_test.go b/internal/adapters/source/ducobox/ducobox_test.go index cbea395..646ae97 100644 --- a/internal/adapters/source/ducobox/ducobox_test.go +++ b/internal/adapters/source/ducobox/ducobox_test.go @@ -18,10 +18,10 @@ func testLogger() *slog.Logger { func TestParseDucoNodeStatus_Box(t *testing.T) { node := nodeBoxStatusDTO{ - baseNodeStatusDTO: baseNodeStatusDTO{Node: 1, DevType: devTypeBox}, - Trgt: 100, - Actl: 80, - Rh: 55.5, + Node: 1, DevType: devTypeBox, + Trgt: 100, + Actl: 80, + Rh: 55.5, } data, err := json.Marshal(node) if err != nil { @@ -63,9 +63,9 @@ func TestParseDucoNodeStatus_BoxLowercase(t *testing.T) { func TestParseDucoNodeStatus_VLV(t *testing.T) { node := nodeBoxValveStatusDTO{ - baseNodeStatusDTO: baseNodeStatusDTO{Node: 2, DevType: devTypeValve}, - Trgt: 50, - Actl: 45, + Node: 2, DevType: devTypeValve, + Trgt: 50, + Actl: 45, } data, err := json.Marshal(node) if err != nil { @@ -88,9 +88,9 @@ func TestParseDucoNodeStatus_VLV(t *testing.T) { func TestParseDucoNodeStatus_UCCO2(t *testing.T) { node := rfSensorStatusDTO{ - baseNodeStatusDTO: baseNodeStatusDTO{Node: 3, DevType: devTypeUCCO2}, - Co2: 800.0, - Rh: 0, + Node: 3, DevType: devTypeUCCO2, + Co2: 800.0, + Rh: 0, } data, err := json.Marshal(node) if err != nil { @@ -113,9 +113,9 @@ func TestParseDucoNodeStatus_UCCO2(t *testing.T) { func TestParseDucoNodeStatus_UCRH(t *testing.T) { node := rfSensorStatusDTO{ - baseNodeStatusDTO: baseNodeStatusDTO{Node: 4, DevType: devTypeUCRH}, - Co2: 0, - Rh: 65.0, + Node: 4, DevType: devTypeUCRH, + Co2: 0, + Rh: 65.0, } data, err := json.Marshal(node) if err != nil { @@ -259,8 +259,8 @@ func TestDucoReader_ReadBoxStatus_InvalidJSON(t *testing.T) { func TestDucoReader_ReadNodeStatus_Success(t *testing.T) { node := nodeBoxStatusDTO{ - baseNodeStatusDTO: baseNodeStatusDTO{Node: 1, DevType: devTypeBox}, - Trgt: 100, + Node: 1, DevType: devTypeBox, + Trgt: 100, } body, _ := json.Marshal(node) diff --git a/internal/adapters/source/enphase/token_test.go b/internal/adapters/source/enphase/token_test.go index 7aa9fff..9401a39 100644 --- a/internal/adapters/source/enphase/token_test.go +++ b/internal/adapters/source/enphase/token_test.go @@ -249,8 +249,7 @@ func TestCloudClient_RejectsSelfSignedTLS(t *testing.T) { _ = resp.Body.Close() t.Fatal("cloud client should reject self-signed TLS") } - var certErr *tls.CertificateVerificationError - if !errors.As(err, &certErr) { + if _, ok := errors.AsType[*tls.CertificateVerificationError](err); !ok { t.Errorf("expected certificate verification error, got: %v", err) } } diff --git a/internal/service/service_test.go b/internal/service/service_test.go index efb25f5..e6335aa 100644 --- a/internal/service/service_test.go +++ b/internal/service/service_test.go @@ -565,7 +565,7 @@ func TestDucoLoggingService_Start_Flushes(t *testing.T) { func TestDucoLoggingService_Start_WithNodes(t *testing.T) { boxStatus := domain.DucoBoxStatus{} nodeData := domain.DucoNodeBoxStatus{ - BaseDucoNodeStatus: domain.BaseDucoNodeStatus{Node: 1, DevType: "BOX"}, + Node: 1, DevType: "BOX", } reader := &mockDucoReader{ boxStatus: boxStatus, From d99a5acd1671df90f778d85a11eab415b14a0b49 Mon Sep 17 00:00:00 2001 From: Jeroen Date: Mon, 24 Aug 2026 11:10:15 +0200 Subject: [PATCH 2/2] Run CodeQL from a workflow instead of default setup Default setup pins its own Go toolchain and runs the extractor with GOTOOLCHAIN=local, so it failed on this branch with "go.mod requires go >= 1.27.0 (running go 1.26.6)". It cannot build a module whose go directive is newer than whatever the CodeQL bundle ships. Driving CodeQL from a workflow lets setup-go install the toolchain go.mod asks for before the build, so a Go bump no longer blocks code scanning. Same languages and the same security-extended query suite as the default setup it replaces. Default setup is now disabled on the repository. --- .github/workflows/codeql.yml | 68 ++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 5 +++ 2 files changed, 73 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..dd474e2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,68 @@ +name: CodeQL + +# Replaces CodeQL default setup. Default setup pins its own Go toolchain and +# runs the extractor with GOTOOLCHAIN=local, so it cannot build a module whose +# go directive is newer than the bundle. Driving it here lets setup-go install +# the toolchain go.mod asks for, so a Go bump never blocks code scanning. + +on: + push: + branches: + - master + pull_request: + branches: + - master + schedule: + # Weekly, Tuesday 05:44 UTC. Off the top of the hour and off the Security + # workflow's Monday slot. + - cron: '44 5 * * 2' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + # Uploading results to the code scanning API. + security-events: write + strategy: + fail-fast: false + matrix: + include: + - language: go + build-mode: manual + - language: actions + build-mode: none + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Go + if: matrix.language == 'go' + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: go.mod + + - name: Initialize CodeQL + uses: github/codeql-action/init@c16c0f3f2812ec4bb3750a5ed64873fe2ce0fbef # codeql-bundle-v2.26.3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # Matches the query suite and threat model the previous default + # setup ran with. + queries: security-extended + + - name: Build + if: matrix.build-mode == 'manual' + run: go build ./... + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@c16c0f3f2812ec4bb3750a5ed64873fe2ce0fbef # codeql-bundle-v2.26.3 + with: + category: /language:${{ matrix.language }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c27d8b..f7d0ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). with `errors.AsType`, which the 1.27 standard library adds. - Every GitHub Action was checked against its latest release. All were already pinned to the newest version by commit SHA, so none needed to move. +- CodeQL moves from default setup to a workflow in the repository. Default setup + pins its own Go toolchain and runs the extractor with `GOTOOLCHAIN=local`, so it + could not build a module whose `go` directive asks for 1.27.0. The workflow runs + `setup-go` against `go.mod` first, so a future Go bump will not block code + scanning. Same languages, same `security-extended` query suite as before. ### Known limits