Skip to content

Commit 756177e

Browse files
chefsaleclaude
andauthored
feat(meroctl): add 'tee fleet-join' subcommand + bump to rc.27 (#2167)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1e20600 commit 756177e

8 files changed

Lines changed: 119 additions & 3 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description = "Core Calimero infrastructure and tools"
99
# Update workspace metadata (see docs/RELEASE.md for versioning and release)
1010
[workspace.metadata.workspaces]
1111
# Shared version of all public crates; bump this when cutting a release.
12-
version = "0.10.1-rc.26"
12+
version = "0.10.1-rc.27"
1313
exclude = [
1414
"./apps/abi_conformance",
1515
"./apps/blobs",

crates/client/src/client/system.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//! System-level operations for the Calimero client.
22
33
use calimero_server_primitives::admin::{
4-
GenerateContextIdentityResponse, GetPeersCountResponse, InviteSpecializedNodeRequest,
5-
InviteSpecializedNodeResponse,
4+
FleetJoinRequest, FleetJoinResponse, GenerateContextIdentityResponse, GetPeersCountResponse,
5+
InviteSpecializedNodeRequest, InviteSpecializedNodeResponse,
66
};
77
use eyre::Result;
88

@@ -41,4 +41,19 @@ where
4141
.await?;
4242
Ok(response)
4343
}
44+
45+
/// Announce this node as a TEE fleet member for the given group.
46+
///
47+
/// Calls POST /admin-api/tee/fleet-join. The local node generates a TDX
48+
/// attestation, broadcasts `TeeAttestationAnnounce` on the namespace
49+
/// topic, waits for admission (up to 30s), and auto-joins all contexts
50+
/// in the group. Used by the mero-tee fleet sidecar after the manager
51+
/// assigns it to a group via /api/fleet/should-join.
52+
pub async fn fleet_join(&self, group_id: String) -> Result<FleetJoinResponse> {
53+
let response = self
54+
.connection
55+
.post("admin-api/tee/fleet-join", FleetJoinRequest { group_id })
56+
.await?;
57+
Ok(response)
58+
}
4459
}

crates/meroctl/src/cli.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod group;
2626
mod namespace;
2727
mod node;
2828
mod peers;
29+
mod tee;
2930
mod upgrade_policy;
3031
pub mod validation;
3132

@@ -38,6 +39,7 @@ use group::GroupCommand;
3839
use namespace::NamespaceCommand;
3940
use node::NodeCommand;
4041
use peers::PeersCommand;
42+
use tee::TeeCommand;
4143

4244
use crate::auth::{authenticate_with_session_cache, check_authentication};
4345

@@ -93,6 +95,7 @@ pub enum SubCommands {
9395
Namespace(NamespaceCommand),
9496
Call(CallCommand),
9597
Peers(PeersCommand),
98+
Tee(TeeCommand),
9699
#[command(subcommand)]
97100
Node(NodeCommand),
98101
}
@@ -167,6 +170,7 @@ impl RootCommand {
167170
SubCommands::Namespace(ns) => ns.run(&mut environment).await,
168171
SubCommands::Call(call) => call.run(&mut environment).await,
169172
SubCommands::Peers(peers) => peers.run(&mut environment).await,
173+
SubCommands::Tee(tee) => tee.run(&mut environment).await,
170174
SubCommands::Node(node) => {
171175
node.run(&environment, self.args.node.as_deref(), &self.args.home)
172176
.await

crates/meroctl/src/cli/tee.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use clap::{Parser, Subcommand};
2+
use const_format::concatcp;
3+
use eyre::Result;
4+
5+
use crate::cli::Environment;
6+
7+
pub mod fleet_join;
8+
9+
pub const EXAMPLES: &str = r"
10+
# Announce this node as a TEE fleet member and auto-join all contexts
11+
# in the group once admission succeeds.
12+
$ meroctl --node node1 tee fleet-join <GROUP_ID>
13+
";
14+
15+
#[derive(Debug, Parser)]
16+
#[command(about = "TEE fleet-node commands")]
17+
#[command(after_help = concatcp!(
18+
"Examples:",
19+
EXAMPLES
20+
))]
21+
pub struct TeeCommand {
22+
#[command(subcommand)]
23+
pub subcommand: TeeSubCommands,
24+
}
25+
26+
#[derive(Debug, Subcommand)]
27+
pub enum TeeSubCommands {
28+
#[command(name = "fleet-join", alias = "fleet_join")]
29+
FleetJoin(fleet_join::FleetJoinCommand),
30+
}
31+
32+
impl TeeCommand {
33+
pub async fn run(self, environment: &mut Environment) -> Result<()> {
34+
match self.subcommand {
35+
TeeSubCommands::FleetJoin(cmd) => cmd.run(environment).await,
36+
}
37+
}
38+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use clap::Parser;
2+
use eyre::Result;
3+
4+
use crate::cli::Environment;
5+
6+
#[derive(Clone, Debug, Parser)]
7+
#[command(about = "Announce this node as a TEE fleet member for a group")]
8+
pub struct FleetJoinCommand {
9+
/// Hex-encoded group ID (64 hex chars / 32 bytes).
10+
#[clap(name = "GROUP_ID")]
11+
pub group_id: String,
12+
}
13+
14+
impl FleetJoinCommand {
15+
pub async fn run(self, environment: &mut Environment) -> Result<()> {
16+
let client = environment.client()?;
17+
let response = client.fleet_join(self.group_id).await?;
18+
environment.output.write(&response);
19+
Ok(())
20+
}
21+
}

crates/meroctl/src/output.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod blobs;
55
pub mod common;
66
pub mod contexts;
77
pub mod groups;
8+
pub mod tee;
89

910
// Re-export common types
1011
pub use blobs::{BlobDownloadResponse, BlobUploadResponse};

crates/meroctl/src/output/tee.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use calimero_server_primitives::admin::FleetJoinResponse;
2+
use comfy_table::{Cell, Color, Table};
3+
4+
use super::Report;
5+
6+
impl Report for FleetJoinResponse {
7+
fn report(&self) {
8+
let mut table = Table::new();
9+
let _ = table.set_header(vec![
10+
Cell::new("Fleet Join").fg(Color::Green),
11+
Cell::new("Value").fg(Color::Blue),
12+
]);
13+
let _ = table.add_row(vec!["Status", &self.status]);
14+
let _ = table.add_row(vec!["Admitted", &self.admitted.to_string()]);
15+
let _ = table.add_row(vec!["Group ID", &self.group_id]);
16+
let _ = table.add_row(vec!["Namespace ID", &self.namespace_id]);
17+
let _ = table.add_row(vec!["Public Key", &self.public_key]);
18+
let _ = table.add_row(vec![
19+
"Contexts Joined",
20+
&self.contexts_joined.len().to_string(),
21+
]);
22+
println!("{table}");
23+
for (idx, ctx) in self.contexts_joined.iter().enumerate() {
24+
println!(" [{idx}] {ctx}");
25+
}
26+
}
27+
}

crates/server/primitives/src/admin/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,16 @@ impl Validate for FleetJoinRequest {
11531153
}
11541154
}
11551155

1156+
#[derive(Clone, Debug, Deserialize, Serialize)]
1157+
pub struct FleetJoinResponse {
1158+
pub status: String,
1159+
pub group_id: String,
1160+
pub namespace_id: String,
1161+
pub public_key: String,
1162+
pub admitted: bool,
1163+
pub contexts_joined: Vec<String>,
1164+
}
1165+
11561166
#[derive(Debug, Serialize)]
11571167
#[serde(rename_all = "camelCase")]
11581168
pub struct TeeInfoResponseData {

0 commit comments

Comments
 (0)