diff --git a/src/agent/protocols/protos/agent.proto b/src/agent/protocols/protos/agent.proto index c29ef7ebc4..4775411b75 100644 --- a/src/agent/protocols/protos/agent.proto +++ b/src/agent/protocols/protos/agent.proto @@ -67,6 +67,7 @@ service AgentService { rpc CopyFile(CopyFileRequest) returns (google.protobuf.Empty); rpc GetOOMEvent(GetOOMEventRequest) returns (OOMEvent); rpc AddSwap(AddSwapRequest) returns (google.protobuf.Empty); + rpc PullImage(PullImageRequest) returns (google.protobuf.Empty); } message CreateContainerRequest { @@ -513,3 +514,8 @@ message GetMetricsRequest {} message Metrics { string metrics = 1; } + +message PullImageRequest { + string image = 1; + string container_id = 2; +} diff --git a/src/agent/src/rpc.rs b/src/agent/src/rpc.rs index 424da5df09..18e072bd64 100644 --- a/src/agent/src/rpc.rs +++ b/src/agent/src/rpc.rs @@ -11,6 +11,7 @@ use tokio::sync::Mutex; use std::ffi::CString; use std::io; use std::path::Path; +use std::process::ExitStatus; use std::sync::Arc; use ttrpc::{ self, @@ -78,6 +79,8 @@ use std::path::PathBuf; const CONTAINER_BASE: &str = "/run/kata-containers"; const MODPROBE_PATH: &str = "/sbin/modprobe"; +const SKOPEO_PATH: &str = "/usr/bin/skopeo"; +const UMOCI_PATH: &str = "/usr/local/bin/umoci"; // Convenience macro to obtain the scope logger macro_rules! sl { @@ -675,6 +678,21 @@ impl protocols::agent_ttrpc::AgentService for AgentService { .map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e.to_string())) } + async fn pull_image( + &self, + _ctx: &TtrpcContext, + req: protocols::agent::PullImageRequest, + ) -> ttrpc::Result { + let image = req.get_image(); + let cid = req.get_container_id(); + + pull_image_from_registry(image, cid) + .map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e.to_string()))?; + unpack_image(cid).map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e.to_string()))?; + + Ok(Empty::new()) + } + async fn pause_container( &self, ctx: &TtrpcContext, @@ -1697,6 +1715,76 @@ fn load_kernel_module(module: &protocols::agent::KernelModule) -> Result<()> { } } +fn pull_image_from_registry(image: &str, cid: &str) -> Result<()> { + let source_image = format!("{}{}", "docker://", image); + + let manifest_path = format!("/tmp/{}/image_manifest", cid); + let target_path_manifest = format!("dir://{}", manifest_path); + + // Define the target transport and path for the OCI image, without signature + let oci_path = format!("/tmp/{}/image_oci:latest", cid); + let target_path_oci = format!("oci://{}", oci_path); + + fs::create_dir_all(&manifest_path)?; + fs::create_dir_all(&oci_path)?; + + let status: ExitStatus = Command::new(SKOPEO_PATH) + .arg("copy") + .arg(source_image) + .arg(&target_path_manifest) + .status()?; + + if !status.success() { + return Err(anyhow!(format!("failed to pull image: {:?}", status))); + } + + // Copy image from one local file-system to another + // Resulting image is still stored in manifest format, but no longer includes the signature + // The image with a signature can then be unpacked into a bundle + let status: ExitStatus = Command::new(SKOPEO_PATH) + .arg("copy") + .arg(&target_path_manifest) + .arg(&target_path_oci) + .arg("--remove-signatures") + .status()?; + + if !status.success() { + return Err(anyhow!(format!("failed to copy image: {:?}", status))); + } + + // To save space delete the manifest. + // TODO LATER - when verify image is added, this will need moving the end of that, if required + fs::remove_dir_all(&manifest_path)?; + Ok(()) +} + +fn unpack_image(cid: &str) -> Result<()> { + let source_path_oci = format!("/tmp/{}/image_oci:latest", cid); + let target_path_bundle = format!("{}{}{}", CONTAINER_BASE, "/", cid); + + info!(sl!(), "cid is {:?}", cid); + info!(sl!(), "target_path_bundle is {:?}", target_path_bundle); + + // Unpack image + + let status: ExitStatus = Command::new(UMOCI_PATH) + .arg("--verbose") + .arg("unpack") + .arg("--image") + .arg(&source_path_oci) + .arg(&target_path_bundle) + .status()?; + + if !status.success() { + return Err(anyhow!(format!("failed to unpack image: {:?}", status))); + } + + // To save space delete the oci image after unpack + fs::remove_dir_all(&source_path_oci)?; + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/tools/agent-ctl/src/client.rs b/tools/agent-ctl/src/client.rs index 3359dde879..5ff801278b 100644 --- a/tools/agent-ctl/src/client.rs +++ b/tools/agent-ctl/src/client.rs @@ -258,6 +258,11 @@ static AGENT_CMDS: &'static [AgentCmd] = &[ st: ServiceType::Agent, fp: agent_cmd_container_write_stdin, }, + AgentCmd { + name: "PullImage", + st: ServiceType::Agent, + fp: agent_cmd_pull_image, + }, ]; static BUILTIN_CMDS: &'static [BuiltinCmd] = &[ @@ -1933,6 +1938,35 @@ fn agent_cmd_sandbox_mem_hotplug_by_probe( Ok(()) } +fn agent_cmd_pull_image( + ctx: &Context, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = PullImageRequest::default(); + + let ctx = clone_context(ctx); + + let image = utils::get_option("image", options, args); + let cid = utils::get_option("cid", options, args); + + req.set_image(image); + req.set_container_id(cid); + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .pull_image(ctx, &req) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + #[inline] fn builtin_cmd_repeat(_args: &str) -> (Result<()>, bool) { // XXX: NOP implementation. Due to the way repeat has to work, providing a diff --git a/tools/osbuilder/rootfs-builder/fedora/Dockerfile.in b/tools/osbuilder/rootfs-builder/fedora/Dockerfile.in index dac32f5050..3cacbe9258 100644 --- a/tools/osbuilder/rootfs-builder/fedora/Dockerfile.in +++ b/tools/osbuilder/rootfs-builder/fedora/Dockerfile.in @@ -30,7 +30,7 @@ RUN dnf -y update && dnf install -y \ m4 \ make \ pkgconfig \ - redhat-release \ + fedora-release-common \ sed \ systemd \ tar \ diff --git a/tools/osbuilder/rootfs-builder/fedora/config.sh b/tools/osbuilder/rootfs-builder/fedora/config.sh index 3e5e9a13f3..60a38fed41 100644 --- a/tools/osbuilder/rootfs-builder/fedora/config.sh +++ b/tools/osbuilder/rootfs-builder/fedora/config.sh @@ -5,7 +5,7 @@ OS_NAME="Fedora" -OS_VERSION=${OS_VERSION:-30} +OS_VERSION=${OS_VERSION:-34} MIRROR_LIST="https://mirrors.fedoraproject.org/metalink?repo=fedora-${OS_VERSION}&arch=\$basearch"