Skip to content

[codex] add flmctl deploy and object helpers#459

Merged
k82cn merged 1 commit into
xflops:mainfrom
k82cn:flm_458
May 17, 2026
Merged

[codex] add flmctl deploy and object helpers#459
k82cn merged 1 commit into
xflops:mainfrom
k82cn:flm_458

Conversation

@k82cn
Copy link
Copy Markdown
Contributor

@k82cn k82cn commented May 17, 2026

Summary

  • add RFE458 design and flmctl deploy for packaging directory, tarball, or executable-file applications
  • add Rust SDK object helpers and object-cache wire integration coverage
  • add binary installer support and URL-aware application release installs
  • add the Candle Based Rust example under examples/candle/based

Notes

Executable-file deploys create <app>.tar.gz locally with bin/<binary> inside, then upload it under a three-component object key like <app>/pkg/<app>-<sha16>.tar.gz to match FlamePy/object-cache rules.

Host-built example binaries were intentionally not committed.

Validation

  • cargo fmt --all -- --check
  • cargo test -p flmctl deploy
  • cargo test -p flame-rs object --lib
  • cargo test -p flame-executor-manager binary
  • cargo test -p flame-object-cache flame_rs_ with local-port permission
  • cargo check -p flmctl
  • cargo check -p flame-rs
  • cargo check -p flame-executor-manager
  • git diff --check

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new flmctl deploy command to simplify application deployment by automating packaging, uploading to the object cache, and registration. It also adds support for binary and Python installers, updates the executor manager to support content-addressed releases, and includes a new example application. My review identified a critical logic error in fetch_object_bytes regarding chunked object retrieval, a potential stability issue with DefaultHasher for release IDs, and an overly restrictive path validation in the tarball unpacking logic.

Comment thread sdk/rust/src/object.rs
Comment on lines +705 to +751
async fn fetch_object_bytes(reference: &ObjectRef) -> Result<Vec<u8>, FlameError> {
ObjectKey::from_key(&reference.key)?;
let endpoint = CacheEndpoint::parse(&reference.endpoint)?;
let tls = current_cache_tls()?;
let mut client = FlightClient::new(connect_cache(&endpoint, tls.as_ref()).await?);
let mut stream = client
.do_get(Ticket::new(format!("{}:0", reference.key)))
.await
.map_err(|e| FlameError::Internal(format!("cache get failed: {}", e)))?;

let mut base = None;
while let Some(batch) = stream
.try_next()
.await
.map_err(|e| FlameError::Internal(format!("cache get failed: {}", e)))?
{
if batch.num_rows() == 0 {
continue;
}
let data = data_column(&batch)?;
if let Some(kind) = kind_column(&batch)? {
for row in 0..batch.num_rows() {
match kind.value(row) {
OBJECT_KIND_BASE => {
if base.is_some() {
return Err(FlameError::InvalidConfig(
"object response contained multiple base rows".to_string(),
));
}
base = Some(data.value(row).to_vec());
}
OBJECT_KIND_PATCH => {}
other => {
return Err(FlameError::InvalidConfig(format!(
"invalid object response kind '{}'",
other
)))
}
}
}
} else if base.is_none() {
base = Some(data.value(0).to_vec());
}
}

base.ok_or_else(|| FlameError::NotFound(reference.key.clone()))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The fetch_object_bytes function contains a logic error that prevents it from correctly handling objects split across multiple rows or record batches. It currently returns an error if it encounters more than one OBJECT_KIND_BASE row, and it only retrieves the first row if the kind column is missing. Since do_put_file uploads objects in 1MB chunks as separate rows, this function will fail for any object larger than the chunk size. The implementation should accumulate bytes from all base rows across all batches in the stream.

async fn fetch_object_bytes(reference: &ObjectRef) -> Result<Vec<u8>, FlameError> {
    ObjectKey::from_key(&reference.key)?;
    let endpoint = CacheEndpoint::parse(&reference.endpoint)?;
    let tls = current_cache_tls()?;
    let mut client = FlightClient::new(connect_cache(&endpoint, tls.as_ref()).await?);
    let mut stream = client
        .do_get(Ticket::new(format!("{}:0", reference.key)))
        .await
        .map_err(|e| FlameError::Internal(format!("cache get failed: {}", e)))?;

    let mut base_data = Vec::new();
    let mut found = false;
    while let Some(batch) = stream
        .try_next()
        .await
        .map_err(|e| FlameError::Internal(format!("cache get failed: {}", e)))?
    {
        if batch.num_rows() == 0 {
            continue;
        }
        let data = data_column(&batch)?;
        let kind = kind_column(&batch)?;
        for row in 0..batch.num_rows() {
            let is_base = match kind {
                Some(k) => k.value(row) == OBJECT_KIND_BASE,
                None => true,
            };
            if is_base {
                base_data.extend_from_slice(data.value(row));
                found = true;
            }
        }
    }

    if !found {
        return Err(FlameError::NotFound(reference.key.clone()));
    }
    Ok(base_data)
}

Comment on lines +57 to +61
fn release_id(&self) -> String {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using std::collections::hash_map::DefaultHasher for generating a release_id that is used as a persistent directory name is not recommended. The algorithm used by DefaultHasher is not guaranteed to be stable across different Rust compiler versions or even different runs of the same binary. If the cluster is upgraded and the hasher implementation changes, the executor manager will fail to locate existing installations on disk, leading to redundant downloads and orphaned directories. Consider using a stable hashing algorithm like SHA-256 or a fixed SipHash implementation to ensure consistent release IDs.

Comment on lines +376 to +390
fn validate_relative_path(path: &Path) -> Result<(), FlameError> {
for component in path.components() {
match component {
Component::Normal(_) => {}
_ => {
return Err(FlameError::InvalidConfig(format!(
"unsafe archive path: {}",
path.display()
)))
}
}
}
Ok(())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The validate_relative_path function is too restrictive because it rejects paths containing Component::CurDir (e.g., ./). Many tarballs created with common tools (like tar -C dir .) include a leading ./ for every entry. This implementation will cause flmctl deploy to fail on valid application packages that use this standard layout. The function should be updated to allow Component::CurDir while still rejecting dangerous components like ParentDir and RootDir.

Suggested change
fn validate_relative_path(path: &Path) -> Result<(), FlameError> {
for component in path.components() {
match component {
Component::Normal(_) => {}
_ => {
return Err(FlameError::InvalidConfig(format!(
"unsafe archive path: {}",
path.display()
)))
}
}
}
Ok(())
}
fn validate_relative_path(path: &Path) -> Result<(), FlameError> {
for component in path.components() {
match component {
Component::Normal(_) | Component::CurDir => {}
_ => {
return Err(FlameError::InvalidConfig(format!(
"unsafe archive path: {}",
path.display()
)))
}
}
}
Ok(())
}

Add RFE458 design and flmctl deploy packaging for directory, tarball, and executable application inputs.

Add Rust SDK object helpers, object-cache integration coverage, binary application installer support, and example installation through flmadm.

Update Pi and Candle Rust examples for the new typed API and direct flmctl deploy flow.
@k82cn k82cn merged commit 89a0dbf into xflops:main May 17, 2026
6 checks passed
@k82cn k82cn deleted the flm_458 branch May 17, 2026 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant