Repository navigation
✨ Feature: add gfs clone <source> <target>, and move the lazy-clone commands under gfs lazy - #201
Draft
medazizktata25 wants to merge 5 commits into
Draft
medazizktata25 wants to merge 5 commits into
medazizktata25 wants to merge 5 commits into
Conversation
clone --from, fetch, pull, remote and freeze only work on a lazy clone but sat at the top level as if they applied to every database, and they held the word `clone` for the special case. They now live under `gfs lazy clone|status|pull|source|freeze`; `lazy clone` takes the URL as an argument and --freeze instead of --snapshot. The old spellings stay for one release, hidden from help, with the same exit codes and stdout and a rename warning on stderr only, so --json consumers see no change.
run() is now create_repository() then start_database(), unchanged in behaviour, so a caller can fill the workspace before the first start.
…pshot A new, independent database holding the data of one snapshot of another: --snapshot <id> or --branch <name>, the latest by default. The snapshot is copied into the new workspace before its database first starts, with the same restore checkout uses, and the database starts on the source's provider, version, compute params and credentials. The copy begins with one commit naming its source; it carries none of the source's history. Everything that can be refused (not a repository, no database, no snapshots, unknown id, snapshot data missing, existing target) is checked before the target is created, so a refusal leaves nothing. Cloning a lazy clone gives a lazy copy, and the command says so. `gfs clone --from <url>` still reaches the lazy clone for one release.
On Kubernetes a snapshot is a VolumeSnapshot, not a directory. The new instance's data volume is now created from the source commit's VolumeSnapshot before the instance exists, and the source's credentials Secret is copied to it, so no blank database ever boots and nothing is torn down and swapped underneath it. Compute::provision_seeded (default: refuse) carries this; init gains start_database_seeded beside start_database, whose signature is unchanged. gfs clone picks the volume path when the source runs on Kubernetes, refuses to cross runtimes, and records the copy's data volume as init does. volumesnapshot_name_for_hash is now the one place that names a snapshot's VolumeSnapshot.
provision_seeded creates the instance's data volume from the source
snapshot, then adopts the credentials Secret and creates the
StatefulSet. If either later step failed, the error returned with the
volume in place and nothing recording the instance, so no teardown
could find it: the PVC stayed until removed by hand.
Seen on k3s with a namespace quota that refuses the StatefulSet
("exceeded quota"): the clone failed and {instance}-data was left
behind. A failure after the volume exists now tears down what this
call made (StatefulSet, Service, data PVC, adopted Secret), keeping
the snapshot, which belongs to the source. Same run with this change:
the clone fails and no PVC is left; a normal clone is unchanged.
medazizktata25
requested a deployment
to
pr-validation
October 27, 2026 14:46 — with
GitHub Actions
Waiting
medazizktata25
requested a deployment
to
pr-validation
October 27, 2026 14:46 — with
GitHub Actions
Waiting
medazizktata25
requested a deployment
to
pr-validation
October 27, 2026 14:46 — with
GitHub Actions
Waiting
This branch is waiting to be deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
None filed.
What
Standalone gfs cannot make a new, independent database from a chosen snapshot of an existing one. It only has the storage primitive (
gfs storage clone, which takes paths). Meanwhile the plain wordcloneis taken by the special case:gfs clone --from <url>is the lazy copy-on-read clone of an external database.This PR adds
gfs clone <source> <target> [--snapshot <id> | --branch <name>]. It is a full, independent copy of the database at<source>, taken from its latest snapshot by default, a given snapshot, or a branch tip. The lazy-clone commands move under one group:gfs clone --from <url> [--snapshot]gfs lazy clone <url> [--freeze]gfs fetchgfs lazy statusgfs pullgfs lazy pullgfs remotegfs lazy sourcegfs freezegfs lazy freezeThe old spellings keep working for one release. They are hidden from help and print a rename warning on stderr only, so exit codes and
--jsonstdout are unchanged.How
Five commits, meant to be reviewed one at a time:
feat(cli): group the lazy-clone commands under gfs lazy. Moves the lazy-only commands undergfs lazyand keeps the old spellings as hidden aliases.refactor(init): split creating a repository from starting its database.run()becomescreate_repository()thenstart_database(), with no behaviour change, so a caller can fill the workspace before the first start. The record-before-start ordering onmainstays insidestart_database.feat(clone): gfs clone <source> <target> copies a database from a snapshot. Adds a newCloneFromSnapshotUseCase:feat(clone): gfs clone on Kubernetes seeds the volume before first start. On Kubernetes a snapshot is a VolumeSnapshot:Compute::provision_seeded; its default refuses.initgainsstart_database_seededbeside an unchangedstart_database.gfs clonerefuses to cross runtimes.fix(clone): a seeded provision that fails removes the volume it made. If adopting the Secret or creating the StatefulSet fails after the PVC exists, nothing records the instance yet, so no later teardown could find it. It now tears down what this call made, keeping the source's snapshot.Review Guide
crates/applications/cli/src/lib.rs: theLazygroup, the hidden aliases,warn_renamed.crates/domain/src/usecases/repository/init_repo_usecase.rs: the split. Diff it againstmainto see the body is moved, not changed.crates/domain/src/usecases/repository/clone_from_snapshot_usecase.rsandcli/src/commands/cmd_clone_from_snapshot.rs: the new command.crates/domain/src/adapters/gfs_repository.rs:restore_workspace_from_snapshot, now shared by checkout and clone, plusseed_head_workspace.crates/adapters/compute-kubernetes/src/lib.rs,provision_seeded.Testing
On Rust 1.93.1 (the pinned toolchain), macOS with Colima for Docker, using
gfs-postgres:16/:17images built from this tree.cargo fmt --check: clean.cargo clippy --locked --all-targets --all-features -- -D warnings: clean, run on Linux (rust:1.93.1-bookworm), as CI does, after the last commit.Full
cargo test --lockedat commit 4: 884 passed, 2 failed, 3 ignored, across 60 test binaries, with no e2e test skipped for a missing image. Both failures fail identically onmain(d5a0e83):clone_range_and_temporal_hydration_elide: the test predates the asynchronous temporal copy; the fix is on its own branch and will come as a separate PR;postgres_checkout_lifecycle: hint bits read as uncommitted work; the fix is on its own branch and will come as a separate PR.After commit 5:
gfs-domain(433) andgfs-compute-kubernetes(68) pass. The full suite was not re-run, since commit 5 only touchesprovision_seeded.New e2e
e2e_clone_from_snapshot(3 tests: SQLite, Postgres with custom credentials, and every refusal leaving no target):lazy_commands_have_one_home_and_the_old_names_still_work: fails when an old spelling is un-hidden, when its warning is dropped, or when the warning goes to stdout.Init, unit tests:
a_failed_start_leaves_a_seeded_instance_recorded;run()skippingstart_database, 7 of 13 existing tests fail.Live on k3s (Ubuntu 24.04 arm64,
openebs-zfsStorageClass and VolumeSnapshotClass): init, two commits, thengfs clone src copy --snapshot <first>. All 10 checks pass:dataSourceis the VolumeSnapshot, and the container never restarted;Calibration: a build whose
provision_seededignores the seed fails 3 checks (PVC not from the snapshot, copy empty, checkout finds no data).Commit 5, live: a namespace ResourceQuota stops the StatefulSet from being created. Before the fix, the refused clone left
{instance}-databehind; with it, nothing is left. The 10-check run above also passes with commit 5.Not tested:
Documentation
gfs lazy …andgfs clone.crates/applications/website/src/pages/docs/commands/clone.rsstill showsgfs clone --from(that spelling still works, with a warning);User Impact
gfs clone <source> <target>makes an independent copy from a snapshot. On Kubernetes it is a VolumeSnapshot clone with no blank boot.gfs lazy. Old spellings work for one release and warn on stderr. Removing them in the next minor release is the breaking change; this PR is not one.Checklist
cargo test). Two fail, identically onmain; see Testing.cargo clippy --all-targets --all-features -- -D warnings), on Linux.cargo fmt --check)gfs clone <source> <target>and thegfs lazyspellings; the old lazy spellings work either way.