Skip to content

Commit 84d9c0e

Browse files
committed
Add sync command usage doc
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
1 parent c744f77 commit 84d9c0e

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

docs/sync.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Flux Mirror Sync Command
2+
3+
The `flux-mirror sync` command mirrors Helm charts and OCI artifacts between registries based on a
4+
declarative YAML config. The command is idempotent, re-running against the same config produces
5+
the same destination state, copying only what's missing or drifted.
6+
7+
See the [config specification](./config.md) for the YAML schema.
8+
9+
## Synopsis
10+
11+
```
12+
flux-mirror sync [-c|--config PATH] [flags]
13+
```
14+
15+
## Configuration source
16+
17+
The config file path is resolved in the following order:
18+
19+
1. `--config` / `-c PATH` flag.
20+
2. `FLUX_MIRROR_CONFIG` environment variable.
21+
22+
```bash
23+
flux-mirror sync -c examples/podinfo.yaml
24+
FLUX_MIRROR_CONFIG=examples/podinfo.yaml flux-mirror sync
25+
```
26+
27+
## Authentication
28+
29+
Auth is read from the ambient Docker config:
30+
31+
- `~/.docker/config.json` (or the `DOCKER_CONFIG` env var if set).
32+
- Any configured credential helpers (e.g., `docker-credential-osxkeychain`, `docker-credential-ecr-login`, `docker-credential-gcloud`).
33+
34+
Log in once with `docker login`, `oras login`, etc. and `flux-mirror` picks up the credentials.
35+
36+
## Flags
37+
38+
| Flag | Default | Description |
39+
|---------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------|
40+
| `-c, --config PATH` || Path to the YAML config file. Falls back to `FLUX_MIRROR_CONFIG`. |
41+
| `-o, --output text\|yaml\|json` | `text` | Output format. `text` is human-friendly; `yaml` and `json` print the structured `Result` to stdout. |
42+
| `--concurrency N` | `4` | Maximum number of copy operations to run in parallel within a single config entry. Entries themselves are processed sequentially. |
43+
| `--retries N` | `3` | Maximum number of retry attempts per job, bounded by `--timeout`. |
44+
| `--overwrite` | `false` | Force `overwrite: true` on every entry, regardless of per-entry config. See [Overwrite Behavior](./config.md#overwrite-behavior). |
45+
| `--dry-run` | `false` | Run the plan and comparison pipeline without performing any writes. Reported as `would-copy` / `would-overwrite` in the output. |
46+
| `--verbose` | `false` | Emit a structured log line per operation (entry started, mirroring tag, tag done, entry summary, sync complete) on stderr. Suppresses the spinner. |
47+
| `--no-progress` | `false` | Disable the live progress spinner. Per-job lines and the Summary still print. |
48+
| `--insecure` | `false` | Allow plaintext HTTP and skip TLS verification. **Test/dev only.** |
49+
| `--timeout DURATION` | `5m` | Per-job total budget covering all retry attempts. |
50+
51+
## Output
52+
53+
### Text mode (default)
54+
55+
Pretty-print mode:
56+
57+
```
58+
✓ ghcr.io/stefanprodan/charts/podinfo:6.10.2 (skipped)
59+
→ localhost:5050/charts/podinfo:6.10.2
60+
✓ ghcr.io/stefanprodan/charts/podinfo:6.11.0 (copied)
61+
→ localhost:5050/charts/podinfo:6.11.0
62+
✗ ghcr.io/stefanprodan/charts/podinfo2 — plan failed: NAME_UNKNOWN
63+
Summary: 1 copied, 1 skipped, 1 failed in 4.2s.
64+
```
65+
66+
### Verbose mode
67+
68+
`--verbose` replaces the pretty output with a full diagnostic log stream on stderr.
69+
70+
Every layer push, blob check (existing vs pushed), manifest digest, fallback-tag update for referrers,
71+
and registry-side warning is logged. Reach for this when diagnosing TLS, auth, missing-blob, or push-rejection issues.
72+
73+
```
74+
2026/05/13 09:00:00 sync started entries=3 concurrency=4 retries=3 timeout=5m0s
75+
2026/05/13 09:00:00 mirroring tag src=ghcr.io/foo/bar:1.0 dst=localhost:5050/bar:1.0
76+
2026/05/13 09:00:00 Copying from ghcr.io/foo/bar:1.0 to localhost:5050/bar:1.0
77+
2026/05/13 09:00:00 pushed blob: sha256:fdf53ef8e04176eedbd42713efb2d002f1741c310627b38f444c6f6d92a598f7
78+
2026/05/13 09:00:00 existing blob: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
79+
2026/05/13 09:00:00 localhost:5050/bar:1.0: digest: sha256:455d6a04fe43df4a93b40e1f92d65b2a9befaa0348e5a461295d961161ebf475 size: 829
80+
2026/05/13 09:00:00 updating fallback tag sha256-455d6a04... with new referrer
81+
2026/05/13 09:00:01 tag done src=ghcr.io/foo/bar:1.0 outcome=copied elapsed=812ms
82+
83+
2026/05/13 09:00:04 sync complete entries=3 copied=1 overwritten=0 skipped=1 drifted=0 failed=1 duration=4.213s
84+
```
85+
86+
### Structured output
87+
88+
`-o yaml` and `-o json` print the full `Result` to stdout — entries with their per-outcome tag lists and any failures
89+
Suitable for piping into another tool.
90+
91+
```bash
92+
flux-mirror sync -c config.yaml -o json | jq '.entries[].outcomes.copied'
93+
```
94+
95+
## Outcomes
96+
97+
Each tag job lands in exactly one of these buckets:
98+
99+
| Outcome | Meaning |
100+
|-------------------|-----------------------------------------------------------------------------------------------|
101+
| `copied` | Destination did not have the tag; mirrored from source. |
102+
| `overwritten` | Destination had a different digest; replaced (only with `overwrite: true`). |
103+
| `skipped` | Destination already had the same digest; nothing to do. |
104+
| `drifted` | Destination has a different digest, `overwrite: false` — left alone, surfaced in the summary. |
105+
| `would-copy` | Dry-run forecast: would have been copied. |
106+
| `would-overwrite` | Dry-run forecast: would have been overwritten. |
107+
108+
Plan-time failures (e.g., source registry rejected a `ListTags`) are reported as a
109+
single `✗ <entry> — plan failed: <err>` line and counted toward `failed`.
110+
111+
## Exit codes
112+
113+
| Code | Meaning |
114+
|------|-----------------------------------------------------------------------------------------------------------------------|
115+
| `0` | Clean run — every tag was copied or skipped as expected, no drift, no failures. |
116+
| `1` | At least one tag job failed (network error, push rejected, retries exhausted, plan failure). |
117+
| `2` | No failures, but at least one tag drifted with `overwrite: false`. The destination is out of date relative to source. |
118+
119+
Failures take precedence over drift. `--dry-run` does not bump the exit code for `would-copy` / `would-overwrite`,
120+
but drift detection still produces `2`.
121+
122+
## Examples
123+
124+
### Mirror latest releases of an OCI artifact
125+
126+
```yaml
127+
# config.yaml
128+
apiVersion: mirror.fluxcd.io/v1alpha1
129+
kind: Config
130+
artifacts:
131+
- source: ghcr.io/stefanprodan/podinfo
132+
destination: localhost:5050/podinfo
133+
selector:
134+
semver: ">=6.9.0"
135+
limit: 3
136+
includeReferrers: true
137+
```
138+
139+
```bash
140+
flux-mirror sync -c config.yaml
141+
```
142+
143+
### Preview without writing
144+
145+
```bash
146+
flux-mirror sync -c config.yaml --dry-run -o yaml
147+
```
148+
149+
### Force-resync drifted tags
150+
151+
```bash
152+
flux-mirror sync -c config.yaml --overwrite
153+
```
154+
155+
### CI-friendly invocation
156+
157+
```bash
158+
flux-mirror sync -c config.yaml --no-progress
159+
```

0 commit comments

Comments
 (0)