Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ jobs:
restore-keys: |
${{ runner.os }}-go1.22-

- name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 #v9.2.0
with:
version: v2.11

- name: Run unit tests
run: make testvv
env:
Expand Down
36 changes: 36 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: "2"
linters:
default: none
enable:
- sloglint
settings:
sloglint:
# Enforce key-value pair form for Info/Debug/Warn/Error/Log/With and
# the package-level slog equivalents. Use l.Log(ctx, level, ...) for
# custom levels instead of LogAttrs when you can.
#
# LogAttrs is also flagged by this rule because it takes ...slog.Attr;
# the few legitimate sites (where attrs is built up as a []slog.Attr)
# carry a //nolint:sloglint with rationale.
kv-only: true
# no-mixed-args is on by default: forbids mixing kv and attrs in one call.
# discard-handler is on by default (since Go 1.24): suggests
# slog.DiscardHandler over slog.NewTextHandler(io.Discard, nil).
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
10 changes: 5 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"net/url"
Expand All @@ -19,7 +20,6 @@ import (

"github.com/DefinedNet/dnapi/keys"
"github.com/DefinedNet/dnapi/message"
"github.com/sirupsen/logrus"
)

// Client communicates with the API server.
Expand Down Expand Up @@ -127,8 +127,8 @@ func mergeIPAddresses(plural []string, singular string) []string {
// generated DH X25519 public key to be signed by the CA, and an Ed 25519 public key for future API call authentication.
// On success it returns the Nebula config generated by the server, a Nebula private key PEM to be inserted into the
// config (see api.InsertConfigPrivateKey), credentials to be used in DNClient API requests, and a meta object.
func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code string) ([]byte, []byte, *keys.Credentials, *ConfigMeta, error) {
logger.WithFields(logrus.Fields{"server": c.dnServer}).Debug("Making enrollment request to API")
func (c *Client) Enroll(ctx context.Context, logger *slog.Logger, code string) ([]byte, []byte, *keys.Credentials, *ConfigMeta, error) {
logger.Debug("Making enrollment request to API", "server", c.dnServer)

// Generate newKeys for the enrollment request
newKeys, err := keys.New()
Expand Down Expand Up @@ -157,7 +157,7 @@ func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code str
}

reqID, r, err := callAPI[message.EnrollResponseData](ctx, c, "POST", message.EnrollEndpoint, payload)
l := logger.WithFields(logrus.Fields{"reqID": reqID})
l := logger.With("reqID", reqID)
if err != nil {
var apiErrors message.APIErrors
if errors.As(err, &apiErrors) && len(apiErrors) == 1 {
Expand All @@ -174,7 +174,7 @@ func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code str
}
}

l.WithError(err).Error("Enrollment request failed with unexpected error")
l.Error("Enrollment request failed with unexpected error", "error", err)
return nil, nil, nil, nil, &APIError{e: fmt.Errorf("unexpected error during enrollment: %w", err), ReqID: reqID}
}
l.Info("Enrollment request succeeded")
Expand Down
24 changes: 18 additions & 6 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -20,7 +21,6 @@ import (
"github.com/DefinedNet/dnapi/internal/testutil"
"github.com/DefinedNet/dnapi/keys"
"github.com/DefinedNet/dnapi/message"
"github.com/sirupsen/logrus"
"github.com/slackhq/nebula/cert"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -29,6 +29,15 @@ import (

type m map[string]interface{}

// dropTimeAttr is a slog.HandlerOptions.ReplaceAttr that drops the time
// attribute from a record, so JSON output is deterministic in tests.
func dropTimeAttr(_ []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
}

func TestEnroll(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -988,10 +997,10 @@ func TestStreamCommandResponse(t *testing.T) {
require.NoError(t, err)

// Configure a logger to write to a buffer and the stream
logger := logrus.New()
logger.SetFormatter(&logrus.JSONFormatter{})
logger.SetOutput(io.MultiWriter(sc, &buf))
logger.SetLevel(logrus.DebugLevel)
logger := slog.New(slog.NewJSONHandler(io.MultiWriter(sc, &buf), &slog.HandlerOptions{
Level: slog.LevelDebug,
ReplaceAttr: dropTimeAttr,
}))

logger.Info("Hello, world! info!")
logger.Warn("Hello, world! warning!")
Expand All @@ -1018,7 +1027,10 @@ func TestStreamCommandResponse(t *testing.T) {
sc, err = c.StreamCommandResponse(context.Background(), *creds, "responseToken")
require.NoError(t, err)

logger.SetOutput(io.MultiWriter(sc, &buf))
logger = slog.New(slog.NewJSONHandler(io.MultiWriter(sc, &buf), &slog.HandlerOptions{
Level: slog.LevelDebug,
ReplaceAttr: dropTimeAttr,
}))

logger.Info("Hello, world! info!")
logger.Warn("Hello, world! warning!")
Expand Down
16 changes: 9 additions & 7 deletions examples/simple/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"time"

"github.com/DefinedNet/dnapi"
"github.com/sirupsen/logrus"
)

func main() {
Expand All @@ -22,18 +22,20 @@ func main() {
os.Exit(1)
}

logger := logrus.New()
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
c := dnapi.NewClient("api-example/1.0", *server)

// initial enrollment example
config, pkey, creds, meta, err := c.Enroll(context.Background(), logger, *code)
if err != nil {
logger.WithError(err).Fatal("Failed to enroll")
logger.Error("Failed to enroll", "error", err)
os.Exit(1)
}

config, err = dnapi.InsertConfigPrivateKey(config, pkey)
if err != nil {
logger.WithError(err).Fatal("Failed to insert private key into config")
logger.Error("Failed to insert private key into config", "error", err)
os.Exit(1)
}

fmt.Printf(
Expand All @@ -53,7 +55,7 @@ func main() {
// check for an update and perform the update if available
updateAvailable, err := c.CheckForUpdate(context.Background(), *creds)
if err != nil {
logger.WithError(err).Error("Failed to check for update")
logger.Error("Failed to check for update", "error", err)
continue
}

Expand All @@ -63,13 +65,13 @@ func main() {
// this makes it less obvious to the caller that they need to save the new credentials to disk
config, pkey, newCreds, meta, err := c.DoUpdate(context.Background(), *creds)
if err != nil {
logger.WithError(err).Error("Failed to perform update")
logger.Error("Failed to perform update", "error", err)
continue
}

config, err = dnapi.InsertConfigPrivateKey(config, pkey)
if err != nil {
logger.WithError(err).Error("Failed to insert private key into config")
logger.Error("Failed to insert private key into config", "error", err)
continue
}

Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/DefinedNet/dnapi
go 1.25

require (
github.com/sirupsen/logrus v1.9.4
github.com/slackhq/nebula v1.10.3
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.47.0
Expand Down
45 changes: 21 additions & 24 deletions internal/testutil/testutil.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,34 @@
package testutil

import (
"io/ioutil"
"io"
"log/slog"
"os"

"github.com/sirupsen/logrus"
)

// NewTestLogger returns a *logrus.Logger struct configured for testing (e.g. end-to-end tests, unit tests, etc.)
func NewTestLogger() *logrus.Logger {
l := logrus.New()
l.SetFormatter(&logrus.JSONFormatter{
DisableTimestamp: true,
FieldMap: logrus.FieldMap{
logrus.FieldKeyMsg: "message",
},
})

v := os.Getenv("TEST_LOGS")
if v == "" {
l.SetOutput(ioutil.Discard)
return l
}
// NewTestLogger returns a *slog.Logger configured for testing (e.g.
// end-to-end tests, unit tests, etc.). Set the TEST_LOGS environment
// variable to 1/2/3 to raise the verbosity from info to debug/trace.
func NewTestLogger() *slog.Logger {
level := slog.LevelInfo
out := io.Discard

switch v {
switch os.Getenv("TEST_LOGS") {
case "":
// Keep the discard writer and info level default.
case "1":
// This is the default level but we are being explicit
l.SetLevel(logrus.InfoLevel)
out = os.Stdout
case "2":
l.SetLevel(logrus.DebugLevel)
out = os.Stdout
level = slog.LevelDebug
case "3":
l.SetLevel(logrus.TraceLevel)
out = os.Stdout
level = slog.Level(-8) // trace-equivalent; below Debug
default:
out = os.Stdout
}

return l
return slog.New(slog.NewJSONHandler(out, &slog.HandlerOptions{
Level: level,
}))
}
Loading