feat: app deploy --package insted of --package-url

update package handling in CLI and only fetch package config
static package index
fix --from-yaml deployment
improved config handling
This commit is contained in:
Noor 2025-03-25 17:36:15 +05:30
parent 103fc2d7f5
commit 88af26351d
Signed by: noormohammedb
GPG Key ID: D83EFB8B3B967146
4 changed files with 118 additions and 99 deletions

@ -136,28 +136,12 @@ fn clap_cmd() -> Command {
.long_help("Port to expose on the application which mapped into the host's public IP's port") .long_help("Port to expose on the application which mapped into the host's public IP's port")
) )
.arg( .arg(
Arg::new("package-url") Arg::new("package")
.long("package-url") .long("package")
.help("Enclave package url") .help("Enclave package name")
.long_help( .default_value("base-package")
"Package registry url, detee-cli app package <binary>".to_string() + .value_parser(["base-package", "actix-app-info", "go-app-info"])
"check detee-cli app package --help for more information"
)
.required(true)
) )
/*
.arg(
Arg::new("package-type")
.long("package-type")
.help("Enclave package type")
.long_help(
"Type of package, public or private. public packages are signed by DeTEE,".to_string() +
"Public packages are used wellknown mr_enclave for hratls connection into enclave."
)
.default_value("public")
.value_parser(["public", "private"])
)
*/
.arg( .arg(
Arg::new("name") Arg::new("name")
.long("name") .long("name")

@ -1,16 +1,16 @@
use crate::sgx::utils::deploy_new_app_and_update_config;
use crate::config::Config; use crate::config::Config;
use crate::name_generator::random_app_name; use crate::name_generator::random_app_name;
use crate::sgx::config::{validate_yaml, DeteeCliExt}; use crate::sgx::config::{validate_yaml, DeteeCliExt};
use crate::sgx::grpc_brain::{delete_app, list_contracts}; use crate::sgx::grpc_brain::{delete_app, list_contracts};
use crate::sgx::grpc_dtpm::{attest_and_send_config, get_config_from_enclave}; use crate::sgx::grpc_dtpm::{attest_and_send_config, get_config_from_enclave};
use crate::sgx::packaging::package_enclave; use crate::sgx::packaging::package_enclave;
use crate::sgx::utils::{fetch_config_and_mr_enclave, override_envs_and_args_launch_config}; use crate::sgx::utils::{
deploy_new_app_and_update_config, fetch_config, override_envs_and_args_launch_config,
};
use crate::sgx::AppDeleteResponse; use crate::sgx::AppDeleteResponse;
use crate::sgx::{ use crate::sgx::{
append_uuid_list, get_app_node, get_app_node_by_contract, get_one_contract, inspect_node, append_uuid_list, get_app_node, get_app_node_by_contract, get_one_contract, inspect_node,
print_nodes, write_uuid_list, package_entry_from_name, print_nodes, write_uuid_list,
}; };
use crate::sgx::{AppContract, AppDeployResponse}; use crate::sgx::{AppContract, AppDeployResponse};
use crate::utils::block_on; use crate::utils::block_on;
@ -72,25 +72,30 @@ fn handle_package(package_match: &ArgMatches) -> Result<SimpleOutput, Box<dyn st
fn handle_deploy( fn handle_deploy(
deploy_match: &ArgMatches, deploy_match: &ArgMatches,
) -> Result<AppDeployResponse, Box<dyn std::error::Error>> { ) -> Result<AppDeployResponse, Box<dyn std::error::Error>> {
let mut app_deploy_config = if let Some(file_path) = deploy_match.get_one::<String>("yaml-path") let (mut app_deploy_config, app_launch_config) = if let Some(file_path) =
deploy_match.get_one::<String>("yaml-path")
{ {
AppDeployConfig::from_path(file_path).unwrap() // TODO: maybe add launch config on deploy command with --launch-config flag
(AppDeployConfig::from_path(file_path).unwrap(), None)
} else { } else {
let vcpu = *deploy_match.get_one::<u32>("vcpus").unwrap(); let vcpu = *deploy_match.get_one::<u32>("vcpus").unwrap();
let memory_mb = *deploy_match.get_one::<u32>("memory").unwrap(); let memory_mb = *deploy_match.get_one::<u32>("memory").unwrap();
let disk_mb = *deploy_match.get_one::<u32>("disk").unwrap(); let disk_mb = *deploy_match.get_one::<u32>("disk").unwrap();
let port = let port =
deploy_match.get_many::<u32>("port").unwrap_or_default().cloned().collect::<Vec<_>>(); deploy_match.get_many::<u32>("port").unwrap_or_default().cloned().collect::<Vec<_>>();
let package_url = deploy_match.get_one::<String>("package-url").unwrap().clone(); let package_name = deploy_match.get_one::<String>("package").unwrap().clone();
// let package_type = deploy_match.get_one::<String>("package-type").unwrap().clone();
let hours = *deploy_match.get_one::<u64>("hours").unwrap(); let hours = *deploy_match.get_one::<u64>("hours").unwrap();
let node_unit_price = *deploy_match.get_one::<u64>("price").unwrap(); let node_unit_price = *deploy_match.get_one::<u64>("price").unwrap();
let location = deploy_match.get_one::<String>("location").unwrap().as_str(); let location = deploy_match.get_one::<String>("location").unwrap().as_str();
let app_name = let app_name =
deploy_match.get_one::<String>("name").cloned().unwrap_or_else(random_app_name); deploy_match.get_one::<String>("name").cloned().unwrap_or_else(random_app_name);
let envs =
deploy_match.get_many::<String>("env").unwrap_or_default().cloned().collect::<Vec<_>>();
let args =
deploy_match.get_many::<String>("arg").unwrap_or_default().cloned().collect::<Vec<_>>();
// let private_package = package_type == "private";
let private_package = false; let private_package = false;
let resource = Resource { vcpu, memory_mb, disk_mb, port }; let resource = Resource { vcpu, memory_mb, disk_mb, port };
let node_pubkey = match block_on(get_app_node(resource.clone(), location.into())) { let node_pubkey = match block_on(get_app_node(resource.clone(), location.into())) {
Ok(node) => node.node_pubkey, Ok(node) => node.node_pubkey,
@ -101,41 +106,41 @@ fn handle_deploy(
} }
}; };
AppDeployConfig { let package_entry = package_entry_from_name(&package_name).unwrap();
package_url, let package_url = package_entry.package_url.clone();
resource, let public_package_mr_enclave = Some(package_entry.mr_enclave.to_vec());
node_unit_price,
hours, let config = block_on(fetch_config(&package_name))?;
node_pubkey,
private_package, let launch_config = override_envs_and_args_launch_config(config, envs, args);
app_name,
..Default::default() (
} AppDeployConfig {
package_url,
resource,
node_unit_price,
hours,
node_pubkey,
private_package,
app_name,
public_package_mr_enclave,
..Default::default()
},
Some(launch_config),
)
}; };
if app_deploy_config.app_name.is_empty() { if app_deploy_config.app_name.is_empty() {
app_deploy_config.app_name = random_app_name(); app_deploy_config.app_name = random_app_name();
} }
let (mr_enclave, mut launch_config) =
block_on(fetch_config_and_mr_enclave(&app_deploy_config.package_url))?;
app_deploy_config.public_package_mr_enclave = Some(mr_enclave.to_vec());
let envs =
deploy_match.get_many::<String>("env").unwrap_or_default().cloned().collect::<Vec<_>>();
let args =
deploy_match.get_many::<String>("arg").unwrap_or_default().cloned().collect::<Vec<_>>();
override_envs_and_args_launch_config(&mut launch_config, envs, args);
let app_name = app_deploy_config.app_name.clone(); let app_name = app_deploy_config.app_name.clone();
match block_on(deploy_new_app_and_update_config(app_deploy_config, launch_config)) { match block_on(deploy_new_app_and_update_config(app_deploy_config, app_launch_config)) {
Ok(new_app_res) if new_app_res.error.is_empty() => { Ok(new_app_res) if new_app_res.error.is_empty() => {
append_uuid_list(&new_app_res.uuid, &app_name)?; append_uuid_list(&new_app_res.uuid, &app_name)?;
Ok(new_app_res.into()) Ok((new_app_res, app_name).into())
} }
Ok(new_app_res) => Err(Box::new(std::io::Error::other(new_app_res.error))), Ok(new_app_res) => Err(Box::new(std::io::Error::other(new_app_res.error))),
Err(e) => Err(Box::new(e)), Err(e) => Err(Box::new(e)),
} }

@ -5,6 +5,8 @@ pub mod grpc_dtpm;
pub mod packaging; pub mod packaging;
pub mod utils; pub mod utils;
use std::sync::LazyLock;
use crate::config::Config; use crate::config::Config;
use crate::snp; use crate::snp;
use crate::{constants::HRATLS_APP_PORT, utils::block_on}; use crate::{constants::HRATLS_APP_PORT, utils::block_on};
@ -54,6 +56,47 @@ pub struct AppContract {
pub exposed_host_ports: Vec<(u32, u32)>, pub exposed_host_ports: Vec<(u32, u32)>,
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PublicIndex {
packages: Vec<PackageElement>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PackageElement {
package_name: String,
package_url: String,
launch_config_url: String,
mr_enclave: [u8; 32],
}
pub static PACKAGES_INDEX: LazyLock<PublicIndex> = LazyLock::new(|| {
PublicIndex { packages: vec![
// TODO: package a general base package
PackageElement{
package_name: "base-package".to_string(),
package_url: "https://registry.detee.ltd/sgx/packages/actix-app-info_package_2025-03-19_13-49-56.tar.gz".to_string(),
launch_config_url: "https://registry.detee.ltd/sgx/launch_configs/actix-app-info-launch-config_001.yaml".to_string(),
mr_enclave: [ 139, 208, 253, 40, 81, 80, 225, 137, 106, 182, 27, 200, 25, 128, 212, 235, 76, 153, 215, 42, 160, 69, 26, 132, 77, 223, 182, 180, 136, 218, 173, 184 ],
},
PackageElement{
package_name: "actix-app-info".to_string(),
package_url: "https://registry.detee.ltd/sgx/packages/actix-app-info_package_2025-03-19_13-49-56.tar.gz".to_string(),
launch_config_url: "https://registry.detee.ltd/sgx/launch_configs/actix-app-info-launch-config_001.yaml".to_string(),
mr_enclave: [ 139, 208, 253, 40, 81, 80, 225, 137, 106, 182, 27, 200, 25, 128, 212, 235, 76, 153, 215, 42, 160, 69, 26, 132, 77, 223, 182, 180, 136, 218, 173, 184 ],
},
// TODO: package a go package
PackageElement{
package_name: "go-app-info".to_string(),
package_url: "https://registry.detee.ltd/sgx/packages/actix-app-info_package_2025-03-19_13-49-56.tar.gz".to_string(),
launch_config_url: "https://registry.detee.ltd/sgx/launch_configs/actix-app-info-launch-config_001.yaml".to_string(),
mr_enclave: [ 139, 208, 253, 40, 81, 80, 225, 137, 106, 182, 27, 200, 25, 128, 212, 235, 76, 153, 215, 42, 160, 69, 26, 132, 77, 223, 182, 180, 136, 218, 173, 184 ],
}
] }
});
pub fn package_entry_from_name(package_name: &str) -> Option<PackageElement> {
PACKAGES_INDEX.packages.iter().find(|package| package.package_name == package_name).cloned()
}
fn display_mins(minutes: &u64) -> String { fn display_mins(minutes: &u64) -> String {
let mins = minutes % 60; let mins = minutes % 60;
let hours = minutes / 60; let hours = minutes / 60;
@ -142,16 +185,16 @@ pub struct AppDeployResponse {
impl crate::HumanOutput for AppDeployResponse { impl crate::HumanOutput for AppDeployResponse {
fn human_cli_print(&self) { fn human_cli_print(&self) {
println!("App deployd with UUID: {}", self.uuid); println!("App deployd with UUID: {}, App Name: {}", self.uuid, self.name);
} }
} }
impl From<NewAppRes> for AppDeployResponse { impl From<(NewAppRes, String)> for AppDeployResponse {
fn from(value: NewAppRes) -> Self { fn from((value, name): (NewAppRes, String)) -> Self {
Self { Self {
status: value.status, status: value.status,
uuid: value.uuid, uuid: value.uuid,
name: "".to_string(), name,
node_ip: value.ip_address, node_ip: value.ip_address,
hratls_port: value hratls_port: value
.mapped_ports .mapped_ports

@ -1,12 +1,12 @@
use super::grpc_brain::new_app;
use crate::constants::HRATLS_APP_PORT; use crate::constants::HRATLS_APP_PORT;
use crate::sgx::get_one_contract; use crate::sgx::get_one_contract;
use crate::sgx::grpc_brain::new_app;
use crate::sgx::grpc_dtpm::attest_and_send_config; use crate::sgx::grpc_dtpm::attest_and_send_config;
use crate::sgx::package_entry_from_name;
use detee_shared::app_proto::NewAppRes; use detee_shared::app_proto::NewAppRes;
use detee_shared::sgx::types::brain::AppDeployConfig; use detee_shared::sgx::types::brain::AppDeployConfig;
use detee_shared::sgx::types::dtpm::DtpmConfig; use detee_shared::sgx::types::dtpm::DtpmConfig;
use detee_shared::sgx::types::dtpm::EnvironmentEntry; use detee_shared::sgx::types::dtpm::EnvironmentEntry;
use serde::{Deserialize, Serialize};
use tokio_retry::strategy::FixedInterval; use tokio_retry::strategy::FixedInterval;
use tokio_retry::Retry; use tokio_retry::Retry;
@ -50,40 +50,17 @@ pub async fn hratls_url_and_mr_enclave_from_app_id(app_id: &str) -> (String, Opt
(format!("https://{public_ip}:{dtpm_port}"), mr_enclave) (format!("https://{public_ip}:{dtpm_port}"), mr_enclave)
} }
#[derive(Serialize, Deserialize, Debug)] pub async fn fetch_config(package_name: &str) -> Result<DtpmConfig, Error> {
struct PublicIndex { let index_package_entry = package_entry_from_name(package_name)
packages: Vec<PackageElement>, .ok_or(Error::PublicPackage("package not found for ".to_string() + package_name))?;
}
#[derive(Serialize, Deserialize, Debug)]
struct PackageElement {
package_url: String,
launch_config_url: String,
mr_enclave: [u8; 32],
}
pub async fn fetch_config_and_mr_enclave( let launch_config_url = index_package_entry.launch_config_url.clone();
package_url: &str,
) -> Result<([u8; 32], DtpmConfig), Error> {
let public_packages_index =
reqwest::get("https://registry.detee.ltd/sgx/public_packages_index.yaml")
.await?
.text()
.await?;
let index = serde_yaml::from_str::<PublicIndex>(&public_packages_index)?;
let index_package_entry =
index.packages.iter().find(|package| package.package_url == package_url).ok_or(
Error::PublicPackage("mr_enclave not found for this public package".to_string()),
)?;
let PackageElement { launch_config_url, mr_enclave, .. } = index_package_entry;
let launch_config_str = reqwest::get(launch_config_url).await?.text().await?; let launch_config_str = reqwest::get(launch_config_url).await?.text().await?;
let launch_config = serde_yaml::from_str::<DtpmConfig>(&launch_config_str)?; let launch_config = serde_yaml::from_str::<DtpmConfig>(&launch_config_str)?;
Ok((*mr_enclave, launch_config)) Ok(launch_config)
} }
pub fn calculate_nanolp_for_app( pub fn calculate_nanolp_for_app(
@ -109,10 +86,13 @@ pub fn calculate_nanolp_for_app(
} }
pub fn override_envs_and_args_launch_config( pub fn override_envs_and_args_launch_config(
launch_config: &mut DtpmConfig, mut launch_config: DtpmConfig,
envs: Vec<String>, envs: Vec<String>,
args: Vec<String>, args: Vec<String>,
) { ) -> DtpmConfig {
if envs.is_empty() && args.is_empty() {
return launch_config;
}
for env in envs { for env in envs {
let mut env = env.split("="); let mut env = env.split("=");
let key = env.next().expect("environment variable must be in the format 'key=value'"); let key = env.next().expect("environment variable must be in the format 'key=value'");
@ -135,22 +115,29 @@ pub fn override_envs_and_args_launch_config(
for arg in args { for arg in args {
launch_config.child_processes.first_mut().unwrap().arguments.push(arg); launch_config.child_processes.first_mut().unwrap().arguments.push(arg);
} }
launch_config
} }
pub async fn deploy_new_app_and_update_config( pub async fn deploy_new_app_and_update_config(
app_deploy_config: AppDeployConfig, app_deploy_config: AppDeployConfig,
launch_config: DtpmConfig, launch_config: Option<DtpmConfig>,
) -> Result<NewAppRes, Error> { ) -> Result<NewAppRes, Error> {
let new_app_res = new_app(app_deploy_config).await?; let new_app_res = new_app(app_deploy_config).await?;
if new_app_res.error.is_empty() { if new_app_res.error.is_empty() {
println!("Deploying..."); if let Some(launch_config) = launch_config {
tokio::time::sleep(tokio::time::Duration::from_millis(3100)).await; println!("Deploying...");
Retry::spawn(FixedInterval::from_millis(500).take(5), || { tokio::time::sleep(tokio::time::Duration::from_millis(3100)).await;
log::debug!("retrying attestation and launch config update"); Retry::spawn(FixedInterval::from_millis(500).take(5), || {
attest_and_send_config(launch_config.clone(), &new_app_res.uuid) log::debug!("retrying attestation and launch config update");
}) attest_and_send_config(launch_config.clone(), &new_app_res.uuid)
.await?; })
Ok(new_app_res) .await?;
Ok(new_app_res)
} else {
Ok(new_app_res)
}
} else { } else {
Err(Error::Deployment(new_app_res.error)) Err(Error::Deployment(new_app_res.error))
} }