mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-05-06 07:27:28 +00:00
tools: Add agent-ctl tool
Add a low-level agent control tool that can manipulate the agent via ttRPC. Fixes: #222. Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
This commit is contained in:
parent
2e53d237ce
commit
8a1949546c
37
tools/agent-ctl/Cargo.toml
Normal file
37
tools/agent-ctl/Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
[package]
|
||||
name = "kata-agent-ctl"
|
||||
version = "0.0.1"
|
||||
authors = ["James O. D. Hunt <james.o.hunt@intel.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
protocols = { path = "../../src/agent/protocols" }
|
||||
rustjail = { path = "../../src/agent/rustjail" }
|
||||
oci = { path = "../../src/agent/oci" }
|
||||
|
||||
clap = "2.33.0"
|
||||
lazy_static = "1.4.0"
|
||||
anyhow = "1.0.31"
|
||||
|
||||
logging = { path = "../../pkg/logging" }
|
||||
slog = "2.5.2"
|
||||
slog-scope = "4.3.0"
|
||||
rand = "0.7.3"
|
||||
protobuf = "2.14.0"
|
||||
|
||||
nix = "0.17.0"
|
||||
libc = "0.2.69"
|
||||
# XXX: Must be the same as the version used by the agent
|
||||
ttrpc = { git = "https://github.com/containerd/ttrpc-rust", branch="0.3.0" }
|
||||
|
||||
# For parsing timeouts
|
||||
humantime = "2.0.0"
|
||||
|
||||
# For Options (state passing)
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
serde_json = "1.0.53"
|
16
tools/agent-ctl/Makefile
Normal file
16
tools/agent-ctl/Makefile
Normal file
@ -0,0 +1,16 @@
|
||||
# Copyright (c) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
default: build
|
||||
|
||||
build:
|
||||
cargo build -v
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
.PHONY: \
|
||||
build \
|
||||
clean
|
39
tools/agent-ctl/README.md
Normal file
39
tools/agent-ctl/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Agent Control tool
|
||||
|
||||
* [Overview](#overview)
|
||||
* [Audience and environment](#audience-and-environment)
|
||||
* [Full details](#full-details)
|
||||
|
||||
## Overview
|
||||
|
||||
The Kata Containers agent control tool (`kata-agent-ctl`) is a low-level test
|
||||
tool. It allows basic interaction with the Kata Containers agent,
|
||||
`kata-agent`, that runs inside the virtual machine.
|
||||
|
||||
Unlike the Kata Runtime, which only ever makes sequences of correctly ordered
|
||||
and valid agent API calls, this tool allows users to make arbitrary agent API
|
||||
calls and to control their parameters.
|
||||
|
||||
## Audience and environment
|
||||
|
||||
> **Warning:**
|
||||
>
|
||||
> This tool is for *advanced* users familiar with the low-level agent API calls.
|
||||
> Further, it is designed to be run on test and development systems **only**: since
|
||||
> the tool can make arbitrary API calls, it is possible to easily confuse
|
||||
> irrevocably other parts of the system or even kill a running container or
|
||||
> sandbox.
|
||||
|
||||
## Full details
|
||||
|
||||
For a usage statement, run:
|
||||
|
||||
```sh
|
||||
$ cargo run -- --help
|
||||
```
|
||||
|
||||
To see some examples, run:
|
||||
|
||||
```sh
|
||||
$ cargo run -- examples
|
||||
```
|
1149
tools/agent-ctl/src/client.rs
Normal file
1149
tools/agent-ctl/src/client.rs
Normal file
File diff suppressed because it is too large
Load Diff
292
tools/agent-ctl/src/main.rs
Normal file
292
tools/agent-ctl/src/main.rs
Normal file
@ -0,0 +1,292 @@
|
||||
// Copyright (c) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::{crate_name, crate_version, App, Arg, SubCommand};
|
||||
use std::io;
|
||||
use std::process::exit;
|
||||
|
||||
// Convenience macro to obtain the scope logger
|
||||
#[macro_export]
|
||||
macro_rules! sl {
|
||||
() => {
|
||||
slog_scope::logger()
|
||||
};
|
||||
}
|
||||
|
||||
mod client;
|
||||
mod rpc;
|
||||
mod types;
|
||||
mod utils;
|
||||
|
||||
const DEFAULT_LOG_LEVEL: slog::Level = slog::Level::Info;
|
||||
|
||||
const DESCRIPTION_TEXT: &str = r#"DESCRIPTION:
|
||||
Low-level test tool that allows basic interaction with
|
||||
the Kata Containers agent using agent API calls."#;
|
||||
|
||||
const ABOUT_TEXT: &str = "Kata Containers agent tool";
|
||||
|
||||
const WARNING_TEXT: &str = r#"WARNING:
|
||||
This tool is for *advanced* users familiar with the low-level agent API calls.
|
||||
Further, it is designed to be run on test and development systems **only**:
|
||||
since the tool can make arbitrary API calls, it is possible to easily confuse
|
||||
irrevocably other parts of the system or even kill a running container or
|
||||
sandbox."#;
|
||||
|
||||
fn make_examples_text(program_name: &str) -> String {
|
||||
let bundle = "$bundle_dir";
|
||||
let cid = 3;
|
||||
let container_id = "$container_id";
|
||||
let config_file_uri = "file:///tmp/config.json";
|
||||
let port = 1024;
|
||||
let sandbox_id = "$sandbox_id";
|
||||
|
||||
format!(
|
||||
r#"EXAMPLES:
|
||||
|
||||
- Check if the agent is running:
|
||||
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd Check
|
||||
|
||||
- Query the agent environment:
|
||||
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd GuestDetails
|
||||
|
||||
- List all available (built-in and Kata Agent API) commands:
|
||||
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd list
|
||||
|
||||
- Generate a random container ID:
|
||||
|
||||
$ {program} generate-cid
|
||||
|
||||
- Generate a random sandbox ID:
|
||||
|
||||
$ {program} generate-sid
|
||||
|
||||
- Attempt to create 7 sandboxes, ignoring any errors:
|
||||
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --repeat 7 --cmd CreateSandbox
|
||||
|
||||
- Query guest details forever:
|
||||
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --repeat -1 --cmd GuestDetails
|
||||
|
||||
- Send a 'SIGUSR1' signal to a container process:
|
||||
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd 'SignalProcess signal=usr1 sid={sandbox_id} cid={container_id}'
|
||||
|
||||
- Create a sandbox with a single container, and then destroy everything:
|
||||
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd CreateSandbox
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --bundle-dir {bundle:?} --cmd CreateContainer
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd DestroySandbox
|
||||
|
||||
- Create a Container using a custom configuration file:
|
||||
|
||||
$ {program} connect --vsock-cid {cid} --vsock-port {port} --bundle-dir {bundle:?} --cmd 'CreateContainer spec={config_file_uri}'
|
||||
"#,
|
||||
bundle = bundle,
|
||||
cid = cid,
|
||||
config_file_uri = config_file_uri,
|
||||
container_id = container_id,
|
||||
port = port,
|
||||
program = program_name,
|
||||
sandbox_id = sandbox_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> {
|
||||
let args = global_args
|
||||
.subcommand_matches("connect")
|
||||
.ok_or("BUG: missing sub-command arguments".to_string())
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
|
||||
let interactive = args.is_present("interactive");
|
||||
let ignore_errors = args.is_present("ignore-errors");
|
||||
|
||||
let cid_str = args
|
||||
.value_of("vsock-cid")
|
||||
.ok_or("need VSOCK cid".to_string())
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
|
||||
let port_str = args
|
||||
.value_of("vsock-port")
|
||||
.ok_or("need VSOCK port number".to_string())
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
|
||||
let cid: u32 = cid_str
|
||||
.parse::<u32>()
|
||||
.map_err(|e| anyhow!(format!("invalid VSOCK CID number: {}", e.to_string())))?;
|
||||
|
||||
let port: u32 = port_str
|
||||
.parse::<u32>()
|
||||
.map_err(|e| anyhow!(format!("invalid VSOCK port number: {}", e)))?;
|
||||
|
||||
let mut commands: Vec<&str> = Vec::new();
|
||||
|
||||
if !interactive {
|
||||
commands = args
|
||||
.values_of("cmd")
|
||||
.ok_or("need commands to send to the server".to_string())
|
||||
.map_err(|e| anyhow!(e))?
|
||||
.collect();
|
||||
}
|
||||
|
||||
// Cannot fail as a default has been specified
|
||||
let log_level_name = global_args.value_of("log-level").unwrap();
|
||||
|
||||
let log_level = logging::level_name_to_slog_level(log_level_name).map_err(|e| anyhow!(e))?;
|
||||
|
||||
let writer = io::stdout();
|
||||
let logger = logging::create_logger(name, crate_name!(), log_level, writer);
|
||||
|
||||
let timeout_nano: i64 = match args.value_of("timeout") {
|
||||
Some(t) => utils::human_time_to_ns(t).map_err(|e| e)?,
|
||||
None => 0,
|
||||
};
|
||||
|
||||
let bundle_dir = args.value_of("bundle-dir").unwrap_or("");
|
||||
|
||||
let result = rpc::run(
|
||||
&logger,
|
||||
cid,
|
||||
port,
|
||||
bundle_dir,
|
||||
interactive,
|
||||
ignore_errors,
|
||||
timeout_nano,
|
||||
commands,
|
||||
);
|
||||
if result.is_err() {
|
||||
return result;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn real_main() -> Result<()> {
|
||||
let name = crate_name!();
|
||||
|
||||
let app = App::new(name)
|
||||
.version(crate_version!())
|
||||
.about(ABOUT_TEXT)
|
||||
.long_about(DESCRIPTION_TEXT)
|
||||
.after_help(WARNING_TEXT)
|
||||
.arg(
|
||||
Arg::with_name("log-level")
|
||||
.long("log-level")
|
||||
.short("l")
|
||||
.help("specific log level")
|
||||
.default_value(logging::slog_level_to_level_name(DEFAULT_LOG_LEVEL).unwrap())
|
||||
.possible_values(&logging::get_log_levels())
|
||||
.takes_value(true)
|
||||
.required(false),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("connect")
|
||||
.about("Connect to agent")
|
||||
.after_help(WARNING_TEXT)
|
||||
.arg(
|
||||
Arg::with_name("bundle-dir")
|
||||
.long("bundle-dir")
|
||||
.help("OCI bundle directory")
|
||||
.takes_value(true)
|
||||
.value_name("directory"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("vsock-cid")
|
||||
.long("vsock-cid")
|
||||
.help("VSOCK Context ID")
|
||||
.takes_value(true)
|
||||
.value_name("CID"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("cmd")
|
||||
.long("cmd")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.help("API command (with optional arguments) to send to the server"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore-errors")
|
||||
.long("ignore-errors")
|
||||
.help("Don't exit on first error"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("interactive")
|
||||
.short("i")
|
||||
.long("interactive")
|
||||
.help("Allow interactive client"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("vsock-port")
|
||||
.long("vsock-port")
|
||||
.help("VSOCK Port number")
|
||||
.takes_value(true)
|
||||
.value_name("port-number"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("timeout")
|
||||
.long("timeout")
|
||||
.help("timeout value as nanoseconds or using human-readable suffixes (0 [forever], 99ns, 30us, 2ms, 5s, 7m, etc)")
|
||||
.takes_value(true)
|
||||
.value_name("human-time"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("generate-cid")
|
||||
.about("Create a random container ID")
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("generate-sid")
|
||||
.about("Create a random sandbox ID")
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("examples")
|
||||
.about("Show usage examples")
|
||||
);
|
||||
|
||||
let args = app.get_matches();
|
||||
|
||||
let subcmd = args
|
||||
.subcommand_name()
|
||||
.ok_or("need sub-command".to_string())
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
|
||||
match subcmd {
|
||||
"generate-cid" => {
|
||||
println!("{}", utils::random_container_id());
|
||||
return Ok(());
|
||||
}
|
||||
"generate-sid" => {
|
||||
println!("{}", utils::random_sandbox_id());
|
||||
return Ok(());
|
||||
}
|
||||
"examples" => {
|
||||
println!("{}", make_examples_text(name));
|
||||
return Ok(());
|
||||
}
|
||||
"connect" => {
|
||||
return connect(name, args);
|
||||
}
|
||||
_ => return Err(anyhow!(format!("invalid sub-command: {:?}", subcmd))),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match real_main() {
|
||||
Err(e) => {
|
||||
eprintln!("ERROR: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
37
tools/agent-ctl/src/rpc.rs
Normal file
37
tools/agent-ctl/src/rpc.rs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Description: ttRPC logic entry point
|
||||
|
||||
use anyhow::Result;
|
||||
use slog::{o, Logger};
|
||||
|
||||
use crate::client::client;
|
||||
use crate::types::Config;
|
||||
|
||||
pub fn run(
|
||||
logger: &Logger,
|
||||
cid: u32,
|
||||
port: u32,
|
||||
bundle_dir: &str,
|
||||
interactive: bool,
|
||||
ignore_errors: bool,
|
||||
timeout_nano: i64,
|
||||
commands: Vec<&str>,
|
||||
) -> Result<()> {
|
||||
let cfg = Config {
|
||||
cid: cid,
|
||||
port: port,
|
||||
bundle_dir: bundle_dir.to_string(),
|
||||
timeout_nano: timeout_nano,
|
||||
interactive: interactive,
|
||||
ignore_errors: ignore_errors,
|
||||
};
|
||||
|
||||
// Maintain the global logger for the duration of the ttRPC comms
|
||||
let _guard = slog_scope::set_global_logger(logger.new(o!("subsystem" => "rpc")));
|
||||
|
||||
client(&cfg, commands)
|
||||
}
|
20
tools/agent-ctl/src/types.rs
Normal file
20
tools/agent-ctl/src/types.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Type used to pass optional state between cooperating API calls.
|
||||
pub type Options = HashMap<String, String>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub cid: u32,
|
||||
pub port: u32,
|
||||
pub bundle_dir: String,
|
||||
pub timeout_nano: i64,
|
||||
pub interactive: bool,
|
||||
pub ignore_errors: bool,
|
||||
}
|
411
tools/agent-ctl/src/utils.rs
Normal file
411
tools/agent-ctl/src/utils.rs
Normal file
@ -0,0 +1,411 @@
|
||||
// Copyright (c) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use crate::types::{Config, Options};
|
||||
use anyhow::{anyhow, Result};
|
||||
use oci::{Process as ociProcess, Root as ociRoot, Spec as ociSpec};
|
||||
use protocols::oci::{
|
||||
Box as grpcBox, Linux as grpcLinux, LinuxCapabilities as grpcLinuxCapabilities,
|
||||
POSIXRlimit as grpcPOSIXRlimit, Process as grpcProcess, Root as grpcRoot, Spec as grpcSpec,
|
||||
User as grpcUser,
|
||||
};
|
||||
use rand::Rng;
|
||||
use slog::{debug, warn};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// Length of a sandbox identifier
|
||||
const SANDBOX_ID_LEN: u8 = 64;
|
||||
|
||||
const FILE_URI: &str = "file://";
|
||||
|
||||
// Length of the guests hostname
|
||||
const MIN_HOSTNAME_LEN: u8 = 8;
|
||||
|
||||
// Name of the OCI configuration file found at the root of an OCI bundle.
|
||||
const CONFIG_FILE: &str = "config.json";
|
||||
|
||||
lazy_static! {
|
||||
// Create a mutable hash map statically
|
||||
static ref SIGNALS: Arc<Mutex<HashMap<&'static str, u8>>> = {
|
||||
|
||||
let mut m: HashMap<&'static str, u8> = HashMap::new();
|
||||
|
||||
m.insert("SIGHUP", 1);
|
||||
m.insert("SIGINT", 2);
|
||||
m.insert("SIGQUIT", 3);
|
||||
m.insert("SIGILL", 4);
|
||||
m.insert("SIGTRAP", 5);
|
||||
m.insert("SIGABRT", 6);
|
||||
m.insert("SIGBUS", 7);
|
||||
m.insert("SIGFPE", 8);
|
||||
m.insert("SIGKILL", 9);
|
||||
m.insert("SIGUSR1", 10);
|
||||
m.insert("SIGSEGV", 11);
|
||||
m.insert("SIGUSR2", 12);
|
||||
m.insert("SIGPIPE", 13);
|
||||
m.insert("SIGALRM", 14);
|
||||
m.insert("SIGTERM", 15);
|
||||
m.insert("SIGSTKFLT", 16);
|
||||
|
||||
// XXX:
|
||||
m.insert("SIGCHLD", 17);
|
||||
m.insert("SIGCLD", 17);
|
||||
|
||||
m.insert("SIGCONT", 18);
|
||||
m.insert("SIGSTOP", 19);
|
||||
m.insert("SIGTSTP", 20);
|
||||
m.insert("SIGTTIN", 21);
|
||||
m.insert("SIGTTOU", 22);
|
||||
m.insert("SIGURG", 23);
|
||||
m.insert("SIGXCPU", 24);
|
||||
m.insert("SIGXFSZ", 25);
|
||||
m.insert("SIGVTALRM", 26);
|
||||
m.insert("SIGPROF", 27);
|
||||
m.insert("SIGWINCH", 28);
|
||||
m.insert("SIGIO", 29);
|
||||
m.insert("SIGPWR", 30);
|
||||
m.insert("SIGSYS", 31);
|
||||
|
||||
Arc::new(Mutex::new(m))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn signame_to_signum(name: &str) -> Result<u8> {
|
||||
if name == "" {
|
||||
return Err(anyhow!("invalid signal"));
|
||||
}
|
||||
|
||||
match name.parse::<u8>() {
|
||||
Ok(n) => return Ok(n),
|
||||
|
||||
// "fall through" on error as we assume the name is not a number, but
|
||||
// a signal name.
|
||||
Err(_) => (),
|
||||
}
|
||||
|
||||
let mut search_term: String;
|
||||
|
||||
if name.starts_with("SIG") {
|
||||
search_term = name.to_string();
|
||||
} else {
|
||||
search_term = format!("SIG{}", name);
|
||||
}
|
||||
|
||||
search_term = search_term.to_uppercase();
|
||||
|
||||
// Access the hashmap
|
||||
let signals_ref = SIGNALS.clone();
|
||||
let m = signals_ref.lock().unwrap();
|
||||
|
||||
match m.get(&*search_term) {
|
||||
Some(value) => Ok(*value),
|
||||
None => Err(anyhow!(format!("invalid signal name: {:?}", name))),
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a human time fornat (like "2s") into the equivalent number
|
||||
// of nano seconds.
|
||||
pub fn human_time_to_ns(human_time: &str) -> Result<i64> {
|
||||
if human_time == "" || human_time == "0" {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let d: humantime::Duration = human_time
|
||||
.parse::<humantime::Duration>()
|
||||
.map_err(|e| anyhow!(e))?
|
||||
.into();
|
||||
|
||||
Ok(d.as_nanos() as i64)
|
||||
}
|
||||
|
||||
// Look up the specified option name and return its value.
|
||||
//
|
||||
// - The function looks for the appropriate option value in the specified
|
||||
// 'args' first.
|
||||
// - 'args' is assumed to be a space-separated set of "name=value" pairs).
|
||||
// - If not found in the args, the function looks in the global options hash.
|
||||
// - If found in neither location, certain well-known options are auto-generated.
|
||||
// - All other options values default to an empty string.
|
||||
// - All options are saved in the global hash before being returned for future
|
||||
// use.
|
||||
pub fn get_option(name: &str, options: &mut Options, args: &str) -> String {
|
||||
let words: Vec<&str> = args.split_whitespace().collect();
|
||||
|
||||
for word in words {
|
||||
let fields: Vec<String> = word.split("=").map(|s| s.to_string()).collect();
|
||||
|
||||
if fields.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if fields[0] == "" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let key = fields[0].clone();
|
||||
|
||||
let mut value = fields[1..].join("=");
|
||||
|
||||
// Expand "spec=file:///some/where/config.json"
|
||||
if key == "spec" && value.starts_with(FILE_URI) {
|
||||
let spec_file = match uri_to_filename(&value) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
warn!(sl!(), "failed to handle spec file URI: {:}", e);
|
||||
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
if spec_file != "" {
|
||||
value = match spec_file_to_string(spec_file) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!(sl!(), "failed to load spec file: {:}", e);
|
||||
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Command args take priority over any previous value,
|
||||
// so update the global set of options for this and all
|
||||
// subsequent commands.
|
||||
options.insert(key, value);
|
||||
}
|
||||
|
||||
// Explains briefly how the option value was determined
|
||||
let mut msg = "cached";
|
||||
|
||||
// If the option exists in the hash, return it
|
||||
if let Some(value) = options.get(name) {
|
||||
debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg);
|
||||
|
||||
return value.to_string();
|
||||
}
|
||||
|
||||
msg = "generated";
|
||||
|
||||
// Handle option values that can be auto-generated
|
||||
let value = match name {
|
||||
"cid" => random_container_id(),
|
||||
"sid" => random_sandbox_id(),
|
||||
|
||||
// Default to CID
|
||||
"exec_id" => {
|
||||
msg = "derived";
|
||||
//derived = true;
|
||||
|
||||
match options.get("cid") {
|
||||
Some(value) => value.to_string(),
|
||||
None => "".to_string(),
|
||||
}
|
||||
}
|
||||
_ => "".to_string(),
|
||||
};
|
||||
|
||||
debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg);
|
||||
|
||||
// Store auto-generated value
|
||||
options.insert(name.to_string(), value.to_string());
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
pub fn generate_random_hex_string(len: u32) -> String {
|
||||
const CHARSET: &[u8] = b"abcdef0123456789";
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let str: String = (0..len)
|
||||
.map(|_| {
|
||||
let idx = rng.gen_range(0, CHARSET.len());
|
||||
CHARSET[idx] as char
|
||||
})
|
||||
.collect();
|
||||
|
||||
str
|
||||
}
|
||||
|
||||
pub fn random_sandbox_id() -> String {
|
||||
generate_random_hex_string(SANDBOX_ID_LEN as u32)
|
||||
}
|
||||
|
||||
pub fn random_container_id() -> String {
|
||||
// Containers and sandboxes have same ID types
|
||||
random_sandbox_id()
|
||||
}
|
||||
|
||||
fn config_file_from_bundle_dir(bundle_dir: &str) -> Result<String> {
|
||||
if bundle_dir == "" {
|
||||
return Err(anyhow!("missing bundle directory"));
|
||||
}
|
||||
|
||||
let config_path = PathBuf::from(&bundle_dir).join(CONFIG_FILE);
|
||||
|
||||
config_path
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|e| anyhow!(format!("failed to construct config file path: {:?}", e)))
|
||||
}
|
||||
|
||||
fn root_oci_to_grpc(bundle_dir: &str, root: &ociRoot) -> Result<grpcRoot> {
|
||||
let root_dir = root.path.clone();
|
||||
|
||||
let path = if root_dir.starts_with("/") {
|
||||
root_dir.clone()
|
||||
} else {
|
||||
// Expand the root directory into an absolute value
|
||||
let abs_root_dir = PathBuf::from(&bundle_dir).join(&root_dir);
|
||||
|
||||
abs_root_dir
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|e| anyhow!(format!("failed to construct bundle path: {:?}", e)))?
|
||||
};
|
||||
|
||||
let grpc_root = grpcRoot {
|
||||
Path: path,
|
||||
Readonly: root.readonly,
|
||||
unknown_fields: protobuf::UnknownFields::new(),
|
||||
cached_size: protobuf::CachedSize::default(),
|
||||
};
|
||||
|
||||
Ok(grpc_root)
|
||||
}
|
||||
|
||||
fn process_oci_to_grpc(p: &ociProcess) -> grpcProcess {
|
||||
let console_size = match &p.console_size {
|
||||
Some(s) => {
|
||||
let mut b = grpcBox::new();
|
||||
|
||||
b.set_Width(s.width);
|
||||
b.set_Height(s.height);
|
||||
|
||||
protobuf::SingularPtrField::some(b)
|
||||
}
|
||||
None => protobuf::SingularPtrField::none(),
|
||||
};
|
||||
|
||||
let oom_score_adj: i64 = match p.oom_score_adj {
|
||||
Some(s) => s.into(),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
let mut user = grpcUser::new();
|
||||
user.set_UID(p.user.uid);
|
||||
user.set_GID(p.user.gid);
|
||||
user.set_AdditionalGids(p.user.additional_gids.clone());
|
||||
|
||||
// FIXME: Implement RLimits OCI spec handling (copy from p.rlimits)
|
||||
//let rlimits = vec![grpcPOSIXRlimit::new()];
|
||||
let rlimits = protobuf::RepeatedField::new();
|
||||
|
||||
// FIXME: Implement Capabilities OCI spec handling (copy from p.capabilities)
|
||||
let capabilities = grpcLinuxCapabilities::new();
|
||||
|
||||
// FIXME: Implement Env OCI spec handling (copy from p.env)
|
||||
let env = protobuf::RepeatedField::new();
|
||||
|
||||
grpcProcess {
|
||||
Terminal: p.terminal,
|
||||
ConsoleSize: console_size,
|
||||
User: protobuf::SingularPtrField::some(user),
|
||||
Args: protobuf::RepeatedField::from_vec(p.args.clone()),
|
||||
Env: env,
|
||||
Cwd: p.cwd.clone(),
|
||||
Capabilities: protobuf::SingularPtrField::some(capabilities),
|
||||
Rlimits: rlimits,
|
||||
NoNewPrivileges: p.no_new_privileges,
|
||||
ApparmorProfile: p.apparmor_profile.clone(),
|
||||
OOMScoreAdj: oom_score_adj,
|
||||
SelinuxLabel: p.selinux_label.clone(),
|
||||
unknown_fields: protobuf::UnknownFields::new(),
|
||||
cached_size: protobuf::CachedSize::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn oci_to_grpc(bundle_dir: &str, cid: &str, oci: &ociSpec) -> Result<grpcSpec> {
|
||||
let process = match &oci.process {
|
||||
Some(p) => protobuf::SingularPtrField::some(process_oci_to_grpc(&p)),
|
||||
None => protobuf::SingularPtrField::none(),
|
||||
};
|
||||
|
||||
let root = match &oci.root {
|
||||
Some(r) => {
|
||||
let grpc_root = root_oci_to_grpc(bundle_dir, &r).map_err(|e| e)?;
|
||||
|
||||
protobuf::SingularPtrField::some(grpc_root)
|
||||
}
|
||||
None => protobuf::SingularPtrField::none(),
|
||||
};
|
||||
|
||||
// FIXME: Implement Linux OCI spec handling
|
||||
let linux = grpcLinux::new();
|
||||
|
||||
if cid.len() < MIN_HOSTNAME_LEN as usize {
|
||||
return Err(anyhow!("container ID too short for hostname"));
|
||||
}
|
||||
|
||||
// FIXME: Implement setting a custom (and unique!) hostname (requires uts ns setup)
|
||||
//let hostname = cid[0..MIN_HOSTNAME_LEN as usize].to_string();
|
||||
let hostname = "".to_string();
|
||||
|
||||
let grpc_spec = grpcSpec {
|
||||
Version: oci.version.clone(),
|
||||
Process: process,
|
||||
Root: root,
|
||||
Hostname: hostname,
|
||||
Mounts: protobuf::RepeatedField::new(),
|
||||
Hooks: protobuf::SingularPtrField::none(),
|
||||
Annotations: HashMap::new(),
|
||||
Linux: protobuf::SingularPtrField::some(linux),
|
||||
Solaris: protobuf::SingularPtrField::none(),
|
||||
Windows: protobuf::SingularPtrField::none(),
|
||||
unknown_fields: protobuf::UnknownFields::new(),
|
||||
cached_size: protobuf::CachedSize::default(),
|
||||
};
|
||||
|
||||
Ok(grpc_spec)
|
||||
}
|
||||
|
||||
fn uri_to_filename(uri: &str) -> Result<String> {
|
||||
if !uri.starts_with(FILE_URI) {
|
||||
return Err(anyhow!(format!("invalid URI: {:?}", uri)));
|
||||
}
|
||||
|
||||
let fields: Vec<&str> = uri.split(FILE_URI).collect();
|
||||
|
||||
if fields.len() != 2 {
|
||||
return Err(anyhow!(format!("invalid URI: {:?}", uri)));
|
||||
}
|
||||
|
||||
Ok(fields[1].to_string())
|
||||
}
|
||||
|
||||
pub fn spec_file_to_string(spec_file: String) -> Result<String> {
|
||||
let oci_spec = ociSpec::load(&spec_file).map_err(|e| anyhow!(e))?;
|
||||
|
||||
serde_json::to_string(&oci_spec).map_err(|e| anyhow!(e))
|
||||
}
|
||||
|
||||
pub fn get_oci_spec_json(cfg: &Config) -> Result<String> {
|
||||
let spec_file = config_file_from_bundle_dir(&cfg.bundle_dir)?;
|
||||
|
||||
spec_file_to_string(spec_file)
|
||||
}
|
||||
|
||||
pub fn get_grpc_spec(options: &mut Options, cid: &str) -> Result<grpcSpec> {
|
||||
let bundle_dir = get_option("bundle-dir", options, "");
|
||||
|
||||
let json_spec = get_option("spec", options, "");
|
||||
assert_ne!(json_spec, "");
|
||||
|
||||
let oci_spec: ociSpec = serde_json::from_str(&json_spec).map_err(|e| anyhow!(e))?;
|
||||
|
||||
Ok(oci_to_grpc(&bundle_dir, cid, &oci_spec)?)
|
||||
}
|
Loading…
Reference in New Issue
Block a user