diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 5edf380..0b63d79 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -1,8 +1,8 @@ name: CI -# Go CI for sherlock. Runs `go vet`, `go test -race`, `go build` on every -# push and PR. No deploy pipeline — sherlock is operator-installed via -# `go install`, not host-deployed. +# Go CI for sherlock. Runs gofmt, go vet, errcheck, staticcheck, +# go test -race, and go build on every push and PR. No deploy pipeline +# — sherlock is operator-installed via `go install`, not host-deployed. # # Why no actions/checkout + actions/setup-go: # act_runner re-clones every third-party JS action from github.com per @@ -29,6 +29,10 @@ env: # x/sync now declare `go 1.25.0`, which transitively forces our module # to `go 1.25+`. 1.26.3 is the current upstream stable. GO_VERSION: "1.26.3" + # Pinned linter versions. Bump deliberately; the runner caches the + # built binaries under ~/go/bin so re-runs are no-ops. + ERRCHECK_VERSION: "v1.20.0" + STATICCHECK_VERSION: "v0.7.0" jobs: build: @@ -88,9 +92,43 @@ jobs: fetch --depth=1 origin "${GITHUB_SHA}" git checkout -q FETCH_HEAD + - name: Install linters (errcheck, staticcheck) + run: | + set -eu + # Both tools are cached under $HOME/go/bin across jobs since + # the host-executor runner persists the home directory. + if ! command -v errcheck >/dev/null 2>&1; then + echo "installing errcheck ${ERRCHECK_VERSION}" + go install "github.com/kisielk/errcheck@${ERRCHECK_VERSION}" + else + echo "errcheck already installed: $(errcheck -h 2>&1 | head -1 || true)" + fi + if ! command -v staticcheck >/dev/null 2>&1; then + echo "installing staticcheck ${STATICCHECK_VERSION}" + go install "honnef.co/go/tools/cmd/staticcheck@${STATICCHECK_VERSION}" + else + echo "staticcheck already installed: $(staticcheck -version)" + fi + + - name: gofmt + run: | + set -eu + diff=$(gofmt -l $(find . -name '*.go' -not -path './vendor/*')) + if [ -n "$diff" ]; then + echo "gofmt would reformat:" + echo "$diff" + exit 1 + fi + - name: go vet run: go vet ./... + - name: errcheck + run: errcheck ./... + + - name: staticcheck + run: staticcheck ./... + - name: go test -race run: go test ./... -race diff --git a/cmd/sherlock-broker/main.go b/cmd/sherlock-broker/main.go index 53ee04d..2c18e67 100644 --- a/cmd/sherlock-broker/main.go +++ b/cmd/sherlock-broker/main.go @@ -35,9 +35,9 @@ import ( var Version = "0.0.0-dev" const ( - envIssuer = "SHERLOCK_AUTHENTIK_ISSUER" - envClientID = "SHERLOCK_AUTHENTIK_CLIENT_ID" - envScopes = "SHERLOCK_AUTHENTIK_SCOPES" + envIssuer = "SHERLOCK_AUTHENTIK_ISSUER" + envClientID = "SHERLOCK_AUTHENTIK_CLIENT_ID" + envScopes = "SHERLOCK_AUTHENTIK_SCOPES" envIdleTimeout = "SHERLOCK_BROKER_IDLE" defaultScopes = "openid profile email offline_access" @@ -105,12 +105,20 @@ func runDaemon() { fmt.Fprintln(os.Stderr, "sherlock-broker:", err) os.Exit(4) } - defer lock.Release() + defer func() { + if err := lock.Release(); err != nil { + log.Printf("pidlock release: %v", err) + } + }() cfg := loadConfig() flow := authn.NewFlow(cfg, nil) - defer flow.Close() + defer func() { + if err := flow.Close(); err != nil { + log.Printf("flow close: %v", err) + } + }() refresher := authn.NewRefresher(cfg, nil) profiles, err := agent.Load(agent.LoadOptions{UserDir: mustAgentsDir()}) diff --git a/cmd/sherlock/main.go b/cmd/sherlock/main.go index ecad3a5..3e3a2d2 100644 --- a/cmd/sherlock/main.go +++ b/cmd/sherlock/main.go @@ -5,99 +5,98 @@ // // Routing rules: // -//sherlock version → print version, no broker -//sherlock login → ensure broker, OAuth flow, persist tokens -//sherlock logout [--shutdown]→ ensure broker, clear tokens, maybe stop it -//sherlock status → ensure broker, print status table -//sherlock run [...] → ensure broker, load profile, exec agent -//sherlock [...] → alias for `run ` when the -// first arg matches a loaded profile name +// sherlock version → print version, no broker +// sherlock login → ensure broker, OAuth flow, persist tokens +// sherlock logout [--shutdown]→ ensure broker, clear tokens, maybe stop it +// sherlock status → ensure broker, print status table +// sherlock run [...] → ensure broker, load profile, exec agent +// sherlock [...] → alias for `run ` when the +// +// first arg matches a loaded profile name package main import ( -"context" -"encoding/json" -"errors" -"flag" -"fmt" -"os" -"os/exec" -"runtime" -"time" + "context" + "flag" + "fmt" + "os" + "os/exec" + "runtime" + "time" -"gitea.alexandru.macocian.me/Charlie/sherlock/internal/agent" -"gitea.alexandru.macocian.me/Charlie/sherlock/internal/broker" -"gitea.alexandru.macocian.me/Charlie/sherlock/internal/keyring" -"gitea.alexandru.macocian.me/Charlie/sherlock/internal/rpc" -"gitea.alexandru.macocian.me/Charlie/sherlock/internal/socket" -"gitea.alexandru.macocian.me/Charlie/sherlock/internal/xdg" + "gitea.alexandru.macocian.me/Charlie/sherlock/internal/agent" + "gitea.alexandru.macocian.me/Charlie/sherlock/internal/broker" + "gitea.alexandru.macocian.me/Charlie/sherlock/internal/keyring" + "gitea.alexandru.macocian.me/Charlie/sherlock/internal/rpc" + "gitea.alexandru.macocian.me/Charlie/sherlock/internal/socket" + "gitea.alexandru.macocian.me/Charlie/sherlock/internal/xdg" ) // Version is overwritten at build time via -ldflags "-X main.Version=...". var Version = "0.0.0-dev" const ( -exitOK = 0 -exitGeneric = 1 -exitUsage = 2 -exitKeyring = 3 -exitBroker = 4 -exitAuth = 5 -exitLoginWait = 5 * time.Minute + exitOK = 0 + exitGeneric = 1 + exitUsage = 2 + exitKeyring = 3 + exitBroker = 4 + exitAuth = 5 + exitLoginWait = 5 * time.Minute ) func main() { -if len(os.Args) < 2 { -usage(os.Stderr) -os.Exit(exitUsage) -} + if len(os.Args) < 2 { + usage(os.Stderr) + os.Exit(exitUsage) + } -sub := os.Args[1] -rest := os.Args[2:] + sub := os.Args[1] + rest := os.Args[2:] -switch sub { -case "version", "--version", "-v": -fmt.Println(Version) -return -case "help", "--help", "-h": -usage(os.Stdout) -return -} + switch sub { + case "version", "--version", "-v": + fmt.Println(Version) + return + case "help", "--help", "-h": + usage(os.Stdout) + return + } -// Every non-trivial subcommand needs the keyring up. -if err := keyring.Probe(); err != nil { -fmt.Fprintln(os.Stderr, "sherlock:", err) -fmt.Fprintln(os.Stderr, "Hint:", keyring.RemediationHint()) -os.Exit(exitKeyring) -} + // Every non-trivial subcommand needs the keyring up. + if err := keyring.Probe(); err != nil { + fmt.Fprintln(os.Stderr, "sherlock:", err) + fmt.Fprintln(os.Stderr, "Hint:", keyring.RemediationHint()) + os.Exit(exitKeyring) + } -switch sub { -case "login": -runLogin(rest) -case "logout": -runLogout(rest) -case "status": -runStatus(rest) -case "run": -if len(rest) == 0 { -fmt.Fprintln(os.Stderr, "sherlock: run requires an agent name") -os.Exit(exitUsage) -} -runAgent(rest[0], rest[1:]) -default: -// Agent alias: `sherlock copilot ...` -> `run copilot ...` -if profileExists(sub) { -runAgent(sub, rest) -return -} -fmt.Fprintf(os.Stderr, "sherlock: unknown subcommand %q\n", sub) -usage(os.Stderr) -os.Exit(exitUsage) -} + switch sub { + case "login": + runLogin(rest) + case "logout": + runLogout(rest) + case "status": + runStatus(rest) + case "run": + if len(rest) == 0 { + fmt.Fprintln(os.Stderr, "sherlock: run requires an agent name") + os.Exit(exitUsage) + } + runAgent(rest[0], rest[1:]) + default: + // Agent alias: `sherlock copilot ...` -> `run copilot ...` + if profileExists(sub) { + runAgent(sub, rest) + return + } + fmt.Fprintf(os.Stderr, "sherlock: unknown subcommand %q\n", sub) + usage(os.Stderr) + os.Exit(exitUsage) + } } func usage(w *os.File) { -fmt.Fprintf(w, `sherlock - Charlie credential broker + agent wrapper + res, err := fmt.Fprintf(w, `sherlock - Charlie credential broker + agent wrapper Usage: sherlock [args...] @@ -114,199 +113,199 @@ Environment (consumed by the broker, set before `+"`sherlock login`"+`): SHERLOCK_AUTHENTIK_ISSUER, SHERLOCK_AUTHENTIK_CLIENT_ID, SHERLOCK_AUTHENTIK_SCOPES (optional), SHERLOCK_BROKER_IDLE (optional). `) + + if err != nil || res == 0 { + fmt.Fprintln(os.Stderr, "sherlock: usage:", err) + os.Exit(exitGeneric) + } } func dialBroker(ctx context.Context) *socket.Client { -sockPath, err := xdg.SocketPath() -if err != nil { -fmt.Fprintln(os.Stderr, "sherlock: socket path:", err) -os.Exit(exitBroker) -} -cli, err := broker.EnsureRunning(ctx, broker.EnsureRunningOptions{SocketPath: sockPath}) -if err != nil { -fmt.Fprintln(os.Stderr, "sherlock: cannot reach broker:", err) -os.Exit(exitBroker) -} -return cli + sockPath, err := xdg.SocketPath() + if err != nil { + fmt.Fprintln(os.Stderr, "sherlock: socket path:", err) + os.Exit(exitBroker) + } + cli, err := broker.EnsureRunning(ctx, broker.EnsureRunningOptions{SocketPath: sockPath}) + if err != nil { + fmt.Fprintln(os.Stderr, "sherlock: cannot reach broker:", err) + os.Exit(exitBroker) + } + return cli } func runLogin(args []string) { -fs := flag.NewFlagSet("login", flag.ExitOnError) -timeout := fs.Duration("timeout", exitLoginWait, "how long to wait for browser callback") -_ = fs.Parse(args) + fs := flag.NewFlagSet("login", flag.ExitOnError) + timeout := fs.Duration("timeout", exitLoginWait, "how long to wait for browser callback") + _ = fs.Parse(args) -ctx, cancel := context.WithTimeout(context.Background(), *timeout+10*time.Second) -defer cancel() -cli := dialBroker(ctx) -defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), *timeout+10*time.Second) + defer cancel() + cli := dialBroker(ctx) + defer func() { _ = cli.Close() }() -var start broker.LoginStartResult -if err := cli.Call(ctx, rpc.MethodLoginStart, struct{}{}, &start); err != nil { -fmt.Fprintln(os.Stderr, "sherlock: login_start:", err) -os.Exit(exitAuth) -} -fmt.Println("Opening browser for Authentik login...") -fmt.Println("If it doesn't open, visit:") -fmt.Println(" ", start.AuthURL) -if err := openBrowser(start.AuthURL); err != nil { -fmt.Fprintln(os.Stderr, "sherlock: could not open browser:", err) -} + var start broker.LoginStartResult + if err := cli.Call(ctx, rpc.MethodLoginStart, struct{}{}, &start); err != nil { + fmt.Fprintln(os.Stderr, "sherlock: login_start:", err) + os.Exit(exitAuth) + } + fmt.Println("Opening browser for Authentik login...") + fmt.Println("If it doesn't open, visit:") + fmt.Println(" ", start.AuthURL) + if err := openBrowser(start.AuthURL); err != nil { + fmt.Fprintln(os.Stderr, "sherlock: could not open browser:", err) + } -wait := broker.LoginWaitParams{State: start.State, TimeoutSeconds: int(timeout.Seconds())} -if err := cli.Call(ctx, rpc.MethodLoginWait, wait, nil); err != nil { -fmt.Fprintln(os.Stderr, "sherlock: login failed:", err) -os.Exit(exitAuth) -} -fmt.Println("Logged in.") + wait := broker.LoginWaitParams{State: start.State, TimeoutSeconds: int(timeout.Seconds())} + if err := cli.Call(ctx, rpc.MethodLoginWait, wait, nil); err != nil { + fmt.Fprintln(os.Stderr, "sherlock: login failed:", err) + os.Exit(exitAuth) + } + fmt.Println("Logged in.") } func runLogout(args []string) { -fs := flag.NewFlagSet("logout", flag.ExitOnError) -shutdown := fs.Bool("shutdown", false, "also stop the broker daemon") -_ = fs.Parse(args) + fs := flag.NewFlagSet("logout", flag.ExitOnError) + shutdown := fs.Bool("shutdown", false, "also stop the broker daemon") + _ = fs.Parse(args) -ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) -defer cancel() -cli := dialBroker(ctx) -defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + cli := dialBroker(ctx) + defer func() { _ = cli.Close() }() -if err := cli.Call(ctx, rpc.MethodLogout, struct{}{}, nil); err != nil { -fmt.Fprintln(os.Stderr, "sherlock: logout:", err) -os.Exit(exitAuth) -} -fmt.Println("Logged out.") -if *shutdown { -// Best-effort: the broker tears down the socket as part of -// shutdown so the response may race with EOF. -_ = cli.Call(ctx, rpc.MethodShutdown, struct{}{}, nil) -fmt.Println("Broker stopped.") -} + if err := cli.Call(ctx, rpc.MethodLogout, struct{}{}, nil); err != nil { + fmt.Fprintln(os.Stderr, "sherlock: logout:", err) + os.Exit(exitAuth) + } + fmt.Println("Logged out.") + if *shutdown { + // Best-effort: the broker tears down the socket as part of + // shutdown so the response may race with EOF. + _ = cli.Call(ctx, rpc.MethodShutdown, struct{}{}, nil) + fmt.Println("Broker stopped.") + } } func runStatus(_ []string) { -ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) -defer cancel() -cli := dialBroker(ctx) -defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + cli := dialBroker(ctx) + defer func() { _ = cli.Close() }() -var st broker.StatusResult -if err := cli.Call(ctx, rpc.MethodStatus, struct{}{}, &st); err != nil { -fmt.Fprintln(os.Stderr, "sherlock: status:", err) -os.Exit(exitGeneric) -} -if !st.LoggedIn { -fmt.Println("Not logged in.") -} else { -fmt.Printf("Logged in as %s <%s>\n", st.Name, st.Email) -fmt.Printf(" subject: %s\n", st.Subject) -if st.IDExpiresAt != nil { -fmt.Printf(" id token until: %s (%s)\n", -st.IDExpiresAt.Format(time.RFC3339), -humanizeUntil(*st.IDExpiresAt)) -} -if st.RefreshExpAt != nil { -fmt.Printf(" refresh until: %s (%s)\n", -st.RefreshExpAt.Format(time.RFC3339), -humanizeUntil(*st.RefreshExpAt)) -} -} -if len(st.Agents) > 0 { -fmt.Println("Available agents:") -for _, a := range st.Agents { -fmt.Printf(" - %s\n", a) -} -} + var st broker.StatusResult + if err := cli.Call(ctx, rpc.MethodStatus, struct{}{}, &st); err != nil { + fmt.Fprintln(os.Stderr, "sherlock: status:", err) + os.Exit(exitGeneric) + } + if !st.LoggedIn { + fmt.Println("Not logged in.") + } else { + fmt.Printf("Logged in as %s <%s>\n", st.Name, st.Email) + fmt.Printf(" subject: %s\n", st.Subject) + if st.IDExpiresAt != nil { + fmt.Printf(" id token until: %s (%s)\n", + st.IDExpiresAt.Format(time.RFC3339), + humanizeUntil(*st.IDExpiresAt)) + } + if st.RefreshExpAt != nil { + fmt.Printf(" refresh until: %s (%s)\n", + st.RefreshExpAt.Format(time.RFC3339), + humanizeUntil(*st.RefreshExpAt)) + } + } + if len(st.Agents) > 0 { + fmt.Println("Available agents:") + for _, a := range st.Agents { + fmt.Printf(" - %s\n", a) + } + } } func runAgent(name string, userArgs []string) { -profiles, err := loadProfiles() -if err != nil { -fmt.Fprintln(os.Stderr, "sherlock: load agent profiles:", err) -os.Exit(exitGeneric) -} -p, ok := profiles[name] -if !ok { -fmt.Fprintf(os.Stderr, "sherlock: unknown agent %q. Loaded: %v\n", name, agent.SortedNames(profiles)) -os.Exit(exitUsage) -} + profiles, err := loadProfiles() + if err != nil { + fmt.Fprintln(os.Stderr, "sherlock: load agent profiles:", err) + os.Exit(exitGeneric) + } + p, ok := profiles[name] + if !ok { + fmt.Fprintf(os.Stderr, "sherlock: unknown agent %q. Loaded: %v\n", name, agent.SortedNames(profiles)) + os.Exit(exitUsage) + } -// Ensure broker is up so future MCPs (Phase 2+) can reach it. -ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) -cli := dialBroker(ctx) -_ = cli.Close() -cancel() + // Ensure broker is up so future MCPs (Phase 2+) can reach it. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + cli := dialBroker(ctx) + _ = cli.Close() + cancel() -// Phase 1: empty servers map. Phase 2 fills this from services.d. -mcpPath, err := agent.RenderMCPConfig(p, agent.MCPConfig{Servers: map[string]agent.MCPServer{}}) -if err != nil { -fmt.Fprintln(os.Stderr, "sherlock: render mcp config:", err) -os.Exit(exitGeneric) -} + // Phase 1: empty servers map. Phase 2 fills this from services.d. + mcpPath, err := agent.RenderMCPConfig(p, agent.MCPConfig{Servers: map[string]agent.MCPServer{}}) + if err != nil { + fmt.Fprintln(os.Stderr, "sherlock: render mcp config:", err) + os.Exit(exitGeneric) + } -argv0, err := agent.ResolveExecutable(p) -if err != nil { -fmt.Fprintln(os.Stderr, "sherlock: resolve executable:", err) -os.Exit(exitGeneric) -} -argv := agent.BuildArgv(p, mcpPath, userArgs) -argv[0] = argv0 -sockPath, _ := xdg.SocketPath() -env := agent.BuildEnv(p, mcpPath, map[string]string{"SHERLOCK_SOCKET": sockPath}) + argv0, err := agent.ResolveExecutable(p) + if err != nil { + fmt.Fprintln(os.Stderr, "sherlock: resolve executable:", err) + os.Exit(exitGeneric) + } + argv := agent.BuildArgv(p, mcpPath, userArgs) + argv[0] = argv0 + sockPath, _ := xdg.SocketPath() + env := agent.BuildEnv(p, mcpPath, map[string]string{"SHERLOCK_SOCKET": sockPath}) -if err := (agent.SyscallExecer{}).Exec(argv0, argv, env); err != nil { -fmt.Fprintln(os.Stderr, "sherlock: exec:", err) -os.Exit(exitGeneric) -} + if err := (agent.SyscallExecer{}).Exec(argv0, argv, env); err != nil { + fmt.Fprintln(os.Stderr, "sherlock: exec:", err) + os.Exit(exitGeneric) + } } func profileExists(name string) bool { -profiles, err := loadProfiles() -if err != nil { -return false -} -_, ok := profiles[name] -return ok + profiles, err := loadProfiles() + if err != nil { + return false + } + _, ok := profiles[name] + return ok } func loadProfiles() (map[string]agent.Profile, error) { -dir, err := xdg.AgentsConfigDir() -if err != nil { -return nil, err -} -return agent.Load(agent.LoadOptions{UserDir: dir}) + dir, err := xdg.AgentsConfigDir() + if err != nil { + return nil, err + } + return agent.Load(agent.LoadOptions{UserDir: dir}) } func openBrowser(url string) error { -var bin string -var args []string -switch runtime.GOOS { -case "darwin": -bin = "open" -args = []string{url} -case "windows": -bin = "rundll32" -args = []string{"url.dll,FileProtocolHandler", url} -default: -bin = "xdg-open" -args = []string{url} -} -cmd := exec.Command(bin, args...) -if err := cmd.Start(); err != nil { -return err -} -go func() { _ = cmd.Wait() }() -return nil + var bin string + var args []string + switch runtime.GOOS { + case "darwin": + bin = "open" + args = []string{url} + case "windows": + bin = "rundll32" + args = []string{"url.dll,FileProtocolHandler", url} + default: + bin = "xdg-open" + args = []string{url} + } + cmd := exec.Command(bin, args...) + if err := cmd.Start(); err != nil { + return err + } + go func() { _ = cmd.Wait() }() + return nil } func humanizeUntil(t time.Time) string { -d := time.Until(t).Round(time.Second) -if d < 0 { -return "expired" + d := time.Until(t).Round(time.Second) + if d < 0 { + return "expired" + } + return "in " + d.String() } -return "in " + d.String() -} - -// Silence unused-import noise if a stub is wired in later (keeps the -// json/errors imports honest in case the CLI grows a custom parser). -var _ = json.Marshal -var _ = errors.New diff --git a/docs/conventions.md b/docs/conventions.md index acb42cb..265083f 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -55,7 +55,7 @@ No top-level `pkg/` until we have an external consumer. ## CI -- `.gitea/workflows/ci.yaml` runs on every push + PR. It runs `go vet`, `go test -race`, `go build`. No other gates. +- `.gitea/workflows/ci.yaml` runs on every push + PR. It runs `gofmt`, `go vet`, `errcheck`, `staticcheck`, `go test -race`, and `go build`. No other gates. - No deploy pipeline — sherlock is operator-installed via `go install`. Release artifacts (binaries to the gitea registry / releases) land in Phase 5 if at all. ## Security diff --git a/internal/authn/flow_test.go b/internal/authn/flow_test.go index ab0d7c9..2b1baf1 100644 --- a/internal/authn/flow_test.go +++ b/internal/authn/flow_test.go @@ -143,7 +143,7 @@ func TestFlow_EndToEnd(t *testing.T) { ClientID: "sherlock-client", Scopes: []string{"openid", "profile", "email"}, }, nil) - defer flow.Close() + defer func() { _ = flow.Close() }() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -187,7 +187,7 @@ func TestFlow_EndToEnd(t *testing.T) { func TestFlow_NotConfigured(t *testing.T) { flow := authn.NewFlow(authn.Config{}, nil) - defer flow.Close() + defer func() { _ = flow.Close() }() _, err := flow.StartFlow(context.Background()) if err == nil || !strings.Contains(err.Error(), "not configured") { t.Fatalf("expected not-configured error, got %v", err) diff --git a/internal/broker/methods.go b/internal/broker/methods.go index 75145cc..5ce4c74 100644 --- a/internal/broker/methods.go +++ b/internal/broker/methods.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "sync" "time" "gitea.alexandru.macocian.me/Charlie/sherlock/internal/authn" @@ -29,14 +28,6 @@ type Methods struct { // AgentNames feeds the status response so the CLI can show which // profiles are available without re-loading them. AgentNames func() []string - - // Clock is injected for tests. - Clock func() time.Time - - mu sync.Mutex - currentSub string - currentSt keyring.TokenSet - currentInit bool } // MethodTable returns the map suitable for NewService. @@ -52,13 +43,6 @@ func (m *Methods) MethodTable() map[string]MethodHandler { } } -func (m *Methods) now() time.Time { - if m.Clock != nil { - return m.Clock() - } - return time.Now() -} - // loadTokens reads the keyring once and caches the result in memory. // We re-read on every status to keep things simple — the keyring is // the source of truth. diff --git a/internal/broker/methods_test.go b/internal/broker/methods_test.go index 5694c35..631bd7a 100644 --- a/internal/broker/methods_test.go +++ b/internal/broker/methods_test.go @@ -7,16 +7,16 @@ import ( "time" "gitea.alexandru.macocian.me/Charlie/sherlock/internal/authn" - fakekeyring "gitea.alexandru.macocian.me/Charlie/sherlock/internal/keyring/fake" "gitea.alexandru.macocian.me/Charlie/sherlock/internal/keyring" + fakekeyring "gitea.alexandru.macocian.me/Charlie/sherlock/internal/keyring/fake" "gitea.alexandru.macocian.me/Charlie/sherlock/internal/rpc" ) func newMethods(t *testing.T) *Methods { t.Helper() return &Methods{ - Store: fakekeyring.New(), - Cfg: authn.Config{}, + Store: fakekeyring.New(), + Cfg: authn.Config{}, AgentNames: func() []string { return []string{"copilot"} }, } } diff --git a/internal/socket/socket.go b/internal/socket/socket.go index d40fdf4..ebe2ff4 100644 --- a/internal/socket/socket.go +++ b/internal/socket/socket.go @@ -117,7 +117,7 @@ func (s *Server) Close() error { func (s *Server) serveConn(ctx context.Context, conn net.Conn) { defer s.wg.Done() - defer conn.Close() + defer func() { _ = conn.Close() }() scanner := bufio.NewScanner(conn) scanner.Buffer(make([]byte, 64*1024), maxFrameBytes) diff --git a/internal/socket/socket_test.go b/internal/socket/socket_test.go index ed079bd..5281ea0 100644 --- a/internal/socket/socket_test.go +++ b/internal/socket/socket_test.go @@ -49,7 +49,7 @@ func TestRoundTrip_Success(t *testing.T) { if err != nil { t.Fatalf("Dial: %v", err) } - defer c.Close() + defer func() { _ = c.Close() }() var resp map[string]string if err := c.Call(context.Background(), "echo", nil, &resp); err != nil { @@ -71,7 +71,7 @@ func TestRoundTrip_Error(t *testing.T) { if err != nil { t.Fatalf("Dial: %v", err) } - defer c.Close() + defer func() { _ = c.Close() }() err = c.Call(context.Background(), "status", nil, nil) if err == nil { @@ -98,7 +98,7 @@ func TestRoundTrip_BadJSON(t *testing.T) { if err != nil { t.Fatalf("dialRaw: %v", err) } - defer conn.Close() + defer func() { _ = conn.Close() }() if _, err := conn.Write([]byte("not json\n")); err != nil { t.Fatalf("Write: %v", err) @@ -140,7 +140,7 @@ func TestConcurrentCalls(t *testing.T) { t.Errorf("Dial: %v", err) return } - defer c.Close() + defer func() { _ = c.Close() }() for j := 0; j < callsEach; j++ { var out map[string]int err := c.Call(context.Background(), "echo",