Ctrl K
ComunidadGuías…

CONTRIBUTING.md

Rama develop · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Contributing to incus-compose

Thank you for your interest in contributing! This document outlines the
conventions and practices we follow.

This project is destined for the **lxc** org. The org-wide contributing policy
([lxc/incus CONTRIBUTING](https://github.com/lxc/incus/blob/main/CONTRIBUTING.md))
applies in full, including:

- **License**: Apache 2.0, no copyright assignment. Contributions cannot include
  code licensed under the GPL, AGPL or LGPL.
- **DCO**: Every commit that lands carries a `Signed-off-by` line
  (`git commit -s`), and only a human may sign it. Work done in a maintainer's
  worktree is committed without any trailer; the maintainer signs it on the
  rebase.
- **AI tooling**: See the org policy. Contributors must fully own their work. AI
  tools cannot be credited. See also [AGENTS.md](AGENTS.md).

## Philosophy

**KISS** is about the solution: solve the problem in front of you, at the size
it actually has. Not the general case, not the one you expect next quarter. A
shallow package structure, direct code, working software over perfect
architecture.

**Boring** is about the reader: the next person should be able to guess how it
works before reading it. A plain loop over a clever pipeline, the standard
library over a dependency, the obvious name over the precise one, the pattern
already used three times in this repo over a better one used zero times. Code is
read far more often than it is written, and "boring" is what makes a diff cheap
to review at 23:00.

Neither is an excuse for a broken design. When they collide with correctness,
correctness wins - but say so, rather than quietly building the clever thing.

- No non-ASCII characters in code and docs

## Working with Go code

Follow the [Go proverbs](https://go-proverbs.github.io/).

## Architecture and design rules

Its core design principles are documented in
[docs/root/developer/index.md](docs/root/developer/index.md).

Read the documentation in your own checkout, not the published site: `docs/` is
a submodule, and a feature branch may carry a version of it that the site does
not have yet.

Before contributing, you **must** read and understand this document. It defines
non-negotiable boundaries, including:

- What incus-compose will and will not implement
- Where Compose semantics must remain untouched
- How mapping to Incus is structured
- Which layers are allowed to change behavior
- Use Incus terms and simple English

Contributions that violate these principles will be rejected, regardless of
feature completeness or test coverage.

## Project Structure

```
incus-compose/
  cmd/incus-compose/    # CLI entry point
  cmd/ic-healthd/       # Sidecar
  client/               # High-level Incus client wrapper
  iclient/              # Incus API client, a fork of the upstream one
  shared/               # Code both binaries use
  internal/             # Helpers no consumer may import
  project/              # Compose project loading and service translation
  examples/             # Example projects
  docs/                 # User-facing documentation (submodule)
  test/                 # Tests and fixtures
  just/                 # justfile modules
```

**Package Guidelines**:

- `cmd/incus-compose/` - CLI flag parsing, command handlers, wiring only
- `cmd/ic-healthd/` - Sidecar for health checking and instance restarts
- `client/` - High-level Incus wrapper, resource management, transactions
- `iclient/` - The Incus REST API, forked from `lxc/incus/client` because that
  one cannot be used from several goroutines. Everything reaches Incus through
  it; nothing else may import the upstream client
- `shared/` - Code both binaries use
- `internal/` - Helpers that must stay ours, such as `internal/testlib`
- `examples/` - Example projects ready to use with incus-compose
- `project/` - Compose-spec loading via compose-go, service-to-instance
  translation
- Root package - No code at root level (all in packages)

**Don't create**: deep nesting like `pkg/application/container/`, or abstraction
layers "for future flexibility".

## Build and Test Commands

Everything runs through `just`; use it instead of raw `go`. `just --list` shows
all of them, and
[docs/root/developer/testing.md](docs/root/developer/testing.md) documents each
in full, along with fixtures, coverage and the environment they need.

```bash
just build                  # Dev binary, and the sidecar image it is stamped with
just run <args>             # go run ./cmd/incus-compose against the .env remote
just incus <args>           # incus against the nested dev environment

just test-local [pattern]   # Unit only, no Incus needed
just test [pattern]         # Unit + integration. What CI runs
just test-e2e [pattern]     # Adds the slow full-CLI tests
just test-log [-p REGEX]    # Plain text of the newest run's log
just cover                  # Per-package and total coverage

just fix [path]             # golangci-lint --fix, scoped to a package
just pre-commit             # tidy, boundary, lint. Run before committing
```

Two that cost a round trip when missed:

- The package pattern comes **first**, `go test` flags after it.
  `just test-local -count=1` reads `-count=1` as the pattern and fails with
  `no Go files`.
- `just build` rebuilds the sidecar image. A change under `cmd/ic-healthd/`,
  `shared/` or `iclient/` reaches the running sidecar no other way.

## Code Style

### Naming

Prefer Go-style concise names over Java-style verbose names:

| Prefer     | Avoid                 |
| ---------- | --------------------- |
| `Copied()` | `IsCopiedToProject()` |
| `Status()` | `GetCurrentStatus()`  |
| `Valid()`  | `IsValidInstance()`   |
| `Backuper` | `BackupManager`       |
| `mu`       | `wellKnownMu`         |
| `err`      | `errorResult`         |

Go code reads better when names are short and context provides meaning.

Name a type for what it _is_, not for the role it plays: `Backuper`, not
`BackupManager`. `-Manager`, `-Handler`, `-Service` and `-Helper` suffixes carry
no information.

Don't qualify a variable with what it guards or holds when the scope already
says it - a mutex in a struct with one lock is `mu`.

### Helpers

Every helper is a jump, and every jump is a frame the reader has to hold while
reading the caller. Extract only what the reader never has to open: if
understanding the caller means going into the helper, inline it. A function with
one caller almost never passes that test.

### Comments

- Every exported function and type gets a doc comment: **one line**, ending with
  a period.
- A second line needs a damn good reason - a trap the caller cannot infer from
  the signature. Anything longer belongs in `docs/`, the issue, or the commit
  message.
- Delete comments that restate the code. A self-explanatory line gets no comment
  at all; that is the common case, not the exception.
- Never explain previous behaviour. That is what `git log` is for.
- Comments are not safeguards. An API is concurrency-safe because it is
  mutex-free or confined to one goroutine, never because a comment says so.

### Use of `any`

Avoid using `any` (`interface{}`). Prefer a small, explicit interface. Use
generics only if they clearly reduce duplication.

### Unused parameters

Use `_` for unused parameters rather than ignoring in the function body:

```go
// Preferred
func (t *logTerminal) Read(_ []byte) (int, error) {

// Avoid
func (t *logTerminal) Read(p []byte) (int, error) {
    _ = p
```

### CLI environment variables

- Use `INCUS_COMPOSE_*` prefix for configuration env vars
- Support common standards like [no-color.org](https://no-color.org/) where
  applicable

### Error Handling

**Return errors, don't panic**:

```go
if err != nil {
    return fmt.Errorf("creating container %s: %w", name, err)
}
```

**Aggregate errors for batch operations**:

```go
var errs error
for _, item := range items {
    err := operation(item)
    if err != nil {
        errs = errors.Join(errs, err)
    }
}
return errs
```

Note the two lines: we do not use the define-and-test one-line `if` form
anywhere, including in examples.

**Use sentinel errors**:

```go
var (
    ErrDisconnected       = errors.New("trying to use a disconnected client")
    ErrInstanceNotRunning = errors.New("the instance is not running")
)
```

**Check errors with errors.Is(), not string contains**:

```go
// Bad
if strings.Contains(err.Error(), "not found") { }

// Good
if errors.Is(err, ErrNotFound) { }
```

### Commit Messages

Use conventional commit format with package scope:

```
<type>(<package>): <description>
```

**Types**:

- `fix` - Bug fix
- `feat` - New feature or improvement
- `chore` - API or CLI API change

**Examples**:

```
fix(client): handle nil pointer in image cache
feat(cmd): add --timeout flag to up command
chore(client): rename Resource interface method
```

### Changelog

[CHANGELOG.md](CHANGELOG.md) follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Add the entry in the
same commit as the change, under the unreleased heading, in
`Added`/`Changed`/`Fixed`, ending with `(by @handle)`.

An entry is needed when a user can observe the difference:

- behaviour, CLI flags, or command output
- a bug they could have hit, even when the cause was internal

No entry for contributor docs (`AGENTS.md`, `CONTRIBUTING.md`, `docs/`), dev
tooling (`justfile`, `scripts/`), tests, refactors with no observable
difference, or a change to an exported symbol that no CLI user can see.

Write what changed for the reader, not what you did to the code - "concurrent
`up` runs no longer fail creating the same network", not "added a retry to
Ensure".

## Testing

Tiers, fixtures, coverage, and how to drive the CLI from a test are documented
in [docs/root/developer/testing.md](docs/root/developer/testing.md). Read it
before writing a test.

One policy rather than a technique, so it lives here: **do not add mocks.** A
fake `incus.InstanceServer` encodes a guess about what the daemon returns, and a
test that passes against the guess proves nothing. Anything needing Incus state
gets it from a real one. The existing ordering mock is the exception; any other
mock is a maintainer's call - ask first.

## Docker Compose Compatibility

Output should match `docker compose config` where possible.

**Intentional differences**:

- OS env vars not included by default (use `--os-env` for compatibility)
- `config --format=json` keeps `x-incus`/`x-incus-compose` (compose-go tags
  extensions `json:"-"`, so docker drops them); it therefore omits docker's
  explicit `command`/`entrypoint`/`ipam` nulls. `--format=yaml` matches exactly.

## Questions?

- Open an issue for bugs or feature requests
- Check existing documentation in `docs/`