Document mred to the docs standard (verified quickstart, reference tables)
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# mopac-redmine-go
|
||||
|
||||
A 100% Go client library and CLI (`mred`) for the Redmine REST API.
|
||||
Replaces the PMO's python glue scripts for every Redmine update: one
|
||||
static binary, zero third-party modules (stdlib only), machine-parseable
|
||||
JSON output, and an API key that never appears in flags, logs, or error
|
||||
strings.
|
||||
|
||||
Status: 2026-08-29 — v0 complete and green: issues (list / show /
|
||||
create / update with notes-as-journals), versions, categories,
|
||||
relations, name-to-id resolution over server enumerations, 0600
|
||||
env-file config, `-o json` on every command, exit codes 0/1/2 with
|
||||
one-line API-error stderr. Built and tested entirely against a fake
|
||||
Redmine (unit suite + end-to-end smoke); the live tracker
|
||||
(projects.knownelement.com) attaches with zero code change via
|
||||
`MRED_URL`/`MRED_KEY`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph host[Host]
|
||||
M[mred CLI\nbin/mred] --config 0600 env --> M
|
||||
S[smoke/smoke.sh]
|
||||
end
|
||||
subgraph docker[Digest-pinned Docker builder — ALL dev]
|
||||
B[go build / vet / test]
|
||||
F[smoke/fakeredmine\nstateful in-memory Redmine]
|
||||
end
|
||||
subgraph wire[Library: package redmine]
|
||||
C[Client\nX-Redmine-API-Key header only]
|
||||
end
|
||||
M --> C
|
||||
C --> F
|
||||
C -.-> R[(real Redmine\nprojects.knownelement.com)]
|
||||
S --> B
|
||||
S --> F
|
||||
```
|
||||
|
||||
All compilation, vetting, and testing runs inside the digest-pinned
|
||||
`golang:1.26-bookworm` builder (`./dev.sh`); the host never runs a Go
|
||||
toolchain. Tests and smoke talk only to the in-process/containerized
|
||||
fake; the real tracker is never contacted by the test suite.
|
||||
|
||||
## Quickstart (verified against the fake)
|
||||
|
||||
This is the exact flow the smoke run (`./dev.sh smoke`) executes:
|
||||
|
||||
```sh
|
||||
./dev.sh check # build + vet + test inside the Docker builder
|
||||
./dev.sh smoke # boots fake Redmine on 127.0.0.1:8601, drives bin/mred
|
||||
|
||||
# against a real tracker instead:
|
||||
export MRED_URL=https://projects.knownelement.com
|
||||
export MRED_KEY=<your API key> # or: --config ~/.config/mred/env (0600)
|
||||
|
||||
mred issue list -p MOPAC # open issues
|
||||
mred issue list -p MOPAC --status all --limit 100
|
||||
mred issue show 506 --with journals
|
||||
mred issue create -p MOPAC -s "Subject" --tracker feature \
|
||||
--priority immediate --category Secrets --version Beta \
|
||||
--due 2026-08-31 --est 8 --desc - <<'EOF'
|
||||
## Scope
|
||||
- body
|
||||
EOF
|
||||
mred issue update 506 --status "In Progress" --note "REPORT delivered"
|
||||
mred relation create 490 492 --type blocks
|
||||
mred version create -p MOPAC -n "Beta" --due 2026-08-31 --status open
|
||||
mred category create -p MOPAC -n "Secrets"
|
||||
```
|
||||
|
||||
Add `-o json` to any command for machine output. Names (`--tracker
|
||||
feature`, `--priority immediate`, `--status done`, `--category`,
|
||||
`--version`) resolve case-insensitively against the server's own
|
||||
enumerations.
|
||||
|
||||
## Command reference
|
||||
|
||||
| Command | Effect | Key flags |
|
||||
|---|---|---|
|
||||
| `issue list -p PROJ` | list issues (open by default) | `--status open\|all\|closed\|NAME`, `--version NAME`, `--limit N`, `-o json` |
|
||||
| `issue show ID` | one issue with description | `--with journals` |
|
||||
| `issue create -p PROJ -s SUBJECT` | create; returns the new issue | `--desc FILE\|-`, `--tracker`, `--priority`, `--category`, `--version`, `--due YYYY-MM-DD`, `--parent ID`, `--est HOURS`, `--note TEXT` |
|
||||
| `issue update ID` | partial update (only set fields are sent) | `--status`, `--priority`, `--category`, `--version`, `--due`, `--done-ratio 0-100`, `--desc FILE\|-`, `--note TEXT` (journal) |
|
||||
| `version list -p PROJ` | roadmap milestones | `-o json` |
|
||||
| `version create -p PROJ -n NAME` | create milestone (sharing=descendants) | `--due`, `--status open\|closed` |
|
||||
| `category list -p PROJ` | issue categories | `-o json` |
|
||||
| `category create -p PROJ -n NAME` | create category | |
|
||||
| `relation create FROM TO` | relate two issues | `--type blocks\|relates` |
|
||||
| `help` | usage | |
|
||||
|
||||
Flags may appear before or after positionals (`mred issue update 506
|
||||
--note X` and `mred relation create 490 492 --type blocks` both parse).
|
||||
|
||||
## Configuration
|
||||
|
||||
| Source | Keys | Discipline |
|
||||
|---|---|---|
|
||||
| environment | `MRED_URL`, `MRED_KEY` | env wins over file |
|
||||
| `--config PATH` (also `$MRED_CONFIG`, default `~/.config/mred/env`) | `MRED_URL=...`, `MRED_KEY=...` | file must be 0600 or stricter; refused before any read; parsed in pure Go (no sourcing/expansion) |
|
||||
|
||||
The API key travels only in the `X-Redmine-API-Key` header. Response
|
||||
bodies are never surfaced in error strings — the fake Redmine
|
||||
deliberately echoes the presented key in error bodies, and the test
|
||||
suite proves nothing leaks (see `TestKeyNeverLeaks` and the smoke
|
||||
redaction pass).
|
||||
|
||||
## Exit codes and stderr
|
||||
|
||||
| Code | Meaning | stderr shape |
|
||||
|---|---|---|
|
||||
| 0 | ok | — |
|
||||
| 1 | usage / config error | one line, names the problem (never values) |
|
||||
| 2 | API error | one line: `mred: redmine: <sentinel>: http NNN` — parseable |
|
||||
|
||||
Sentinels (library): `redmine.ErrAuth`, `ErrNotFound`, `ErrValidation`,
|
||||
`ErrServer`, `ErrUnreachable`, `ErrMalformedResponse` — all comparable
|
||||
with `errors.Is`.
|
||||
|
||||
## Library surface (what the harness calls)
|
||||
|
||||
```go
|
||||
import "git.knownelement.com/ukrrs/mopac-redmine-go/redmine"
|
||||
|
||||
c := redmine.New(redmine.Config{BaseURL: url, APIKey: key})
|
||||
|
||||
issues, total, _ := c.ListIssues(ctx, redmine.IssueFilter{Project: "MOPAC"})
|
||||
issue, _ := c.GetIssue(ctx, 506, true) // + journals
|
||||
created, _ := c.CreateIssue(ctx, redmine.IssueParams{Project: "MOPAC", Subject: "…"})
|
||||
_ = c.UpdateIssue(ctx, 506, redmine.IssueParams{StatusID: 3, Notes: "REPORT delivered"})
|
||||
versions, _ := c.ListVersions(ctx, "MOPAC")
|
||||
cats, _ := c.ListCategories(ctx, "MOPAC")
|
||||
rel, _ := c.CreateRelation(ctx, 490, 492, "blocks")
|
||||
|
||||
// name resolution shared with the CLI:
|
||||
id, _ := c.StatusIDByName(ctx, "done") // 3
|
||||
id, _ = c.VersionIDByName(ctx, "MOPAC", "Beta") // milestone id
|
||||
id, _ = c.CategoryIDByName(ctx, "MOPAC", "Secrets")
|
||||
```
|
||||
|
||||
`UpdateIssue` sends only the fields you set (partial update — untouched
|
||||
attributes are never clobbered). All methods take a `context.Context`.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
./dev.sh build|vet|test|check|smoke|shell # everything runs in the digest-pinned builder
|
||||
make build|vet|test|check|smoke # same, Makefile front door
|
||||
```
|
||||
|
||||
Test suite: table-driven round-trips against the stateful fake
|
||||
(`internal/fakeredmine`): create/update/note/relations/versions,
|
||||
list filters, name resolution, error mapping (401/403/404/422/5xx →
|
||||
sentinels), unreachable/malformed handling, and key-redaction.
|
||||
|
||||
## Status
|
||||
|
||||
| Area | State |
|
||||
|---|---|
|
||||
| Library (issues, versions, categories, relations, enums) | done, green |
|
||||
| CLI surface (task spec in Redmine #506) | done, green |
|
||||
| Fake-Redmine test suite | done, green (unit + smoke) |
|
||||
| Key redaction | done, tested (fake echoes the key; nothing leaks) |
|
||||
| Dev harness (Docker-only builds) | done (`dev.sh`/`Makefile`) |
|
||||
| Live-tracker verification | pending first PMO switch (credentials via `~/.creds/redmine.env`) |
|
||||
| Uploads / attachments | not in scope for v0 |
|
||||
|
||||
License: AGPLv3 (see `LICENSE`). Repository:
|
||||
https://git.knownelement.com/ukrrs/mopac-redmine-go
|
||||
@@ -0,0 +1,5 @@
|
||||
# mred connection env file (copy to ~/.config/mred/env and chmod 600)
|
||||
# Only MRED_URL and MRED_KEY are used; both are required. The file must
|
||||
# be 0600 or stricter — mred refuses looser files before reading them.
|
||||
MRED_URL=https://projects.knownelement.com
|
||||
MRED_KEY=your-redmine-api-key
|
||||
Reference in New Issue
Block a user