Skip to content

Commit

Permalink
Merge pull request #608 from kinode-dao/release-candidate
Browse files Browse the repository at this point in the history
v0.9.9-rc
  • Loading branch information
nick1udwig authored Nov 13, 2024
2 parents 1d01d9b + 0908b84 commit efabf3c
Show file tree
Hide file tree
Showing 23 changed files with 866 additions and 80 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "kinode_lib"
authors = ["KinodeDAO"]
version = "0.9.8"
version = "0.9.9"
edition = "2021"
description = "A general-purpose sovereign cloud computing platform"
homepage = "https://kinode.org"
Expand Down
2 changes: 1 addition & 1 deletion kinode/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "kinode"
authors = ["KinodeDAO"]
version = "0.9.8"
version = "0.9.9"
edition = "2021"
description = "A general-purpose sovereign cloud computing platform"
homepage = "https://kinode.org"
Expand Down
77 changes: 74 additions & 3 deletions kinode/packages/app_store/app_store/src/http_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use crate::{
kinode::process::chain::{ChainRequests, ChainResponses},
kinode::process::downloads::{
DownloadRequests, DownloadResponses, LocalDownloadRequest, RemoveFileRequest,
DownloadRequests, DownloadResponses, Entry, LocalDownloadRequest, RemoveFileRequest,
},
state::{MirrorCheck, PackageState, State},
};
Expand All @@ -31,6 +31,7 @@ pub fn init_frontend(our: &Address, http_server: &mut server::HttpServer) {
"/apps/:id", // detail about an on-chain app
"/downloads/:id", // local downloads for an app
"/installed/:id", // detail about an installed app
"/manifest", // manifest of a downloaded app, id & version hash in query params
// actions
"/apps/:id/download", // download a listed app
"/apps/:id/install", // install a downloaded app
Expand Down Expand Up @@ -190,6 +191,7 @@ fn make_widget() -> String {
/// - get all apps we've published: GET /ourapps
/// - get detail about a specific app: GET /apps/:id
/// - get detail about a specific apps downloads: GET /downloads/:id
/// - get manifest of a specific downloaded app: GET /manifest?id={id}&version_hash={version_hash}
/// - remove a downloaded app: POST /downloads/:id/remove
/// - get online/offline mirrors for a listed app: GET /mirrorcheck/:node
Expand Down Expand Up @@ -225,8 +227,8 @@ pub fn handle_http_request(
}
}

fn get_package_id(url_params: &HashMap<String, String>) -> anyhow::Result<PackageId> {
let Some(package_id) = url_params.get("id") else {
fn get_package_id(params: &HashMap<String, String>) -> anyhow::Result<PackageId> {
let Some(package_id) = params.get("id") else {
return Err(anyhow::anyhow!("Missing id"));
};

Expand All @@ -246,6 +248,7 @@ fn gen_package_info(id: &PackageId, state: &PackageState) -> serde_json::Value {
"our_version_hash": state.our_version_hash,
"verified": state.verified,
"caps_approved": state.caps_approved,
"pending_update_hash": state.pending_update_hash,
})
}

Expand All @@ -258,6 +261,7 @@ fn serve_paths(

let bound_path: &str = req.bound_path(Some(&our.process.to_string()));
let url_params = req.url_params();
let query_params = req.query_params();

match bound_path {
// GET all apps
Expand Down Expand Up @@ -362,6 +366,73 @@ fn serve_paths(
)),
}
}
"/manifest" => {
// get manifest of a downloaded app, version hash and id in query params
let Ok(package_id) = get_package_id(query_params) else {
return Ok((
StatusCode::BAD_REQUEST,
None,
format!("Missing id in query params.").into_bytes(),
));
};

let Some(version_hash) = query_params.get("version_hash") else {
return Ok((
StatusCode::BAD_REQUEST,
None,
format!("Missing version_hash in query params.").into_bytes(),
));
};

let package_id = crate::kinode::process::main::PackageId::from_process_lib(package_id);

// get the file corresponding to the version hash, extract manifest and return.
let resp = Request::to(("our", "downloads", "app_store", "sys"))
.body(serde_json::to_vec(&DownloadRequests::GetFiles(Some(
package_id.clone(),
)))?)
.send_and_await_response(5)??;

let msg = serde_json::from_slice::<DownloadResponses>(resp.body())?;
match msg {
DownloadResponses::GetFiles(files) => {
let file_name = format!("{version_hash}.zip");
let file_entry = files.into_iter().find(|entry| match entry {
Entry::File(file) => file.name == file_name,
_ => false,
});

match file_entry {
Some(Entry::File(file)) => {
let response = serde_json::json!({
"package_id": package_id,
"version_hash": version_hash,
"manifest": file.manifest,
});
return Ok((StatusCode::OK, None, serde_json::to_vec(&response)?));
}
_ => {
return Ok((
StatusCode::NOT_FOUND,
None,
format!("File with version hash {} not found", version_hash)
.into_bytes(),
));
}
}
}
DownloadResponses::Err(e) => Ok((
StatusCode::NOT_FOUND,
None,
format!("Error from downloads: {:?}", e).into_bytes(),
)),
_ => Ok((
StatusCode::INTERNAL_SERVER_ERROR,
None,
format!("Invalid response from downloads: {:?}", msg).into_bytes(),
)),
}
}
"/installed" => {
let all: Vec<serde_json::Value> = state
.packages
Expand Down
35 changes: 22 additions & 13 deletions kinode/packages/app_store/app_store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,21 +178,30 @@ fn handle_message(
let package_id = req.download_info.package_id;
let version_hash = req.download_info.version_hash;

if let Some(package) = state.packages.get(&package_id.clone().to_process_lib()) {
if package.manifest_hash == Some(manifest_hash) {
print_to_terminal(1, "auto_install:main, manifest_hash match");
if let Err(e) =
utils::install(&package_id, None, &version_hash, state, &our.node)
{
print_to_terminal(1, &format!("error auto_installing package: {e}"));
} else {
println!(
"auto_installed update for package: {:?}",
&package_id.to_process_lib()
);
let process_lib_package_id = package_id.clone().to_process_lib();

// first, check if we have the package and get its manifest hash
let should_auto_install = state
.packages
.get(&process_lib_package_id)
.map(|package| package.manifest_hash == Some(manifest_hash.clone()))
.unwrap_or(false);

if should_auto_install {
if let Err(e) =
utils::install(&package_id, None, &version_hash, state, &our.node)
{
if let Some(package) = state.packages.get_mut(&process_lib_package_id) {
package.pending_update_hash = Some(version_hash);
}
println!("error auto-installing package: {e}");
} else {
print_to_terminal(1, "auto_install:main, manifest_hash do not match");
println!("auto-installed update for package: {process_lib_package_id}");
}
} else {
if let Some(package) = state.packages.get_mut(&process_lib_package_id) {
package.pending_update_hash = Some(version_hash);
println!("error auto-installing package: manifest hash mismatch");
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions kinode/packages/app_store/app_store/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,19 @@ pub struct PackageState {
/// capabilities have changed. if they have changed, auto-install must fail
/// and the user must approve the new capabilities.
pub manifest_hash: Option<String>,
/// stores the version hash of a failed auto-install attempt, which can be
/// later installed by the user by approving new caps.
pub pending_update_hash: Option<String>,
}

// this seems cleaner to me right now with pending_update_hash, but given how we serialize
// the state to disk right now, with installed_apis and packages being populated directly
// from the filesystem, not sure I'd like to serialize the whole of this state (maybe separate out the pending one?)
// another option would be to have the download_api recheck the manifest hash? but not sure...
// arbitrary complexity here.

// alternative is main loop doing this, storing it.

/// this process's saved state
pub struct State {
/// packages we have installed
Expand Down Expand Up @@ -122,6 +133,7 @@ impl State {
verified: true, // implicitly verified (TODO re-evaluate)
caps_approved: false, // must re-approve if you want to do something ??
manifest_hash: Some(manifest_hash),
pending_update_hash: None, // ... this could be a separate state saved. don't want to reflect this info on-disk as a file.
},
);

Expand Down
1 change: 1 addition & 0 deletions kinode/packages/app_store/app_store/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ pub fn install(
verified: true, // sideloaded apps are implicitly verified because there is no "source" to verify against
caps_approved: true, // TODO see if we want to auto-approve local installs
manifest_hash: Some(manifest_hash),
pending_update_hash: None, // TODO: doublecheck if problematically overwrites auto_update state.
};

if let Ok(extracted) = extract_api(&process_package_id) {
Expand Down
72 changes: 63 additions & 9 deletions kinode/packages/app_store/downloads/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,32 @@ fn handle_message(
desired_version_hash,
worker_address: our_worker.to_string(),
}))
.expects_response(60)
.context(&download_request)
.send()?;
}
DownloadRequests::RemoteDownload(download_request) => {
// this is a node requesting a download from us.
// check if we are mirroring. we should maybe implement some back and forth here.
// small handshake for started? but we do not really want to wait for that in this loop..
// might be okay. implement.
let RemoteDownloadRequest {
package_id,
desired_version_hash,
worker_address,
} = download_request;

let process_lib_package_id = package_id.clone().to_process_lib();

// check if we are mirroring, if not send back an error.
if !state.mirroring.contains(&process_lib_package_id) {
let resp = DownloadResponses::Err(DownloadError::NotMirroring);
Response::new().body(&resp).send()?;
return Ok(()); // return here, todo unify remote and local responses?
}

if !download_zip_exists(&process_lib_package_id, &desired_version_hash) {
let resp = DownloadResponses::Err(DownloadError::FileNotFound);
Response::new().body(&resp).send()?;
return Ok(()); // return here, todo unify remote and local responses?
}

let target_worker = Address::from_str(&worker_address)?;
let _ = spawn_send_transfer(
our,
Expand All @@ -230,6 +243,8 @@ fn handle_message(
APP_SHARE_TIMEOUT,
&target_worker,
)?;
let resp = DownloadResponses::Success;
Response::new().body(&resp).send()?;
}
DownloadRequests::Progress(ref progress) => {
// forward progress to main:app_store:sys,
Expand Down Expand Up @@ -428,11 +443,34 @@ fn handle_message(
} else {
match message.body().try_into()? {
Resp::Download(download_response) => {
// these are handled in line.
print_to_terminal(
1,
&format!("got a weird download response: {:?}", download_response),
);
// get context of the response.
// handled are errors or ok responses from a remote node.

if let Some(context) = message.context() {
let download_request = serde_json::from_slice::<LocalDownloadRequest>(context)?;
match download_response {
DownloadResponses::Err(e) => {
Request::to(("our", "main", "app_store", "sys"))
.body(DownloadCompleteRequest {
package_id: download_request.package_id.clone(),
version_hash: download_request.desired_version_hash.clone(),
err: Some(e),
})
.send()?;
}
DownloadResponses::Success => {
// todo: maybe we do something here.
print_to_terminal(
1,
&format!(
"downloads: got success response from remote node: {:?}",
download_request
),
);
}
_ => {}
}
}
}
Resp::HttpClient(resp) => {
let Some(context) = message.context() else {
Expand Down Expand Up @@ -575,6 +613,22 @@ fn extract_and_write_manifest(file_contents: &[u8], manifest_path: &str) -> anyh
Ok(())
}

/// Check if a download zip exists for a given package and version hash.
/// Used to check if we can share a package or not!
fn download_zip_exists(package_id: &PackageId, version_hash: &str) -> bool {
let filename = format!(
"/app_store:sys/downloads/{}:{}/{}.zip",
package_id.package_name,
package_id.publisher(),
version_hash
);
let res = vfs::metadata(&filename, None);
match res {
Ok(meta) => meta.file_type == vfs::FileType::File,
Err(_e) => false,
}
}

fn get_manifest_hash(package_id: PackageId, version_hash: String) -> anyhow::Result<String> {
let package_dir = format!("{}/{}", "/app_store:sys/downloads", package_id.to_string());
let manifest_path = format!("{}/{}.json", package_dir, version_hash);
Expand Down
Loading

0 comments on commit efabf3c

Please sign in to comment.