bootc-dev / bootc-dev/bootc

Resolving conflicts for etc merge

Aperta
#2,285 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
Rust
Stelle
2.3k
Fork
230
Merge medio
3g 12h
PR unite (30g)
38

Descrizione

The following doc is mostly AI generated. I've made a few tweaks

# `/etc` Three-Way Merge Conflict Resolution

## Problem

When upgrading a bootc deployment, `/etc` undergoes a three-way merge between:

1. **Pristine etc** -- The unmodified `/etc` from the currently booted image.
2. **Current etc** -- The live `/etc` with user modifications.
3. **New etc** -- The `/etc` from the new deployment image.

User modifications (added files, changed permissions, edited content) are preserved
across upgrades. However, a **type conflict** can occur when a path changes between
file and directory across deployments:

- A user-modified **file** now defaults to a **directory** in the new image.
- A user-modified **directory** now defaults to a **file** in the new image.

These conflicts are **unmergeable**, i.e. the merge cannot reconcile both the user's
modification and the new image's structural change at the same path.

## MergeStrategy

`MergeStrategy` is an enum that controls how unmergeable paths are handled during
the three-way `/etc` merge. It is defined in the `etc-merge` crate and derives
`clap::ValueEnum`, `Serialize`, and `Deserialize` for use as both a CLI argument
and a configuration value.

### Variants

| Strategy | Behavior |
|-----------|----------|
| `fail` | **(Default)** Abort the merge. The deployment finalization fails and the system continues booting the current deployment. |
| `skip` | Keep the new image's default for conflicting paths. The user's modification at that path is dropped. The merge succeeds. |
| `replace` | Keep the user's version. The conflicting entry in the new `/etc` is removed and replaced with the user's file or directory (including children). The merge succeeds. |

### Conflict detection

> [!NOTE]
> We now detect any conflicts during upgrade/switch and throw an error if we find conflicts
and `merge_strategy` is `Fail`

During `compute_diff`, the `check_if_mergable` function compares each added or
modified path against the new `/etc` tree. If a file-vs-directory type mismatch is
found, the path is recorded in `diff.unmergable_paths` with a human-readable reason.

The `merge()` function checks this list before proceeding:
- With `Fail`, it bails immediately if any unmergeable paths exist.
- With `Skip` or `Replace`, it proceeds and handles each conflict according to the
strategy.

## Configuration

The merge strategy is determined through the following sources, in order of precedence:

### 1. CLI option (`--merge-strategy`)

Available on `bootc upgrade` and `bootc switch`:

```
bootc upgrade --merge-strategy skip
bootc switch --merge-strategy replace
```

The `--merge-strategy` flag accepts `fail`, `skip`, or `replace`. The default is `fail`.

This is implemented via the `EtcMergeStrategyOpts` struct which is flattened into
`UpgradeOpts` and `SwitchOpts`.

### 2. Image configuration file (`/usr/lib/bootc/finalize.toml`)

Image authors can ship a default merge strategy inside the container image:

```toml
merge_strategy = "skip"
```

This file is read from the staged deployment's mounted EROFS image at
`usr/lib/bootc/finalize.toml`. It is consulted only when conflicts are detected
and the CLI-provided strategy is `fail` (the default). If the config file specifies
a non-`fail` strategy, it overrides the default.

This allows image authors to opt their users into a safe default without requiring
CLI flags on every upgrade.

## Finalization

The resolved merge strategy is persisted in the staged deployment JSON file at
`/run/composefs/staged-deployment`. This file is written during `write_composefs_state`
and read during `composefs_backend_finalize`.

The resolution flow is:

1. CLI provides `--merge-strategy` (default: `fail`).
2. During `write_composefs_state`, if conflicts exist and strategy is `fail`, check
the image's `finalize.toml` for a configured strategy.
3. If a non-`fail` strategy is found in the config, use it. Otherwise, fail with a
hint about `--merge-strategy` and `finalize.toml`.
4. The resolved strategy is serialized into the staged deployment file.
5. On reboot, `composefs_backend_finalize` reads the staged deployment file and
passes the strategy to `merge()`.

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Start in the etc-merge crate with compute_diff, check_if_mergable, and merge to trace how unmergeable paths and MergeStrategy are handled. Then follow EtcMergeStrategyOpts through UpgradeOpts and SwitchOpts, and inspect write_composefs_state and composefs_backend_finalize for finalize.toml and staged-deployment handling. Done means fail, skip, and replace behave as documented for upgrade and switch conflicts.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
rust
Ambito
operating-systems
Tipo di issue
Funzionalità
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Tranquilla
Chiarezza
Abbastanza chiara
Idoneità per principianti
55/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.