diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml
index f9ecc83..d84b91f 100644
--- a/.github/workflows/push.yaml
+++ b/.github/workflows/push.yaml
@@ -13,11 +13,11 @@ jobs:
- name: checkout main branch
uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
- go-version: 1.22
+ go-version: '1.25'
- - run: go test -coverprofile=cover.out ./...
+ - run: go test -v -race -coverprofile=cover.out ./...
- name: checkout covrage branch
uses: actions/checkout@v4
diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml
index 34dc084..9e42913 100644
--- a/.github/workflows/validate.yaml
+++ b/.github/workflows/validate.yaml
@@ -12,19 +12,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: extractions/setup-just@v2
- - run: just lint
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.25'
+ - uses: golangci/golangci-lint-action@v7
+ with:
+ version: v2.6.1
test:
name: Unit Tests
runs-on: ubuntu-latest
strategy:
matrix:
- go: ["1.21", "1.22"]
+ go: ["1.24", "1.25"]
steps:
- uses: actions/checkout@v4
- - uses: extractions/setup-just@v2
- - run: GO_VERSION=${{ matrix.go }} just test
+ - uses: actions/setup-go@v5
+ with:
+ go-version: ${{ matrix.go }}
+ - run: go test -race ./...
inttest:
name: Integration Tests
diff --git a/.gitignore b/.gitignore
index 3626c4d..3a8c3c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
.idea/
confd-basic*.zip
out
+cover.out
+*.test
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..3e88561
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,28 @@
+version: "2"
+linters:
+ enable:
+ - errorlint
+ settings:
+ errcheck:
+ check-type-assertions: true
+ check-blank: true
+ errorlint:
+ errorf: true
+ asserts: true
+ comparison: true
+ exclusions:
+ generated: lax
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
+formatters:
+ enable:
+ - gofmt
+ - goimports
+ exclusions:
+ generated: lax
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
diff --git a/Dockerfile b/Dockerfile
index 3296ae5..760195b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,4 @@
-ARG GO_VER=1.20
-ARG GOLANGCI_VER=1.51
+ARG GO_VER=1.24
FROM golang:${GO_VER} as base
WORKDIR /src
@@ -7,22 +6,6 @@ COPY go.mod go.sum /src/
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download -x
-FROM golangci/golangci-lint:v${GOLANGCI_VER}-alpine AS lint-base
-
-FROM base AS lint
-RUN --mount=target=. \
- --mount=from=lint-base,src=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
- --mount=type=cache,target=/go/pkg/mod \
- --mount=type=cache,target=/root/.cache/go-build \
- --mount=type=cache,target=/root/.cache/golangci-lint \
- golangci-lint run --timeout 10m0s ./...
-
-FROM base as unittest
-RUN --mount=target=/src \
- --mount=type=cache,target=/go/pkg/mod \
- --mount=type=cache,target=/root/.cache/go-build \
- mkdir /out && \
- go test -v -race -coverprofile=/out/cover.out ./... | tee /out/test.stdout
FROM base AS inttest
RUN apt update && apt install -y \
@@ -37,8 +20,4 @@ RUN --mount=target=/src \
go test --tags=inttest -c -o /out/inttest.test
WORKDIR /out
COPY inttest/wait-for-hello.sh .
-CMD ./inttest.test -test.v -test.race
-
-
-FROM scratch AS unittest-coverage
-COPY --from=unittest /out /
\ No newline at end of file
+CMD ./inttest.test -test.v -test.race
\ No newline at end of file
diff --git a/README.md b/README.md
index a2fd730..cc81cdd 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
This library is used to create client applications for connecting to network devices via NETCONF.
-Like Go itself, only the latest two Go versions are tested and supported (Go 1.24 or Go 1.23).
+Like Go itself, only the latest two Go versions are tested and supported (Go 1.24 or Go 1.26).
NOTICE: The API is pretty stable but not finalized yet. So changes may happen before a 1.0 release. Check for API changes when you upgrade.
diff --git a/example_ssh_test.go b/example_ssh_test.go
index 6f6924f..544f717 100644
--- a/example_ssh_test.go
+++ b/example_ssh_test.go
@@ -5,9 +5,9 @@ import (
"log"
"time"
+ "golang.org/x/crypto/ssh"
"nemith.io/netconf"
ncssh "nemith.io/netconf/transport/ssh"
- "golang.org/x/crypto/ssh"
)
const sshAddr = "myrouter.example.com:830"
@@ -27,7 +27,7 @@ func Example_ssh() {
if err != nil {
panic(err)
}
- defer transport.Close()
+ defer transport.Close() // nolint:errcheck
session, err := netconf.Open(transport)
if err != nil {
diff --git a/example_tls_test.go b/example_tls_test.go
index ee91fc0..c7c9ab7 100644
--- a/example_tls_test.go
+++ b/example_tls_test.go
@@ -55,13 +55,13 @@ func Example_tls() {
if err != nil {
panic(err)
}
- defer transport.Close()
+ defer transport.Close() // nolint:errcheck
session, err := netconf.Open(transport)
if err != nil {
panic(err)
}
- defer session.Close(context.Background())
+ defer session.Close(context.Background()) // nolint:errcheck
// timeout for the call itself.
ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
diff --git a/go.mod b/go.mod
index 0bf8888..a6824de 100644
--- a/go.mod
+++ b/go.mod
@@ -1,18 +1,16 @@
module nemith.io/netconf
-go 1.23.0
-
-toolchain go1.24.1
+go 1.24.10
require (
- github.com/stretchr/testify v1.11.0
- golang.org/x/crypto v0.34.0
+ github.com/stretchr/testify v1.11.1
+ golang.org/x/crypto v0.45.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
- golang.org/x/sys v0.30.0 // indirect
+ golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39
+ golang.org/x/sys v0.38.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index f835c5a..7f59f97 100644
--- a/go.sum
+++ b/go.sum
@@ -2,16 +2,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
-github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
-golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
-golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
-golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
-golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
+golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/inttest/README.md b/inttest/README.md
index 44d1654..1824a31 100644
--- a/inttest/README.md
+++ b/inttest/README.md
@@ -88,6 +88,6 @@ CONFD_IMAGE=conf-basic:7.8 just ceos
Netopeer2 is an opensource NETCONF server. A docker image is automaticall built when running the tests so no additional work is needed.
```plain
-cd inttent
+cd inttest
just netopeer2
```
diff --git a/inttest/justfile b/inttest/justfile
index 18ae056..6cf6728 100644
--- a/inttest/justfile
+++ b/inttest/justfile
@@ -17,7 +17,7 @@ args := '\
--exit-code-from inttest
'
-all: csrx ceos confd netopeer2
+all: csrx ceos confd
csrx:
#!/usr/bin/env bash
diff --git a/justfile b/justfile
index 763e3bc..13c02ce 100644
--- a/justfile
+++ b/justfile
@@ -5,26 +5,14 @@ pwd := justfile_directory()
@list:
just --list
-golangci_version := "1.51"
-go_version := env_var_or_default("GO_VERSION", "1.20")
-
test *args:
- docker buildx build \
- --build-arg GO_VER={{ go_version }} \
- --output out/test \
- --target unittest-coverage . \
- {{ args }}
- cat out/test/test.stdout
+ go test -v -race ./... {{ args }}
lint *args:
- @docker buildx build \
- --build-arg GO_VER={{ go_version }} \
- --build-arg GOLANGCI_VER={{ golangci_version }} \
- --target lint . \
- {{ args }}
+ golangci-lint run --timeout 10m0s ./... {{ args }}
inttest:
just inttest/all
clean:
- @rm -rf ./out
\ No newline at end of file
+ @rm -rf ./out
diff --git a/ops.go b/ops.go
index 6fdf571..b7ed6cf 100644
--- a/ops.go
+++ b/ops.go
@@ -322,7 +322,7 @@ type LockReq struct {
func (s *Session) Lock(ctx context.Context, target Datastore) error {
req := LockReq{
- XMLName: xml.Name{Local: "lock"},
+ XMLName: xml.Name{Space: "urn:ietf:params:xml:ns:netconf:base:1.0", Local: "lock"},
Target: target,
}
@@ -332,7 +332,7 @@ func (s *Session) Lock(ctx context.Context, target Datastore) error {
func (s *Session) Unlock(ctx context.Context, target Datastore) error {
req := LockReq{
- XMLName: xml.Name{Local: "unlock"},
+ XMLName: xml.Name{Space: "urn:ietf:params:xml:ns:netconf:base:1.0", Local: "unlock"},
Target: target,
}
diff --git a/ops_test.go b/ops_test.go
index b3879ee..79097a4 100644
--- a/ops_test.go
+++ b/ops_test.go
@@ -339,7 +339,7 @@ func TestLock(t *testing.T) {
{
target: Candidate,
matches: []*regexp.Regexp{
- regexp.MustCompile(`\S*\S*\S*\S*`),
+ regexp.MustCompile(`\S*\S*\S*\S*`),
},
},
}
@@ -373,7 +373,7 @@ func TestUnlock(t *testing.T) {
{
target: Candidate,
matches: []*regexp.Regexp{
- regexp.MustCompile(`\S*\S*\S*\S*`),
+ regexp.MustCompile(`\S*\S*\S*\S*`),
},
},
}
diff --git a/session.go b/session.go
index cedb286..0ea1fb3 100644
--- a/session.go
+++ b/session.go
@@ -94,7 +94,7 @@ func Open(transport transport.Transport, opts ...SessionOption) (*Session, error
// this needs a timeout of some sort.
if err := s.handshake(); err != nil {
- s.tr.Close()
+ s.tr.Close() // nolint:errcheck // TODO: catch and log err
return nil, err
}
@@ -115,8 +115,7 @@ func (s *Session) handshake() error {
if err != nil {
return err
}
- // TODO: capture this error some how (ah defer and errors)
- defer r.Close()
+ defer r.Close() // nolint:errcheck // TODO: catch and log err
var serverMsg helloMsg
if err := xml.NewDecoder(r).Decode(&serverMsg); err != nil {
@@ -188,7 +187,7 @@ func (s *Session) recvMsg() error {
if err != nil {
return err
}
- defer r.Close()
+ defer r.Close() // nolint:errcheck // TODO: catch error and log
dec := xml.NewDecoder(r)
root, err := startElement(dec)
@@ -379,7 +378,7 @@ func (s *Session) Close(ctx context.Context) error {
}
}
- if callErr != io.EOF {
+ if !errors.Is(callErr, io.EOF) {
return callErr
}
diff --git a/transport/frame.go b/transport/frame.go
index 9fdabce..208089b 100644
--- a/transport/frame.go
+++ b/transport/frame.go
@@ -219,7 +219,9 @@ func (r *chunkReader) Read(p []byte) (int, error) {
return 0, ErrInvalidIO
}
// make sure we can't try to read more than the max chunk
- p = p[:maxChunk]
+ if len(p) > maxChunk {
+ p = p[:maxChunk]
+ }
// done with existing chunk so grab the next one
if r.chunkLeft <= 0 {
@@ -271,10 +273,10 @@ func (r *chunkReader) Close() error {
// readHeader return io.EOF when it encounter the end-of-frame
// marker ("\n##\n")
err := r.readHeader()
- switch err {
- case nil:
+ switch {
+ case err == nil:
break
- case io.EOF:
+ case errors.Is(err, io.EOF):
return nil
default:
return err
@@ -381,7 +383,7 @@ func (r *eomReader) Close() error {
var err error
for err == nil {
_, err = r.ReadByte()
- if err == io.EOF {
+ if errors.Is(err, io.EOF) {
return nil
}
}
diff --git a/transport/frame_test.go b/transport/frame_test.go
index 3cac2a2..edb2c40 100644
--- a/transport/frame_test.go
+++ b/transport/frame_test.go
@@ -3,6 +3,7 @@ package transport
import (
"bufio"
"bytes"
+ "errors"
"io"
"testing"
@@ -97,13 +98,12 @@ func TestChunkReaderReadByte(t *testing.T) {
}
buf = buf[:n]
- if err != io.EOF {
+ if !errors.Is(err, io.EOF) {
assert.Equal(t, tc.err, err)
}
assert.Equal(t, tc.want, buf)
- // TODO: validate the return error
- r.Close()
+ r.Close() //nolint:errcheck // TODO: validate the close error
})
}
}
@@ -120,7 +120,7 @@ func TestChunkReaderRead(t *testing.T) {
assert.Equal(t, tc.want, got)
// TODO: validate the return error
- r.Close()
+ r.Close() // nolint:errcheck // TODO: validate the close error
})
}
}
@@ -159,7 +159,7 @@ func BenchmarkChunkedReadByte(b *testing.B) {
b.Run(bc.name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- _, _ = bc.r.ReadByte()
+ _, _ = bc.r.ReadByte() //nolint:errcheck
b.SetBytes(1)
}
})
@@ -266,14 +266,13 @@ func TestEOMReadByte(t *testing.T) {
}
buf = buf[:n]
- if err != io.EOF {
+ if !errors.Is(err, io.EOF) {
assert.Equal(t, err, tc.err)
}
assert.Equal(t, tc.want, buf)
- // TODO: validate the return error
- r.Close()
+ r.Close() // nolint:errcheck // TODO: validate the close error
})
}
}
@@ -287,8 +286,8 @@ func TestEOMRead(t *testing.T) {
got, err := io.ReadAll(r)
assert.Equal(t, err, tc.err)
assert.Equal(t, tc.want, got)
- // TODO: validate the return error
- r.Close()
+
+ r.Close() // nolint:errcheck // TODO: validate the close error
})
}
}
@@ -334,7 +333,7 @@ func BenchmarkEOMReadByte(b *testing.B) {
b.Run(bc.name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- _, _ = bc.r.ReadByte()
+ _, _ = bc.r.ReadByte() // nolint:errcheck
b.SetBytes(1)
}
})
diff --git a/transport/ssh/ssh.go b/transport/ssh/ssh.go
index b979132..80359fe 100644
--- a/transport/ssh/ssh.go
+++ b/transport/ssh/ssh.go
@@ -6,12 +6,12 @@ import (
"io"
"net"
- "nemith.io/netconf/transport"
"golang.org/x/crypto/ssh"
+ "nemith.io/netconf/transport"
)
// alias it to a private type so we can make it private when embedding
-type framer = transport.Framer //nolint:golint,unused
+type framer = transport.Framer
// Transport implements RFC6242 for implementing NETCONF protocol over SSH.
type Transport struct {
@@ -55,7 +55,7 @@ func Dial(ctx context.Context, network, addr string, config *ssh.ClientConfig) (
case <-ctx.Done():
// context is canceled so close the underlying connection. Will
// will catch ctx.Err() later.
- conn.Close()
+ _ = conn.Close() // nolint:errcheck // TODO: catch and log err
case <-done:
}
}()
diff --git a/transport/ssh/ssh_test.go b/transport/ssh/ssh_test.go
index d118974..307bfba 100644
--- a/transport/ssh/ssh_test.go
+++ b/transport/ssh/ssh_test.go
@@ -80,7 +80,8 @@ func newTestServer(t *testing.T, handlerFn func(*testing.T, ssh.Channel, <-chan
for newChannel := range chans {
if newChannel.ChannelType() != "session" {
- _ = newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
+ err := newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
+ assert.NoError(t, err)
continue
}
@@ -110,11 +111,15 @@ func TestTransport(t *testing.T) {
if req.Type != "subsystem" || !bytes.Equal(req.Payload[4:], []byte("netconf")) {
panic(fmt.Sprintf("unknown ssh request: %q: %q", req.Type, req.Payload))
}
- _ = req.Reply(true, nil)
+ err := req.Reply(true, nil)
+ assert.NoError(t, err)
}
}()
- _, _ = io.WriteString(ch, "muffins]]>]]>")
- _, _ = io.Copy(&srvIn, ch)
+ _, err := io.WriteString(ch, "muffins]]>]]>")
+ require.NoError(t, err)
+
+ _, err = io.Copy(&srvIn, ch)
+ require.NoError(t, err)
close(srvDone)
})
require.NoError(t, err)
@@ -137,7 +142,8 @@ func TestTransport(t *testing.T) {
assert.NoError(t, err)
out := "a man a plan a canal panama"
- _, _ = io.WriteString(w, out)
+ _, err = io.WriteString(w, out)
+ assert.NoError(t, err)
err = w.Close()
assert.NoError(t, err)
diff --git a/transport/tls/tls.go b/transport/tls/tls.go
index 71e5cdb..1ac17b5 100644
--- a/transport/tls/tls.go
+++ b/transport/tls/tls.go
@@ -9,7 +9,7 @@ import (
)
// alias it to a private type so we can make it private when embedding
-type framer = transport.Framer //nolint:golint,unused
+type framer = transport.Framer
// Transport implements RFC7589 for implementing NETCONF over TLS.
type Transport struct {