Skip to content

Commit 0fc9997

Browse files
MalteJclaude
andcommitted
Use image config for container Entrypoint, Cmd, Env, WorkingDir
Parse OCI image config during pull and store in metadata. Apply Kubernetes/CRI semantics when generating OCI runtime spec: - command overrides ENTRYPOINT - args overrides CMD - If only args set, use image ENTRYPOINT + container args Simplified OCI spec by removing capabilities, resources, and masked paths. Added writable /tmp and /run tmpfs mounts for container apps. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 54158d1 commit 0fc9997

8 files changed

Lines changed: 182 additions & 118 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mvirt-one/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ tonic-build = "0.12"
6767
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "net"] }
6868
tonic = "0.12"
6969
nix = { version = "0.29", features = ["user", "process", "signal"] }
70+
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }

mvirt-one/src/services/image/filestore.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! Handles layer extraction and rootfs assembly.
44
//! Based on FeOS image-service/filestore.rs pattern.
55
6-
use super::{ImageInfo, ImageState, PulledImageData};
6+
use super::{ImageConfig, ImageInfo, ImageState, PulledImageData};
77
use crate::error::ImageError;
88
use flate2::read::GzDecoder;
99
use log::{error, info, warn};
@@ -20,6 +20,14 @@ use tokio::sync::{mpsc, oneshot};
2020
#[derive(Serialize, Deserialize)]
2121
struct ImageMetadata {
2222
image_ref: String,
23+
#[serde(default)]
24+
entrypoint: Vec<String>,
25+
#[serde(default)]
26+
cmd: Vec<String>,
27+
#[serde(default)]
28+
env: Vec<String>,
29+
#[serde(default)]
30+
working_dir: String,
2331
}
2432

2533
/// Commands for the FileStore actor.
@@ -146,14 +154,21 @@ impl FileStore {
146154
}
147155
}
148156

149-
// Write config.json
157+
// Parse image config (Entrypoint, Cmd, Env, etc.)
158+
let image_config = super::parse_image_config(&image_data.config);
159+
160+
// Write config.json (raw OCI image config)
150161
fs::write(final_dir.join("config.json"), image_data.config)
151162
.await
152163
.map_err(ImageError::Storage)?;
153164

154-
// Write metadata.json
165+
// Write metadata.json (with parsed config for easy loading)
155166
let metadata = ImageMetadata {
156167
image_ref: image_ref.to_string(),
168+
entrypoint: image_config.entrypoint,
169+
cmd: image_config.cmd,
170+
env: image_config.env,
171+
working_dir: image_config.working_dir,
157172
};
158173
let metadata_json = serde_json::to_string_pretty(&metadata)
159174
.map_err(|e| ImageError::Storage(std::io::Error::other(e)))?;
@@ -201,6 +216,12 @@ impl FileStore {
201216
image_ref: metadata.image_ref,
202217
rootfs_path: rootfs_path.to_string_lossy().to_string(),
203218
state: ImageState::Ready,
219+
config: ImageConfig {
220+
entrypoint: metadata.entrypoint,
221+
cmd: metadata.cmd,
222+
env: metadata.env,
223+
working_dir: metadata.working_dir,
224+
},
204225
};
205226
store.insert(uuid.to_string(), image_info);
206227
} else {

mvirt-one/src/services/image/mod.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ pub enum Command {
4242
pub struct PullResponse {
4343
pub image_id: String,
4444
pub rootfs_path: String,
45+
pub config: ImageConfig,
46+
}
47+
48+
/// Parsed image configuration (Entrypoint, Cmd, Env, etc.)
49+
#[derive(Debug, Clone, Default)]
50+
pub struct ImageConfig {
51+
pub entrypoint: Vec<String>,
52+
pub cmd: Vec<String>,
53+
pub env: Vec<String>,
54+
pub working_dir: String,
4555
}
4656

4757
/// Information about a cached image.
@@ -51,6 +61,7 @@ pub struct ImageInfo {
5161
pub image_ref: String,
5262
pub rootfs_path: String,
5363
pub state: ImageState,
64+
pub config: ImageConfig,
5465
}
5566

5667
/// State of an image in the cache.
@@ -75,3 +86,41 @@ pub struct PulledLayer {
7586
pub media_type: String,
7687
pub data: Vec<u8>,
7788
}
89+
90+
/// Parse image config from OCI image config JSON blob.
91+
pub fn parse_image_config(config_json: &[u8]) -> ImageConfig {
92+
use serde::Deserialize;
93+
94+
#[derive(Deserialize, Default)]
95+
struct OciImageSpec {
96+
config: Option<OciImageConfigInner>,
97+
}
98+
99+
#[derive(Deserialize, Default)]
100+
struct OciImageConfigInner {
101+
#[serde(rename = "Entrypoint")]
102+
entrypoint: Option<Vec<String>>,
103+
#[serde(rename = "Cmd")]
104+
cmd: Option<Vec<String>>,
105+
#[serde(rename = "Env")]
106+
env: Option<Vec<String>>,
107+
#[serde(rename = "WorkingDir")]
108+
working_dir: Option<String>,
109+
}
110+
111+
match serde_json::from_slice::<OciImageSpec>(config_json) {
112+
Ok(spec) => {
113+
let config = spec.config.unwrap_or_default();
114+
ImageConfig {
115+
entrypoint: config.entrypoint.unwrap_or_default(),
116+
cmd: config.cmd.unwrap_or_default(),
117+
env: config.env.unwrap_or_default(),
118+
working_dir: config.working_dir.unwrap_or_default(),
119+
}
120+
}
121+
Err(e) => {
122+
log::warn!("Failed to parse image config: {}", e);
123+
ImageConfig::default()
124+
}
125+
}
126+
}

mvirt-one/src/services/image/orchestrator.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use super::filestore::{FileCommand, FileStore};
66
use super::puller::pull_oci_image;
7-
use super::{Command, ImageInfo, ImageState, PullResponse};
7+
use super::{Command, ImageConfig, ImageInfo, ImageState, PullResponse, parse_image_config};
88
use crate::error::ImageError;
99
use log::{error, info};
1010
use std::collections::HashMap;
@@ -74,6 +74,7 @@ impl ImageOrchestrator {
7474
let _ = responder.send(Ok(PullResponse {
7575
image_id: id.clone(),
7676
rootfs_path: info.rootfs_path.clone(),
77+
config: info.config.clone(),
7778
}));
7879
return;
7980
}
@@ -93,6 +94,7 @@ impl ImageOrchestrator {
9394
image_ref: image_ref.clone(),
9495
rootfs_path: String::new(),
9596
state: ImageState::Downloading,
97+
config: ImageConfig::default(),
9698
},
9799
);
98100

@@ -109,9 +111,13 @@ impl ImageOrchestrator {
109111
}
110112
};
111113

112-
// Update state to extracting
114+
// Parse image config (Entrypoint, Cmd, Env, etc.)
115+
let image_config = parse_image_config(&image_data.config);
116+
117+
// Update state to extracting and store config
113118
if let Some(info) = self.store.get_mut(&image_id) {
114119
info.state = ImageState::Extracting;
120+
info.config = image_config.clone();
115121
}
116122

117123
// Store the image
@@ -144,6 +150,7 @@ impl ImageOrchestrator {
144150
let _ = responder.send(Ok(PullResponse {
145151
image_id,
146152
rootfs_path,
153+
config: image_config,
147154
}));
148155
}
149156
Ok(Err(e)) => {

0 commit comments

Comments
 (0)