agent: Add PullImage endpoint

This commit adds the PullImge endpoint to the agent
and the agent-ctl command to test it.

Fixes: #2509

Signed-off-by: Georgina Kinge <georgina.kinge@ibm.com>
Co-authored-by: stevenhorsman <steven@uk.ibm.com>
This commit is contained in:
Georgina Kinge 2021-08-25 18:07:08 +01:00 committed by stevenhorsman
parent c9e6efb1e1
commit af44b7a591
5 changed files with 130 additions and 2 deletions

View File

@ -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;
}

View File

@ -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<protocols::empty::Empty> {
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::*;

View File

@ -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

View File

@ -30,7 +30,7 @@ RUN dnf -y update && dnf install -y \
m4 \
make \
pkgconfig \
redhat-release \
fedora-release-common \
sed \
systemd \
tar \

View File

@ -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"