Skip to content

Latest commit

 

History

History
194 lines (123 loc) · 11.3 KB

File metadata and controls

194 lines (123 loc) · 11.3 KB

Contributing to dutctl

👍🎉 First off, thanks for taking the time to contribute! 🎉👍

The following is a set of guidelines for contributing to the dutctl project, which are maintained by Blindspot Software GmbH on GitHub. These are mostly guidelines, not rules. Use your best judgment.

If you discover a security issue, please bring it to our attention right away! Please refer to our Security Policy for information about reporting vulnerabilities.

Read our Code of Conduct to keep contributions approachable and respectable

Use the table of contents icon on the top right corner of this document to get to a specific section of this guide quickly.

The dutctl project follows a governance model that balances the leadership of Blindspot Software with community input. For details about how decisions are made and how you can participate, please read our Governance document.

How Can I Contribute?

Reporting Bugs

  • Ensure the bug was not already reported by searching on GitHub under Issues.
  • If you're unable to find an open issue addressing the problem, open a new one. Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
  • Use the bug report template.

Suggesting Features

  • Open a feature request issue on the issue tracker.
  • Use the feature request template.

Your First Code Contribution

Unsure where to begin contributing to dutctl? You can start by looking through these issues:

Pull Requests

  1. Fork and Pull Request: All changes are made through pull requests.
  2. Issue First: For significant changes, opening an issue for discussion before implementation is recommended.
  3. Test: If you've added code that should be tested, add tests.
  4. Documentation: New features should include appropriate documentation.
  5. Consider Draft PRs: This way you can ensure the CI passes, before asking for review.
  6. 🚀 Issue the pull request!

Development Workflow

Setting Up the Development Environment

  1. Install Go: https://go.dev/learn/ , see go.mod file for the minimal required version
  2. Clone the repository: git clone https://github.com/BlindspotSoftware/dutctl.git
  3. Install dependencies: go mod download
  4. Set up (optional) development tools:
    • Install golangci-lint (see Code Quality section below)
    • Set up commit hooks for conventional commits (see Conventional Commits section below)

Developer Certificate of Origin

The Developer Certificate of Origin (DCO) is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the .

By adding a Signed-off-by line to commit messages, you adhere to these requirements:

This is my commit message

Signed-off-by: Random J Developer <random@developer.example.org>

Tip

Use the '-s' flag on git commits to append this automatically.

Conventional Commits

This project uses Conventional Commits for its commit message format.

Commit messages should follow this pattern:

<type>(<optional scope>): <description>

[optional body]

[optional footer(s)]

Where type is one of the following:

  • build: Tooling, etc.
  • chore: Housekeeping, dependency management, go.mod etc.
  • ci: Continuous integration, workflows, etc.
  • docs: Readme, doc comments
  • feat: Source code changes introducing new functionality
  • fix: Bug fixes, no new functionality
  • refactor: Source code changes without changing behavior
  • revert: Revert a commit
  • test: Add tests, increase coverage, which were not committed initially with a fix or feat commit

Local linting minimizes overhead before creating a PR

If you want to enable commitlint locally, check out https://commitlint.js.org/guides/local-setup.html

Tip

You can skip the husky part and just use GitHub hooks out of the box:

Rename .git/hooks/commit-msg.sample into .git/hooks/commit-msg and put in

#!/bin/sh
npx commitlint --edit

This change will not be propagated to the remote repo.

You can bypass commitlint locally like so: git commit --no-verify -m"commitlint won't like"

Testing

  • Write tests for new features and bug fixes
  • Run tests locally before submitting PRs: go test ./...
  • Aim for reasonable test coverage of new code

Style Guides

Go Code Style Guide

  • Follow the standard Go Code Review Comments guidelines (The exception proves the rule ...)
  • Follow the Effective Go principles
  • Document all exported symbols with proper Go doc comments

To automatically check for most of these styles and practices the CI runs golangci-lint to run a collection of linters. The rules and settings will be adapted as the project grows. Contributions are welcome here, too.

Run golangci-lint locally

For a faster development cycle, you can integrate golangci-lint into your local setup. The current version and configuration is pinned in .golangci.yml.

We recommend setting up your editor to run golangci-lint automatically. Most popular Go IDEs support this:

  • VS Code: Use the Go extension which supports golangci-lint
  • GoLand: Install the Golangci-lint plugin
  • Vim/Neovim: Configure with ALE or similar linting engines

Logging

The dutctl client keeps diagnostic logging separate from command output:

  • stdout carries results and agent/module output (the output.Formatter). Never log to stdout.
  • stderr carries client diagnostics via the standard log/slog package.

Use only two levels:

  • slog.Debug — internal trace; hidden unless the user passes --log debug.
  • slog.Warn — non-fatal anomalies. By default (--log warn) warnings are collected and printed as a short summary when the command finishes, so they never interrupt streaming output.

Other slog entry points (slog.Info, slog.Error, slog.Log, the *Context variants) are rejected by forbidigo. The handler still maps any level by severity (>= Warn → warn, else debug), but write Debug/Warn in code.

Errors that should stop the command are returned, not logged — they bubble up to a single exit point and are rendered through the formatter (format-aware, on stderr). There is intentionally no error log level.

The handler and the --log flag (debug|warn|none, default warn) live in cmds/dutctl/clilog.go.

Agent and server logging

dutagent (and dutserver) are service daemons, so they log differently from the client: structured records via internal/log (built on log/slog), to stderr, at the full set of levels. The base logger is installed in start() (slog.SetDefault); the -log flag sets the level (debug|info|warn|error, default debug) and -log-json switches the text handler for a JSON one. Human/TTY output is 2006/01/02 15:04:05 LEVEL [scope] message key=value (color only on a terminal); JSON emits scope as an attribute. Never log to stdout.

Obtain the logger from the context. Code retrieves it with log.FromContext(ctx). At a component boundary, the caller sets the scope and any shared attributes before handing control on, so each component logs only its own concern:

  • ctx = log.WithScope(ctx, "session") — set the component scope (a flat label; a new scope replaces the old one).
  • ctx = log.With(ctx, "device", dev) — add structured attributes that descend with the context.

Module Init/Deinit and Run all receive a context carrying the module-scoped logger — a good place to log applied defaults from missing config, and the external hosts/tools/devices the module talks to. Where there genuinely is no context — process bootstrap and lifecycle — log through the slog package directly (slog.Info/slog.Error/…); where even that is unavailable (the Session methods, Locker, chanio), use a logger frozen into the struct at construction (see session.log, Locker). Scopes in dutagent: agent (bootstrap/lifecycle, the default), rpc, session, locker, module. Scopes in dutserver: server (bootstrap/lifecycle, the default), rpc, relay (with directional relay downstream/relay upstream for the two forwarders of the relayed Run stream), and registry (the device→agent map).

Module logging is for the admin/operator (the person who wrote the config and wired the DUTs), not a re-narration of what the module already prints to the client. Worth logging: external interactions with their effective parameters (tool + command, host:port, device + settings), and defaults a module applies for missing config. Init/Deinit should not log lifecycle markers — the framework already logs those.

Three logging regions, enforced by forbidigo (see the "slog logging buckets" block in .golangci.yml) — you don't have to decide the idiom, the linter guides you:

  • Client (cmds/dutctl and its output rendering): slog.Debug/slog.Warn only — its two-channel output model. slog.Info/Error/… are rejected.
  • Agent/server setup (process bootstrap, module Init/Deinit lifecycle summaries, the frozen-logger objects): no request context, so log through the slog package directly (slog.Info, slog.Error, …).
  • Agent/server request path (RPC handlers, FSM, broker/workers, relay, modules): obtain the logger from the request context — log.FromContext(ctx).X — never bare slog.

The default (any file not explicitly listed as a client or setup file in the exclusions) is the strictest request-path rule, so new code is nudged toward log.FromContext(ctx) automatically.

Levels:

  • Error — the agent failed at something it was asked to do; an operator likely must act.
  • Warn — a handled or tolerated anomaly (e.g. a malformed client message, an admin force-unlock).
  • Info — normal operational milestones; a few lines per request (request received/finished, running a module, listening).
  • Debug — internal tracing (per-message traffic, worker start/stop, module init/deinit).

An error returned up the call stack is logged once where it becomes terminal (the RPC handler), not at every hop. Module bodies should not log lifecycle markers ("Run called"); the framework logs the transition and sets the scope.

Documentation Style Guide

  • Use Markdown for documentation
  • Keep README.md and other documentation up to date with code changes
  • Use clear, concise language
  • Include examples where appropriate

Thank you for contributing to dutctl! Your efforts help make this project better for everyone. ❤️