Optimize CI caching for Blacksmith runners, fix SNMP bulk walk on Mikrotik

- Dockerfile: add BuildKit cache mounts for Go module and build caches,
  pin both stages to Alpine 3.23
- CI: replace BUILDKIT_INLINE_CACHE with registry-based layer caching
  (mode=max) for branch and release builds
- .dockerignore: exclude test files, Rust legacy, nix, and other
  non-build artifacts from Docker context
- SNMP: lower MaxRepetitions from default 50 to 10 to prevent Mikrotik
  routers from timing out during bulk walks
This commit is contained in:
Graham McIntire 2026-02-11 10:30:17 -06:00
parent 86da744e68
commit a757bd7615
No known key found for this signature in database
4 changed files with 48 additions and 15 deletions

View file

@ -1,11 +1,24 @@
.git/ .git/
.github/
.gitignore .gitignore
.direnv/
.envrc
.cargo/
.claude/
.conductor/
Dockerfile Dockerfile
.dockerignore .dockerignore
README.md README.md
docker-compose.example.yml
flake.nix
flake.lock
cover.out
coverage/
proto/
*.db *.db
*.db-shm *.db-shm
*.db-wal *.db-wal
*_test.go
src/ src/
native/ native/
target/ target/

View file

@ -86,8 +86,8 @@ jobs:
context: . context: .
push: true push: true
provenance: false provenance: false
build-args: | cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
BUILDKIT_INLINE_CACHE=1 cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
tags: | tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.branch.outputs.name }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.branch.outputs.name }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
@ -170,7 +170,8 @@ jobs:
provenance: false provenance: false
build-args: | build-args: |
VERSION=${{ needs.version.outputs.version }} VERSION=${{ needs.version.outputs.version }}
BUILDKIT_INLINE_CACHE=1 cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }}
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }},mode=max
tags: | tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.version }}-${{ matrix.arch }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.version }}-${{ matrix.arch }}
docker.io/${{ env.DOCKERHUB_IMAGE }}:${{ needs.version.outputs.version }}-${{ matrix.arch }} docker.io/${{ env.DOCKERHUB_IMAGE }}:${{ needs.version.outputs.version }}-${{ matrix.arch }}

View file

@ -1,12 +1,15 @@
FROM golang:1.25-alpine AS builder FROM golang:1.25-alpine3.23 AS builder
WORKDIR /app WORKDIR /app
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN go mod download RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . . COPY . .
ARG VERSION=dev ARG VERSION=dev
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION}" -o towerops-agent . RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION}" -o towerops-agent .
FROM alpine:3.21 FROM alpine:3.23
RUN apk add --no-cache ca-certificates iputils RUN apk add --no-cache ca-certificates iputils
COPY --from=builder /app/towerops-agent /usr/local/bin/towerops-agent COPY --from=builder /app/towerops-agent /usr/local/bin/towerops-agent
RUN adduser -D -u 1000 towerops && chown towerops /usr/local/bin/towerops-agent RUN adduser -D -u 1000 towerops && chown towerops /usr/local/bin/towerops-agent

24
snmp.go
View file

@ -10,6 +10,21 @@ import (
"github.com/towerops-app/towerops-agent/pb" "github.com/towerops-app/towerops-agent/pb"
) )
// snmpQuerier abstracts SNMP operations for testability.
type snmpQuerier interface {
Get(oids []string) (*gosnmp.SnmpPacket, error)
BulkWalkAll(rootOid string) ([]gosnmp.SnmpPDU, error)
}
// snmpDial connects to an SNMP device and returns a querier + close function.
var snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
conn, err := newSnmpConn(dev)
if err != nil {
return nil, nil, err
}
return conn, func() { _ = conn.Conn.Close() }, nil
}
// executeSnmpJob runs SNMP GET/WALK queries for a job and sends results. // executeSnmpJob runs SNMP GET/WALK queries for a job and sends results.
func executeSnmpJob(job *pb.AgentJob, resultCh chan<- *pb.SnmpResult) { func executeSnmpJob(job *pb.AgentJob, resultCh chan<- *pb.SnmpResult) {
dev := job.SnmpDevice dev := job.SnmpDevice
@ -18,12 +33,12 @@ func executeSnmpJob(job *pb.AgentJob, resultCh chan<- *pb.SnmpResult) {
return return
} }
conn, err := newSnmpConn(dev) conn, closeFn, err := snmpDial(dev)
if err != nil { if err != nil {
slog.Error("snmp connect", "job_id", job.JobId, "device", dev.Ip, "error", err) slog.Error("snmp connect", "job_id", job.JobId, "device", dev.Ip, "error", err)
return return
} }
defer func() { _ = conn.Conn.Close() }() defer closeFn()
oidValues := make(map[string]string) oidValues := make(map[string]string)
@ -85,7 +100,7 @@ func executeCredentialTest(job *pb.AgentJob, resultCh chan<- *pb.CredentialTestR
return return
} }
conn, err := newSnmpConn(dev) conn, closeFn, err := snmpDial(dev)
timestamp := time.Now().Unix() timestamp := time.Now().Unix()
if err != nil { if err != nil {
@ -97,7 +112,7 @@ func executeCredentialTest(job *pb.AgentJob, resultCh chan<- *pb.CredentialTestR
} }
return return
} }
defer func() { _ = conn.Conn.Close() }() defer closeFn()
result, err := conn.Get([]string{"1.3.6.1.2.1.1.1.0"}) result, err := conn.Get([]string{"1.3.6.1.2.1.1.1.0"})
if err != nil { if err != nil {
@ -130,6 +145,7 @@ func newSnmpConn(dev *pb.SnmpDevice) (*gosnmp.GoSNMP, error) {
Port: uint16(dev.Port), Port: uint16(dev.Port),
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
Retries: 2, Retries: 2,
MaxRepetitions: 10,
} }
// Transport // Transport