cli: Support `--seed` on `rad clone`

Allows specifying one or more seeds to clone from. This is useful for
cloning private repositories which are not in the routing table.
This commit is contained in:
cloudhead 2024-01-03 14:07:19 +01:00
parent 150130e99b
commit 04bfbca15d
No known key found for this signature in database
4 changed files with 129 additions and 13 deletions

View File

@ -0,0 +1,45 @@
Given a private repo `rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu` belonging to Alice,
Alice allows Bob to fetch it, and Bob, without the updated identity document
is able to fetch it by specifiying Alice as a seed.
``` ~alice
$ rad id update --title "Allow Bob" --description "" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
...
$ rad inspect --identity
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "radicle heartwood protocol & stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
"threshold": 1,
"visibility": {
"type": "private",
"allow": [
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
]
}
}
```
``` ~bob
$ rad ls --all --private
$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [...]/.radicle/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ radicle heartwood protocol & stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the project directory.
```

View File

@ -2,14 +2,13 @@ Given a private repo `rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu` belonging to Alice,
Bob tries to fetch it, and even though he's connected to Alice, it fails.
``` ~bob
$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --scope followed
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'followed'
$ rad ls
```
``` ~bob (fail)
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. error: failed to perform fetch handshake
✗ Error: repository fetch from 1 seed(s) failed
✗ Error: repository rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu not found
```
She allows Bob to view the repository. And when she syncs, one node (Bob) gets

View File

@ -36,9 +36,17 @@ Usage
rad clone <rid> [<directory>] [--scope <scope>] [<option>...]
The `clone` command will use your local node's routing table to find seeds from
which it can clone the repository.
For private repositories, the `--seed` option can be passed to clone directly
from a known seed in the privacy set.
Options
--scope <scope> Follow scope (default: all)
-s, --seed <nid> Clone from this seed (may be specified multiple times)
--timeout <secs> Timeout for fetching repository (default: 9)
--help Print help
"#,
@ -52,6 +60,10 @@ pub struct Options {
directory: Option<PathBuf>,
/// The seeding scope of the repository.
scope: Scope,
/// Sync mode.
mode: sync::RepoSync,
/// Fetch timeout.
timeout: time::Duration,
}
impl Args for Options {
@ -61,15 +73,33 @@ impl Args for Options {
let mut parser = lexopt::Parser::from_args(args);
let mut id: Option<Id> = None;
let mut scope = Scope::All;
let mut mode = sync::RepoSync::default();
let mut timeout = time::Duration::from_secs(9);
let mut directory = None;
while let Some(arg) = parser.next()? {
match arg {
Long("seed") | Short('s') => {
let value = parser.value()?;
let value = term::args::nid(&value)?;
if let sync::RepoSync::Seeds(seeds) = &mut mode {
seeds.push(value);
} else {
mode = sync::RepoSync::Seeds(vec![value]);
}
}
Long("scope") => {
let value = parser.value()?;
scope = term::args::parse_value("scope", value)?;
}
Long("timeout") => {
let value = parser.value()?;
let secs = term::args::number(&value)?;
timeout = time::Duration::from_secs(secs as u64);
}
Long("no-confirm") => {
// We keep this flag here for consistency though it doesn't have any effect,
// since the command is fully non-interactive.
@ -99,6 +129,8 @@ impl Args for Options {
id,
directory,
scope,
mode,
timeout,
},
vec![],
))
@ -120,6 +152,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
options.id,
options.directory.clone(),
options.scope,
options.mode,
options.timeout,
&mut node,
&signer,
&profile.storage,
@ -204,6 +238,8 @@ pub fn clone<G: Signer>(
id: Id,
directory: Option<PathBuf>,
scope: Scope,
mode: sync::RepoSync,
timeout: time::Duration,
node: &mut Node,
signer: &G,
storage: &Storage,
@ -218,7 +254,7 @@ pub fn clone<G: Signer>(
> {
let me = *signer.public_key();
// Track.
// Seed repository.
if node.seed(id, scope)? {
term::success!(
"Seeding policy updated for {} with scope '{scope}'",
@ -226,12 +262,7 @@ pub fn clone<G: Signer>(
);
}
let results = sync::fetch(
id,
sync::RepoSync::default(),
time::Duration::from_secs(9),
node,
)?;
let results = sync::fetch(id, mode, timeout, node)?;
let Ok(repository) = storage.repository(id) else {
// If we don't have the project locally, even after attempting to fetch,
// there's nothing we can do.

View File

@ -1683,6 +1683,47 @@ fn rad_init_private_clone() {
.unwrap();
}
#[test]
fn rad_init_private_clone_seed() {
let mut environment = Environment::new();
let alice = environment.node(Config::test(Alias::new("alice")));
let bob = environment.node(Config::test(Alias::new("bob")));
let working = environment.tmp().join("working");
fixtures::repository(working.join("alice"));
let alice = alice.spawn();
let mut bob = bob.spawn();
test(
"examples/rad-init-private.md",
working.join("alice"),
Some(&alice.home),
[],
)
.unwrap();
bob.connect(&alice).converge([&alice]);
formula(
&environment.tmp(),
"examples/rad-init-private-clone-seed.md",
)
.unwrap()
.home(
"alice",
working.join("alice"),
[("RAD_HOME", alice.home.path().display())],
)
.home(
"bob",
bob.home.path(),
[("RAD_HOME", bob.home.path().display())],
)
.run()
.unwrap();
}
#[test]
fn rad_publish() {
let mut environment = Environment::new();