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/
.github/
.gitignore
.direnv/
.envrc
.cargo/
.claude/
.conductor/
Dockerfile
.dockerignore
README.md
docker-compose.example.yml
flake.nix
flake.lock
cover.out
coverage/
proto/
*.db
*.db-shm
*.db-wal
*_test.go
src/
native/
target/

View file

@ -86,8 +86,8 @@ jobs:
context: .
push: true
provenance: false
build-args: |
BUILDKIT_INLINE_CACHE=1
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.branch.outputs.name }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
@ -170,7 +170,8 @@ jobs:
provenance: false
build-args: |
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: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ 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
COPY go.mod go.sum ./
RUN go mod download
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
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
COPY --from=builder /app/towerops-agent /usr/local/bin/towerops-agent
RUN adduser -D -u 1000 towerops && chown towerops /usr/local/bin/towerops-agent

32
snmp.go
View file

@ -10,6 +10,21 @@ import (
"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.
func executeSnmpJob(job *pb.AgentJob, resultCh chan<- *pb.SnmpResult) {
dev := job.SnmpDevice
@ -18,12 +33,12 @@ func executeSnmpJob(job *pb.AgentJob, resultCh chan<- *pb.SnmpResult) {
return
}
conn, err := newSnmpConn(dev)
conn, closeFn, err := snmpDial(dev)
if err != nil {
slog.Error("snmp connect", "job_id", job.JobId, "device", dev.Ip, "error", err)
return
}
defer func() { _ = conn.Conn.Close() }()
defer closeFn()
oidValues := make(map[string]string)
@ -85,7 +100,7 @@ func executeCredentialTest(job *pb.AgentJob, resultCh chan<- *pb.CredentialTestR
return
}
conn, err := newSnmpConn(dev)
conn, closeFn, err := snmpDial(dev)
timestamp := time.Now().Unix()
if err != nil {
@ -97,7 +112,7 @@ func executeCredentialTest(job *pb.AgentJob, resultCh chan<- *pb.CredentialTestR
}
return
}
defer func() { _ = conn.Conn.Close() }()
defer closeFn()
result, err := conn.Get([]string{"1.3.6.1.2.1.1.1.0"})
if err != nil {
@ -126,10 +141,11 @@ func executeCredentialTest(job *pb.AgentJob, resultCh chan<- *pb.CredentialTestR
// newSnmpConn creates a gosnmp.GoSNMP connection from protobuf device config.
func newSnmpConn(dev *pb.SnmpDevice) (*gosnmp.GoSNMP, error) {
conn := &gosnmp.GoSNMP{
Target: dev.Ip,
Port: uint16(dev.Port),
Timeout: 10 * time.Second,
Retries: 2,
Target: dev.Ip,
Port: uint16(dev.Port),
Timeout: 10 * time.Second,
Retries: 2,
MaxRepetitions: 10,
}
// Transport