commit
73d1dfa16d
|
|
@ -16,7 +16,12 @@ jobs:
|
|||
- run: cargo check --examples --tests --all-targets
|
||||
- run: cargo fmt --all -- --check --files-with-diff
|
||||
- run: cargo clippy --all-targets --all-features -- -D warnings
|
||||
- run: cargo test --all-features
|
||||
# runc::tests::test_exec needs $XDG_RUNTIME_DIR to be set
|
||||
- env:
|
||||
XDG_RUNTIME_DIR: /tmp/dummy-xdr
|
||||
run: |
|
||||
mkdir -p /tmp/dummy-xdr
|
||||
cargo test --all-features
|
||||
|
||||
deny:
|
||||
name: Deny
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@ members = [
|
|||
"crates/logging",
|
||||
"crates/shim-protos",
|
||||
"crates/shim",
|
||||
"crates/snapshots"
|
||||
"crates/snapshots",
|
||||
"crates/runc"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ async fn main() {
|
|||
|
||||
// test container output
|
||||
let actual_stdout = fs::read_to_string(stdout).expect("read stdout actual");
|
||||
assert_eq!(actual_stdout.strip_suffix("\n").unwrap(), output);
|
||||
assert_eq!(actual_stdout.strip_suffix('\n').unwrap(), output);
|
||||
|
||||
// clear stdin/stdout/stderr
|
||||
let _ = fs::remove_dir_all(tmp);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "runc"
|
||||
version = "0.1.0"
|
||||
authors = ["Yuna Tomida <ytomida.mmm@gmail.com>", "The containerd Authors"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/containerd/rust-extensions"
|
||||
keywords = ["containerd", "containers", "runc"]
|
||||
description = "A crate for consuming the runc binary in your Rust applications"
|
||||
homepage = "https://containerd.io"
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
async-trait = "0.1.52"
|
||||
futures = "0.3.19"
|
||||
libc = "0.2.112"
|
||||
log = "0.4.14"
|
||||
nix = "0.23.1"
|
||||
oci-spec = "0.5.4"
|
||||
path-absolutize = "3.0.11"
|
||||
rand = "0.8.4"
|
||||
serde = { version = "1.0.133", features = ["derive"] }
|
||||
serde_json = "1.0.74"
|
||||
tempfile = "3.3.0"
|
||||
thiserror = "1.0.30"
|
||||
time = { version = "0.3.7", features = ["serde", "std"] }
|
||||
tokio = { version = "1.15.0", features = ["full"] }
|
||||
uuid = { version = "0.8.2", features = ["v4"] }
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# Rust binding for runc client
|
||||
|
||||
A crate for consuming the runc binary in your Rust applications, similar to [go-runc](https://github.com/containerd/go-runc) for Go.
|
||||
This crate is based on archived [rust-runc](https://github.com/pwFoo/rust-runc).
|
||||
|
||||
## Usage
|
||||
Both sync/async version is available.
|
||||
You can build runc client with `RuncConfig` in method chaining style.
|
||||
Call `build()` or `build_async()` to get client.
|
||||
Note that async client depends on [tokio](https://github.com/tokio-rs/tokio), then please use it on tokio runtime.
|
||||
|
||||
```rust
|
||||
use runc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let config = runc::Config::new()
|
||||
.root("./new_root")
|
||||
.debug(false)
|
||||
.log("/path/to/logfile.json")
|
||||
.log_format(runc::LogFormat::Json)
|
||||
.rootless(true);
|
||||
|
||||
let client = config.build_async().unwrap();
|
||||
|
||||
let opts = runc::options::CreateOpts::new()
|
||||
.pid_file("/path/to/pid/file")
|
||||
.no_pivot(true);
|
||||
|
||||
client.create("container-id", "path/to/bundle", Some(&opts)).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
## Limitations
|
||||
- Supported commands are only:
|
||||
- create
|
||||
- start
|
||||
- state
|
||||
- kill
|
||||
- delete
|
||||
- Exec is **not** available in `RuncAsyncClient` now.
|
||||
- Console utilites are **not** available
|
||||
- see [Go version](https://github.com/containerd/go-runc/blob/main/console.go)
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Forked from https://github.com/pwFoo/rust-runc/blob/313e6ae5a79b54455b0a242a795c69adf035141a/src/lib.rs
|
||||
|
||||
/*
|
||||
* Copyright 2020 fsyncd, Berlin, Germany.
|
||||
* Additional material, copyright of the containerd authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::serde::timestamp;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
/// Information for runc container
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Container {
|
||||
pub id: String,
|
||||
pub pid: usize,
|
||||
pub status: String,
|
||||
pub bundle: String,
|
||||
pub rootfs: String,
|
||||
#[serde(with = "timestamp")]
|
||||
pub created: OffsetDateTime,
|
||||
pub annotations: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde_test() {
|
||||
let j = r#"
|
||||
{
|
||||
"id": "fake",
|
||||
"pid": 1000,
|
||||
"status": "RUNNING",
|
||||
"bundle": "/path/to/bundle",
|
||||
"rootfs": "/path/to/rootfs",
|
||||
"created": 1431684000,
|
||||
"annotations": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}"#;
|
||||
|
||||
let c: Container = serde_json::from_str(j).unwrap();
|
||||
assert_eq!(c.id, "fake");
|
||||
assert_eq!(c.pid, 1000);
|
||||
assert_eq!(c.status, "RUNNING");
|
||||
assert_eq!(c.bundle, "/path/to/bundle");
|
||||
assert_eq!(c.rootfs, "/path/to/rootfs");
|
||||
assert_eq!(
|
||||
c.created,
|
||||
OffsetDateTime::from_unix_timestamp(1431684000).unwrap()
|
||||
);
|
||||
assert_eq!(c.annotations.get("foo"), Some(&"bar".to_string()));
|
||||
assert_eq!(c.annotations.get("bar"), None);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Forked from https://github.com/pwFoo/rust-runc/blob/313e6ae5a79b54455b0a242a795c69adf035141a/src/lib.rs
|
||||
|
||||
/*
|
||||
* Copyright 2020 fsyncd, Berlin, Germany.
|
||||
* Additional material, copyright of the containerd authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::ExitStatus;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Unable to extract test files: {0}")]
|
||||
BundleExtractFailed(io::Error),
|
||||
|
||||
#[error("Invalid path: {0}")]
|
||||
InvalidPath(io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
JsonDeserializationFailed(#[from] serde_json::error::Error),
|
||||
|
||||
#[error("Missing container statistics")]
|
||||
MissingContainerStats,
|
||||
|
||||
#[error(transparent)]
|
||||
ProcessSpawnFailed(io::Error),
|
||||
|
||||
#[error("Error occured in runc: {0}")]
|
||||
InvalidCommand(io::Error),
|
||||
|
||||
#[error("Runc command failed: status={status}, stdout=\"{stdout}\", stderr=\"{stderr}\"")]
|
||||
CommandFailed {
|
||||
status: ExitStatus,
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
},
|
||||
|
||||
#[error("Runc IO unavailable: {0}")]
|
||||
UnavailableIO(io::Error),
|
||||
|
||||
#[error("Runc command timed out: {0}")]
|
||||
CommandTimeout(tokio::time::error::Elapsed),
|
||||
|
||||
#[error("Unable to parse runc version")]
|
||||
InvalidVersion,
|
||||
|
||||
#[error("Unable to locate the runc")]
|
||||
NotFound,
|
||||
|
||||
#[error("Error occurs with fs: {0}")]
|
||||
FileSystemError(io::Error),
|
||||
|
||||
#[error("Failed to spec file: {0}")]
|
||||
SpecFileCreationFailed(io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
SpecFileCleanupFailed(io::Error),
|
||||
|
||||
#[error("Failed to find valid path for spec file")]
|
||||
SpecFileNotFound,
|
||||
|
||||
#[error("Top command is missing a pid header")]
|
||||
TopMissingPidHeader,
|
||||
|
||||
#[error("Top command returned an empty response")]
|
||||
TopShortResponseError,
|
||||
|
||||
#[error("Unix socket connection error: {0}")]
|
||||
UnixSocketConnectionFailed(io::Error),
|
||||
|
||||
#[error("Unable to bind to unix socket: {0}")]
|
||||
UnixSocketBindFailed(io::Error),
|
||||
|
||||
#[error("Unix socket failed to receive pty")]
|
||||
UnixSocketReceiveMessageFailed,
|
||||
|
||||
#[error("Unix socket unexpectedly closed")]
|
||||
UnixSocketClosed,
|
||||
|
||||
#[error("Failed to handle environment variable: {0}")]
|
||||
EnvError(env::VarError),
|
||||
|
||||
#[error("Sorry, this part of api is not implemented: {0}")]
|
||||
Unimplemented(String),
|
||||
|
||||
#[error("Error occured in runc client: {0}")]
|
||||
Other(Box<dyn std::error::Error + Send>),
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Forked from https://github.com/pwFoo/rust-runc/blob/313e6ae5a79b54455b0a242a795c69adf035141a/src/events.rs
|
||||
|
||||
/*
|
||||
* Copyright 2020 fsyncd, Berlin, Germany.
|
||||
* Additional material, copyright of the containerd authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Event type generated by runc
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
pub enum EventType {
|
||||
/// Statistics
|
||||
Stats,
|
||||
/// Out of memory
|
||||
Oom,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Event {
|
||||
#[serde(rename = "type")]
|
||||
pub event_type: EventType,
|
||||
pub id: String,
|
||||
#[serde(rename = "data")]
|
||||
pub stats: Option<Stats>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Stats {
|
||||
pub cpu: Cpu,
|
||||
pub memory: Memory,
|
||||
pub pids: Pids,
|
||||
#[serde(rename = "blkio")]
|
||||
pub block_io: BlkIO,
|
||||
#[serde(rename = "hugetlb")]
|
||||
pub huge_tlb: HugeTLB,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HugeTLB {
|
||||
pub usage: Option<u64>,
|
||||
pub max: Option<u64>,
|
||||
#[serde(rename = "failcnt")]
|
||||
pub fail_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BlkIOEntry {
|
||||
pub major: Option<u64>,
|
||||
pub minor: Option<u64>,
|
||||
pub op: Option<String>,
|
||||
pub value: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BlkIO {
|
||||
/// Number of bytes transferred to and from the disk
|
||||
#[serde(rename = "ioServiceBytesRecursive")]
|
||||
pub io_service_bytes_recursive: Option<Vec<BlkIOEntry>>,
|
||||
/// Number of io requests issued to the disk
|
||||
#[serde(rename = "ioServicedRecursive")]
|
||||
pub io_serviced_recursive: Option<Vec<BlkIOEntry>>,
|
||||
/// Number of queued disk io requests
|
||||
#[serde(rename = "ioQueueRecursive")]
|
||||
pub io_queued_recursive: Option<Vec<BlkIOEntry>>,
|
||||
/// Amount of time io requests took to service
|
||||
#[serde(rename = "ioServiceTimeRecursive")]
|
||||
pub io_service_time_recursive: Option<Vec<BlkIOEntry>>,
|
||||
/// Amount of time io requests spent waiting in the queue
|
||||
#[serde(rename = "ioWaitTimeRecursive")]
|
||||
pub io_wait_time_recursive: Option<Vec<BlkIOEntry>>,
|
||||
/// Number of merged io requests
|
||||
#[serde(rename = "ioMergedRecursive")]
|
||||
pub io_merged_recursive: Option<Vec<BlkIOEntry>>,
|
||||
/// Disk time allocated the device
|
||||
#[serde(rename = "ioTimeRecursive")]
|
||||
pub io_time_recursive: Option<Vec<BlkIOEntry>>,
|
||||
/// Number of sectors transferred to and from the io device
|
||||
#[serde(rename = "sectorsRecursive")]
|
||||
pub sectors_recursive: Option<Vec<BlkIOEntry>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Pids {
|
||||
/// Number of pids in the cgroup
|
||||
pub current: Option<u64>,
|
||||
/// Active pids hard limit
|
||||
pub limit: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Throttling {
|
||||
/// Number of periods with throttling active
|
||||
pub periods: Option<u64>,
|
||||
#[serde(rename = "throttledPeriods")]
|
||||
/// Number of periods when the container hit its throttling limit
|
||||
pub throtted_periods: Option<u64>,
|
||||
/// Aggregate time the container was throttled for in nanoseconds
|
||||
#[serde(rename = "throttledTime")]
|
||||
pub throtted_time: Option<u64>,
|
||||
}
|
||||
|
||||
/// Each members represents time in nanoseconds
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CpuUsage {
|
||||
/// Total CPU time consumed
|
||||
pub total: Option<u64>,
|
||||
/// Total CPU time consumed per core
|
||||
pub per_cpu: Option<Vec<u64>>,
|
||||
/// Total CPU time consumed in kernel mode
|
||||
pub kernel: u64,
|
||||
/// Total CPU time consumed in user mode
|
||||
pub user: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Cpu {
|
||||
pub usage: Option<u64>,
|
||||
pub throttling: Option<Throttling>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MemoryEntry {
|
||||
/// Memory limit in bytes
|
||||
pub limit: u64,
|
||||
/// Usage in bytes
|
||||
pub usage: Option<u64>,
|
||||
/// Maximum usage in bytes
|
||||
pub max: Option<u64>,
|
||||
/// Count of memory allocation failures
|
||||
#[serde(rename = "failcnt")]
|
||||
pub fail_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Memory {
|
||||
/// Memory usage for cache
|
||||
pub cache: Option<u64>,
|
||||
/// Overall memory usage, excluding swap
|
||||
pub usage: Option<MemoryEntry>,
|
||||
/// Overall memory usage, including swap
|
||||
pub swap: Option<MemoryEntry>,
|
||||
/// Kernel usage of memory
|
||||
pub kernel: Option<MemoryEntry>,
|
||||
/// Kernel TCP of memory
|
||||
#[serde(rename = "kernelTCP")]
|
||||
pub kernel_tcp: Option<MemoryEntry>,
|
||||
/// Raw stats of memory
|
||||
pub raw: Option<HashMap<String, u64>>,
|
||||
}
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::FromRawFd;
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::sys::stat::Mode;
|
||||
use nix::unistd::{Gid, Uid};
|
||||
|
||||
pub trait Io: Sync + Send {
|
||||
/// Return write side of stdin
|
||||
fn stdin(&self) -> Option<File> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Return read side of stdout
|
||||
fn stdout(&self) -> Option<File> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Return read side of stderr
|
||||
fn stderr(&self) -> Option<File> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Set IO for passed command.
|
||||
/// Read side of stdin, write side of stdout and write side of stderr should be provided to command.
|
||||
fn set(&self, _cmd: &mut Command) -> std::io::Result<()>;
|
||||
|
||||
// tokio version of set()
|
||||
fn set_tk(&self, _cmd: &mut tokio::process::Command) -> std::io::Result<()>;
|
||||
|
||||
fn close_after_start(&self);
|
||||
}
|
||||
|
||||
impl Debug for dyn Io {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Io",)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IOOption {
|
||||
pub open_stdin: bool,
|
||||
pub open_stdout: bool,
|
||||
pub open_stderr: bool,
|
||||
}
|
||||
|
||||
impl Default for IOOption {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
open_stdin: true,
|
||||
open_stdout: true,
|
||||
open_stderr: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct represents pipe that can be used to transfer
|
||||
/// stdio inputs and outputs
|
||||
/// when one side of closed, this struct represent it with [`None`]
|
||||
#[derive(Debug)]
|
||||
pub struct Pipe {
|
||||
// Might be ugly hack: using mutex in order to take rd/wr under immutable [`Pipe`]
|
||||
rd: Mutex<Option<File>>,
|
||||
wr: Mutex<Option<File>>,
|
||||
}
|
||||
|
||||
impl Pipe {
|
||||
pub fn new() -> std::io::Result<Self> {
|
||||
let (r, w) = nix::unistd::pipe()?;
|
||||
let (rd, wr) = unsafe {
|
||||
(
|
||||
Mutex::new(Some(File::from_raw_fd(r))),
|
||||
Mutex::new(Some(File::from_raw_fd(w))),
|
||||
)
|
||||
};
|
||||
Ok(Self { rd, wr })
|
||||
}
|
||||
|
||||
pub fn take_read(&self) -> Option<File> {
|
||||
let mut m = self.rd.lock().unwrap();
|
||||
m.take()
|
||||
}
|
||||
|
||||
pub fn take_write(&self) -> Option<File> {
|
||||
let mut m = self.wr.lock().unwrap();
|
||||
m.take()
|
||||
}
|
||||
|
||||
pub fn close_read(&self) {
|
||||
let mut m = self.rd.lock().unwrap();
|
||||
let _ = m.take();
|
||||
}
|
||||
|
||||
pub fn close_write(&self) {
|
||||
let mut m = self.wr.lock().unwrap();
|
||||
let _ = m.take();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PipedIo {
|
||||
stdin: Option<Pipe>,
|
||||
stdout: Option<Pipe>,
|
||||
stderr: Option<Pipe>,
|
||||
}
|
||||
|
||||
impl PipedIo {
|
||||
pub fn new(uid: u32, gid: u32, opts: IOOption) -> std::io::Result<Self> {
|
||||
let uid = Some(Uid::from_raw(uid));
|
||||
let gid = Some(Gid::from_raw(gid));
|
||||
let stdin = if opts.open_stdin {
|
||||
let pipe = Pipe::new()?;
|
||||
{
|
||||
let m = pipe.rd.lock().unwrap();
|
||||
if let Some(f) = m.as_ref() {
|
||||
nix::unistd::fchown(f.as_raw_fd(), uid, gid)?;
|
||||
}
|
||||
}
|
||||
Some(pipe)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let stdout = if opts.open_stdout {
|
||||
let pipe = Pipe::new()?;
|
||||
{
|
||||
let m = pipe.wr.lock().unwrap();
|
||||
if let Some(f) = m.as_ref() {
|
||||
nix::unistd::fchown(f.as_raw_fd(), uid, gid)?;
|
||||
}
|
||||
}
|
||||
Some(pipe)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let stderr = if opts.open_stderr {
|
||||
let pipe = Pipe::new()?;
|
||||
{
|
||||
let m = pipe.wr.lock().unwrap();
|
||||
if let Some(f) = m.as_ref() {
|
||||
nix::unistd::fchown(f.as_raw_fd(), uid, gid)?;
|
||||
}
|
||||
}
|
||||
Some(pipe)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Io for PipedIo {
|
||||
fn stdin(&self) -> Option<File> {
|
||||
if let Some(ref stdin) = self.stdin {
|
||||
stdin.take_write()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn stdout(&self) -> Option<File> {
|
||||
if let Some(ref stdout) = self.stdout {
|
||||
stdout.take_read()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn stderr(&self) -> Option<File> {
|
||||
if let Some(ref stderr) = self.stderr {
|
||||
stderr.take_read()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Note that this internally use [`std::fs::File`]'s [`try_clone()`].
|
||||
/// Thus, the files passed to commands will be not closed after command exit.
|
||||
fn set(&self, cmd: &mut Command) -> std::io::Result<()> {
|
||||
if let Some(ref p) = self.stdin {
|
||||
let m = p.rd.lock().unwrap();
|
||||
if let Some(stdin) = &*m {
|
||||
let f = stdin.try_clone()?;
|
||||
cmd.stdin(f);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref p) = self.stdout {
|
||||
let m = p.wr.lock().unwrap();
|
||||
if let Some(f) = &*m {
|
||||
let f = f.try_clone()?;
|
||||
cmd.stdout(f);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref p) = self.stderr {
|
||||
let m = p.wr.lock().unwrap();
|
||||
if let Some(f) = &*m {
|
||||
let f = f.try_clone()?;
|
||||
cmd.stderr(f);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tk(&self, cmd: &mut tokio::process::Command) -> std::io::Result<()> {
|
||||
if let Some(ref p) = self.stdin {
|
||||
let m = p.rd.lock().unwrap();
|
||||
if let Some(stdin) = &*m {
|
||||
let f = stdin.try_clone()?;
|
||||
cmd.stdin(f);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref p) = self.stdout {
|
||||
let m = p.wr.lock().unwrap();
|
||||
if let Some(f) = &*m {
|
||||
let f = f.try_clone()?;
|
||||
cmd.stdout(f);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref p) = self.stderr {
|
||||
let m = p.wr.lock().unwrap();
|
||||
if let Some(f) = &*m {
|
||||
let f = f.try_clone()?;
|
||||
cmd.stderr(f);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// closing only write side (should be stdout/err "from" runc process)
|
||||
fn close_after_start(&self) {
|
||||
if let Some(ref p) = self.stdout {
|
||||
p.close_write();
|
||||
}
|
||||
if let Some(ref p) = self.stderr {
|
||||
p.close_write();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IO setup for /dev/null use with runc
|
||||
#[derive(Debug)]
|
||||
pub struct NullIo {
|
||||
dev_null: Mutex<Option<File>>,
|
||||
}
|
||||
|
||||
impl NullIo {
|
||||
pub fn new() -> std::io::Result<Self> {
|
||||
let fd = nix::fcntl::open("/dev/null", OFlag::O_RDONLY, Mode::empty())?;
|
||||
let dev_null = unsafe { Mutex::new(Some(std::fs::File::from_raw_fd(fd))) };
|
||||
Ok(Self { dev_null })
|
||||
}
|
||||
}
|
||||
|
||||
impl Io for NullIo {
|
||||
fn set(&self, cmd: &mut Command) -> std::io::Result<()> {
|
||||
if let Some(null) = self.dev_null.lock().unwrap().as_ref() {
|
||||
cmd.stdout(null.try_clone()?);
|
||||
cmd.stderr(null.try_clone()?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tk(&self, cmd: &mut tokio::process::Command) -> std::io::Result<()> {
|
||||
if let Some(null) = self.dev_null.lock().unwrap().as_ref() {
|
||||
cmd.stdout(null.try_clone()?);
|
||||
cmd.stderr(null.try_clone()?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// closing only write side (should be stdout/err "from" runc process)
|
||||
fn close_after_start(&self) {
|
||||
let mut m = self.dev_null.lock().unwrap();
|
||||
let _ = m.take();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,980 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Forked from https://github.com/pwFoo/rust-runc/blob/313e6ae5a79b54455b0a242a795c69adf035141a/src/lib.rs
|
||||
|
||||
/*
|
||||
* Copyright 2020 fsyncd, Berlin, Germany.
|
||||
* Additional material, copyright of the containerd authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! A crate for consuming the runc binary in your Rust applications, similar to [go-runc](https://github.com/containerd/go-runc) for Go.
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
use std::path::Path;
|
||||
use std::process::{ExitStatus, Output, Stdio};
|
||||
use std::time::Duration;
|
||||
|
||||
use oci_spec::runtime::{Linux, Process};
|
||||
|
||||
// suspended for difficulties
|
||||
// pub mod console;
|
||||
pub mod container;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod io;
|
||||
pub mod monitor;
|
||||
pub mod options;
|
||||
mod runc;
|
||||
mod utils;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::error::Error;
|
||||
use crate::events::{Event, Stats};
|
||||
use crate::monitor::{DefaultMonitor, Exit, ProcessMonitor};
|
||||
use crate::options::*;
|
||||
use crate::utils::{JSON, TEXT};
|
||||
|
||||
type Result<T> = std::result::Result<T, crate::error::Error>;
|
||||
|
||||
/// Response is for (pid, exit status, outputs).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Response {
|
||||
pub pid: u32,
|
||||
pub status: ExitStatus,
|
||||
pub output: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Version {
|
||||
pub runc_version: Option<String>,
|
||||
pub spec_version: Option<String>,
|
||||
pub commit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LogFormat {
|
||||
Json,
|
||||
Text,
|
||||
}
|
||||
|
||||
impl Display for LogFormat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LogFormat::Json => write!(f, "{}", JSON),
|
||||
LogFormat::Text => write!(f, "{}", TEXT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for runc client.
|
||||
///
|
||||
/// This struct provide chaining interface like, for example, [`std::fs::OpenOptions`].
|
||||
/// Note that you cannot access the members of Config directly.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// use runc::{LogFormat, Config};
|
||||
///
|
||||
/// let config = Config::new()
|
||||
/// .root("./new_root")
|
||||
/// .debug(false)
|
||||
/// .log("/path/to/logfile.json")
|
||||
/// .log_format(LogFormat::Json)
|
||||
/// .rootless(true);
|
||||
/// let client = config.build();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Config(runc::Config);
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn command<P>(&mut self, command: P) -> &mut Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.0.command(command);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn root<P>(&mut self, root: P) -> &mut Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.0.root(root);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn debug(&mut self, debug: bool) -> &mut Self {
|
||||
self.0.debug(debug);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log<P>(&mut self, log: P) -> &mut Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.0.log(log);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_format(&mut self, log_format: LogFormat) -> &mut Self {
|
||||
self.0.log_format(log_format);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_format_json(&mut self) -> &mut Self {
|
||||
self.0.log_format_json();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_format_text(&mut self) -> &mut Self {
|
||||
self.0.log_format_text();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn systemd_cgroup(&mut self, systemd_cgroup: bool) -> &mut Self {
|
||||
self.0.systemd_cgroup(systemd_cgroup);
|
||||
self
|
||||
}
|
||||
|
||||
// FIXME: criu is not supported now
|
||||
// pub fn criu(mut self, criu: bool) -> Self {
|
||||
// self.0.criu(criu);
|
||||
// self
|
||||
// }
|
||||
|
||||
pub fn rootless(&mut self, rootless: bool) -> &mut Self {
|
||||
self.0.rootless(rootless);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_pgid(&mut self, set_pgid: bool) -> &mut Self {
|
||||
self.0.set_pgid(set_pgid);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rootless_auto(&mut self) -> &mut Self {
|
||||
self.0.rootless_auto();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn timeout(&mut self, millis: u64) -> &mut Self {
|
||||
self.0.timeout(millis);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Result<Client> {
|
||||
Ok(Client(self.0.build()?))
|
||||
}
|
||||
|
||||
pub fn build_async(&mut self) -> Result<AsyncClient> {
|
||||
Ok(AsyncClient(self.0.build()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client(runc::Runc);
|
||||
|
||||
impl Client {
|
||||
/// Create a new runc client from the supplied configuration
|
||||
pub fn from_config(mut config: Config) -> Result<Self> {
|
||||
config.build()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn command(&self, args: &[String]) -> Result<std::process::Command> {
|
||||
let args = [&self.0.args()?, args].concat();
|
||||
let mut cmd = std::process::Command::new(&self.0.command);
|
||||
cmd.args(&args).env_remove("NOTIFY_SOCKET"); // NOTIFY_SOCKET introduces a special behavior in runc but should only be set if invoked from systemd
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn command(&self, _args: &[String]) -> Result<std::process::Command> {
|
||||
Err(Error::Unimplemented("command".to_string()))
|
||||
}
|
||||
|
||||
pub fn checkpoint(&self) -> Result<()> {
|
||||
Err(Error::Unimplemented("checkpoint".to_string()))
|
||||
}
|
||||
|
||||
fn launch(&self, mut cmd: std::process::Command, combined_output: bool) -> Result<Response> {
|
||||
let child = cmd.spawn().map_err(Error::ProcessSpawnFailed)?;
|
||||
let pid = child.id();
|
||||
let result = child.wait_with_output().map_err(Error::InvalidCommand)?;
|
||||
let status = result.status;
|
||||
let stdout = String::from_utf8(result.stdout).unwrap();
|
||||
let stderr = String::from_utf8(result.stderr).unwrap();
|
||||
if status.success() {
|
||||
if combined_output {
|
||||
Ok(Response {
|
||||
pid,
|
||||
status,
|
||||
output: stdout + stderr.as_str(),
|
||||
})
|
||||
} else {
|
||||
Ok(Response {
|
||||
pid,
|
||||
status,
|
||||
output: stdout,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new container
|
||||
pub fn create<P>(&self, id: &str, bundle: P, opts: Option<&CreateOpts>) -> Result<Response>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut args = vec![
|
||||
"create".to_string(),
|
||||
"--bundle".to_string(),
|
||||
utils::abs_string(bundle)?,
|
||||
];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args()?);
|
||||
}
|
||||
args.push(id.to_string());
|
||||
let mut cmd = self.command(&args)?;
|
||||
match opts {
|
||||
Some(CreateOpts { io: Some(_io), .. }) => {
|
||||
_io.set(&mut cmd).map_err(Error::UnavailableIO)?;
|
||||
let res = self.launch(cmd, true)?;
|
||||
_io.close_after_start();
|
||||
Ok(res)
|
||||
}
|
||||
_ => self.launch(cmd, true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a container
|
||||
pub fn delete(&self, id: &str, opts: Option<&DeleteOpts>) -> Result<()> {
|
||||
let mut args = vec!["delete".to_string()];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args());
|
||||
}
|
||||
args.push(id.to_string());
|
||||
self.launch(self.command(&args)?, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute an additional process inside the container
|
||||
pub fn exec(&self, id: &str, spec: &Process, opts: Option<&ExecOpts>) -> Result<()> {
|
||||
let filename = utils::temp_filename_in_runtime_dir()?;
|
||||
let spec_json = serde_json::to_string(spec).map_err(Error::JsonDeserializationFailed)?;
|
||||
std::fs::write(&filename, spec_json).map_err(Error::SpecFileCreationFailed)?;
|
||||
let mut args = vec!["exec".to_string(), "process".to_string(), filename];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args()?);
|
||||
}
|
||||
args.push(id.to_string());
|
||||
let mut cmd = self.command(&args)?;
|
||||
if let Some(ExecOpts { io: Some(_io), .. }) = opts {
|
||||
_io.set(&mut cmd).map_err(Error::UnavailableIO)?;
|
||||
}
|
||||
let _ = self.launch(cmd, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send the specified signal to processes inside the container
|
||||
pub fn kill(&self, id: &str, sig: u32, opts: Option<&KillOpts>) -> Result<()> {
|
||||
let mut args = vec!["kill".to_string()];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args());
|
||||
}
|
||||
args.push(id.to_string());
|
||||
args.push(sig.to_string());
|
||||
let _ = self.launch(self.command(&args)?, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List all containers associated with this runc instance
|
||||
pub fn list(&self) -> Result<Vec<Container>> {
|
||||
let args = ["list".to_string(), "--format-json".to_string()];
|
||||
let res = self.launch(self.command(&args)?, true)?;
|
||||
let output = res.output.trim();
|
||||
// Ugly hack to work around golang
|
||||
Ok(if output == "null" {
|
||||
Vec::new()
|
||||
} else {
|
||||
serde_json::from_str(output).map_err(Error::JsonDeserializationFailed)?
|
||||
})
|
||||
}
|
||||
|
||||
/// Pause a container
|
||||
pub fn pause(&self, id: &str) -> Result<()> {
|
||||
let args = ["pause".to_string(), id.to_string()];
|
||||
let _ = self.launch(self.command(&args)?, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restore(&self) -> Result<()> {
|
||||
Err(Error::Unimplemented("restore".to_string()))
|
||||
}
|
||||
|
||||
/// Resume a container
|
||||
pub fn resume(&self, id: &str) -> Result<()> {
|
||||
let args = ["pause".to_string(), id.to_string()];
|
||||
let _ = self.launch(self.command(&args)?, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run the create, start, delete lifecycle of the container and return its exit status
|
||||
pub fn run<P>(&self, id: &str, bundle: P, opts: Option<&CreateOpts>) -> Result<Response>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut args = vec!["run".to_string(), "--bundle".to_string()];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args()?);
|
||||
}
|
||||
args.push(utils::abs_string(bundle)?);
|
||||
args.push(id.to_string());
|
||||
let mut cmd = self.command(&args)?;
|
||||
if let Some(CreateOpts { io: Some(_io), .. }) = opts {
|
||||
_io.set(&mut cmd).map_err(Error::UnavailableIO)?;
|
||||
};
|
||||
self.launch(self.command(&args)?, true)
|
||||
}
|
||||
|
||||
/// Start an already created container
|
||||
pub fn start(&self, id: &str) -> Result<Response> {
|
||||
let args = ["start".to_string(), id.to_string()];
|
||||
self.launch(self.command(&args)?, true)
|
||||
}
|
||||
|
||||
/// Return the state of a container
|
||||
pub fn state(&self, id: &str) -> Result<Container> {
|
||||
let args = ["state".to_string(), id.to_string()];
|
||||
let res = self.launch(self.command(&args)?, true)?;
|
||||
serde_json::from_str(&res.output).map_err(Error::JsonDeserializationFailed)
|
||||
}
|
||||
|
||||
/// Update a container with the provided resource spec
|
||||
pub fn update(&self, id: &str, resources: &Linux) -> Result<()> {
|
||||
let filename = utils::temp_filename_in_runtime_dir()?;
|
||||
let spec_json =
|
||||
serde_json::to_string(resources).map_err(Error::JsonDeserializationFailed)?;
|
||||
std::fs::write(&filename, spec_json).map_err(Error::SpecFileCreationFailed)?;
|
||||
let args = [
|
||||
"update".to_string(),
|
||||
"--resources".to_string(),
|
||||
filename,
|
||||
id.to_string(),
|
||||
];
|
||||
self.launch(self.command(&args)?, true)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AsyncClient(runc::Runc);
|
||||
|
||||
// As monitor instance never have to be mutable (it has only &self methods), declare it as const.
|
||||
const MONITOR: DefaultMonitor = DefaultMonitor::new();
|
||||
|
||||
/// Async client for runc
|
||||
/// Note that you MUST use this client on tokio runtime, as this client internally use [`tokio::process::Command`]
|
||||
/// and some other utilities.
|
||||
impl AsyncClient {
|
||||
/// Create a new runc client from the supplied configuration
|
||||
pub fn from_config(mut config: Config) -> Result<Self> {
|
||||
config.build_async()
|
||||
}
|
||||
|
||||
pub fn command(&self, args: &[String]) -> Result<tokio::process::Command> {
|
||||
let args = [&self.0.args()?, args].concat();
|
||||
let mut cmd = tokio::process::Command::new(&self.0.command);
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
cmd.args(&args).env_remove("NOTIFY_SOCKET"); // NOTIFY_SOCKET introduces a special behavior in runc but should only be set if invoked from systemd
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
pub async fn launch(
|
||||
&self,
|
||||
cmd: tokio::process::Command,
|
||||
combined_output: bool,
|
||||
) -> Result<Response> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<Exit>();
|
||||
let start = MONITOR.start(cmd, tx);
|
||||
let wait = MONITOR.wait(rx);
|
||||
let (
|
||||
Output {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
},
|
||||
Exit { pid, .. },
|
||||
) = tokio::try_join!(start, wait).map_err(Error::InvalidCommand)?;
|
||||
|
||||
// ugly hack to work around
|
||||
let stdout = String::from_utf8(stdout)
|
||||
.expect("returned non-utf8 characters from container process.");
|
||||
let stderr = String::from_utf8(stderr)
|
||||
.expect("returned non-utf8 characters from container process.");
|
||||
|
||||
if status.success() {
|
||||
if combined_output {
|
||||
Ok(Response {
|
||||
pid,
|
||||
status,
|
||||
output: stdout + stderr.as_str(),
|
||||
})
|
||||
} else {
|
||||
Ok(Response {
|
||||
pid,
|
||||
status,
|
||||
output: stdout,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn checkpoint(&self) -> Result<()> {
|
||||
Err(Error::Unimplemented("checkpoint".to_string()))
|
||||
}
|
||||
|
||||
/// Create a new container
|
||||
pub async fn create<P>(&self, id: &str, bundle: P, opts: Option<&CreateOpts>) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut args = vec![
|
||||
"create".to_string(),
|
||||
"--bundle".to_string(),
|
||||
utils::abs_string(bundle)?,
|
||||
];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args()?);
|
||||
}
|
||||
args.push(id.to_string());
|
||||
let mut cmd = self.command(&args)?;
|
||||
match opts {
|
||||
Some(CreateOpts { io: Some(_io), .. }) => {
|
||||
_io.set_tk(&mut cmd).map_err(Error::UnavailableIO)?;
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<Exit>();
|
||||
let start = MONITOR.start(cmd, tx);
|
||||
let wait = MONITOR.wait(rx);
|
||||
let (
|
||||
Output {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
},
|
||||
_,
|
||||
) = tokio::try_join!(start, wait).map_err(Error::InvalidCommand)?;
|
||||
_io.close_after_start();
|
||||
|
||||
let stdout = String::from_utf8(stdout).unwrap();
|
||||
let stderr = String::from_utf8(stderr).unwrap();
|
||||
if !status.success() {
|
||||
return Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let _ = self.launch(cmd, true).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a container
|
||||
pub async fn delete(&self, id: &str, opts: Option<&DeleteOpts>) -> Result<()> {
|
||||
let mut args = vec!["delete".to_string()];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args());
|
||||
}
|
||||
args.push(id.to_string());
|
||||
let _ = self.launch(self.command(&args)?, true).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return an event stream of container notifications
|
||||
pub async fn events(&self, _id: &str, _interval: &Duration) -> Result<()> {
|
||||
Err(Error::Unimplemented("events".to_string()))
|
||||
}
|
||||
|
||||
/// Execute an additional process inside the container
|
||||
pub async fn exec(&self, _id: &str, _spec: &Process, _opts: Option<&ExecOpts>) -> Result<()> {
|
||||
Err(Error::Unimplemented("exec".to_string()))
|
||||
}
|
||||
|
||||
/// Send the specified signal to processes inside the container
|
||||
pub async fn kill(&self, id: &str, sig: u32, opts: Option<&KillOpts>) -> Result<()> {
|
||||
let mut args = vec!["kill".to_string()];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args());
|
||||
}
|
||||
args.push(id.to_string());
|
||||
args.push(sig.to_string());
|
||||
let _ = self.launch(self.command(&args)?, true).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List all containers associated with this runc instance
|
||||
pub async fn list(&self) -> Result<Vec<Container>> {
|
||||
let args = ["list".to_string(), "--format-json".to_string()];
|
||||
let res = self.launch(self.command(&args)?, true).await?;
|
||||
let output = res.output.trim();
|
||||
// Ugly hack to work around golang
|
||||
Ok(if output == "null" {
|
||||
Vec::new()
|
||||
} else {
|
||||
serde_json::from_str(output).map_err(Error::JsonDeserializationFailed)?
|
||||
})
|
||||
}
|
||||
|
||||
/// Pause a container
|
||||
pub async fn pause(&self, id: &str) -> Result<()> {
|
||||
let args = ["pause".to_string(), id.to_string()];
|
||||
let _ = self.launch(self.command(&args)?, true).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List all the processes inside the container, returning their pids
|
||||
pub async fn ps(&self, id: &str) -> Result<Vec<usize>> {
|
||||
let args = [
|
||||
"ps".to_string(),
|
||||
"--format-json".to_string(),
|
||||
id.to_string(),
|
||||
];
|
||||
let res = self.launch(self.command(&args)?, true).await?;
|
||||
let output = res.output.trim();
|
||||
// Ugly hack to work around golang
|
||||
Ok(if output == "null" {
|
||||
Vec::new()
|
||||
} else {
|
||||
serde_json::from_str(output).map_err(Error::JsonDeserializationFailed)?
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn restore(&self) -> Result<()> {
|
||||
Err(Error::Unimplemented("restore".to_string()))
|
||||
}
|
||||
|
||||
/// Resume a container
|
||||
pub async fn resume(&self, id: &str) -> Result<()> {
|
||||
let args = ["pause".to_string(), id.to_string()];
|
||||
let _ = self.launch(self.command(&args)?, true).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run the create, start, delete lifecycle of the container and return its exit status
|
||||
pub async fn run<P>(&self, id: &str, bundle: P, opts: Option<&CreateOpts>) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut args = vec!["run".to_string(), "--bundle".to_string()];
|
||||
if let Some(opts) = opts {
|
||||
args.append(&mut opts.args()?);
|
||||
}
|
||||
args.push(utils::abs_string(bundle)?);
|
||||
args.push(id.to_string());
|
||||
let _ = self.launch(self.command(&args)?, true).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start an already created container
|
||||
pub async fn start(&self, id: &str) -> Result<()> {
|
||||
let args = vec!["start".to_string(), id.to_string()];
|
||||
let _ = self.launch(self.command(&args)?, true).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the state of a container
|
||||
pub async fn state(&self, id: &str) -> Result<Vec<usize>> {
|
||||
let args = vec!["state".to_string(), id.to_string()];
|
||||
let res = self.launch(self.command(&args)?, true).await?;
|
||||
serde_json::from_str(&res.output).map_err(Error::JsonDeserializationFailed)
|
||||
}
|
||||
|
||||
/// Return the latest statistics for a container
|
||||
pub async fn stats(&self, id: &str) -> Result<Stats> {
|
||||
let args = vec!["events".to_string(), "--stats".to_string(), id.to_string()];
|
||||
let res = self.launch(self.command(&args)?, true).await?;
|
||||
let event: Event =
|
||||
serde_json::from_str(&res.output).map_err(Error::JsonDeserializationFailed)?;
|
||||
if let Some(stats) = event.stats {
|
||||
Ok(stats)
|
||||
} else {
|
||||
Err(Error::MissingContainerStats)
|
||||
}
|
||||
}
|
||||
|
||||
/// Update a container with the provided resource spec
|
||||
pub async fn update(&self, id: &str, resources: &Linux) -> Result<()> {
|
||||
let filename = utils::temp_filename_in_runtime_dir()?;
|
||||
let spec_json =
|
||||
serde_json::to_string(resources).map_err(Error::JsonDeserializationFailed)?;
|
||||
std::fs::write(&filename, spec_json).map_err(Error::SpecFileCreationFailed)?;
|
||||
let args = vec![
|
||||
"update".to_string(),
|
||||
"--resources".to_string(),
|
||||
filename,
|
||||
id.to_string(),
|
||||
];
|
||||
let _ = self.launch(self.command(&args)?, true).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(target_os = "linux")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// following style of go-runc, use only true/false to test
|
||||
const CMD_TRUE: &str = "/bin/true";
|
||||
const CMD_FALSE: &str = "/bin/false";
|
||||
|
||||
fn ok_client() -> Client {
|
||||
Config::new()
|
||||
.command(CMD_TRUE)
|
||||
.build()
|
||||
.expect("unable to create runc instance")
|
||||
}
|
||||
|
||||
fn fail_client() -> Client {
|
||||
Config::new()
|
||||
.command(CMD_FALSE)
|
||||
.build()
|
||||
.expect("unable to create runc instance")
|
||||
}
|
||||
|
||||
fn ok_async_client() -> AsyncClient {
|
||||
Config::new()
|
||||
.command(CMD_TRUE)
|
||||
.build_async()
|
||||
.expect("unable to create runc instance")
|
||||
}
|
||||
|
||||
fn fail_async_client() -> AsyncClient {
|
||||
Config::new()
|
||||
.command(CMD_FALSE)
|
||||
.build_async()
|
||||
.expect("unable to create runc instance")
|
||||
}
|
||||
|
||||
fn dummy_process() -> Process {
|
||||
serde_json::from_str(
|
||||
"
|
||||
{
|
||||
\"user\": {
|
||||
\"uid\": 1000,
|
||||
\"gid\": 1000
|
||||
},
|
||||
\"cwd\": \"/path/to/dir\"
|
||||
}",
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create() {
|
||||
let opts = CreateOpts::new();
|
||||
let ok_runc = ok_client();
|
||||
ok_runc
|
||||
.create("fake-id", "fake-bundle", Some(&opts))
|
||||
.expect("true failed.");
|
||||
eprintln!("ok_runc succeeded.");
|
||||
let fail_runc = fail_client();
|
||||
match fail_runc.create("fake-id", "fake-bundle", Some(&opts)) {
|
||||
Ok(_) => panic!("fail_runc returned exit status 0."),
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
}) => {
|
||||
if status.code().unwrap() == 1 && stdout.is_empty() && stderr.is_empty() {
|
||||
eprintln!("fail_runc succeeded.");
|
||||
} else {
|
||||
panic!("unexpected outputs from fail_runc.")
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("unexpected error from fail_runc: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run() {
|
||||
let opts = CreateOpts::new();
|
||||
let ok_runc = ok_client();
|
||||
ok_runc
|
||||
.run("fake-id", "fake-bundle", Some(&opts))
|
||||
.expect("true failed.");
|
||||
eprintln!("ok_runc succeeded.");
|
||||
let fail_runc = fail_client();
|
||||
match fail_runc.run("fake-id", "fake-bundle", Some(&opts)) {
|
||||
Ok(_) => panic!("fail_runc returned exit status 0."),
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
}) => {
|
||||
if status.code().unwrap() == 1 && stdout.is_empty() && stderr.is_empty() {
|
||||
eprintln!("fail_runc succeeded.");
|
||||
} else {
|
||||
panic!("unexpected outputs from fail_runc.")
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("unexpected error from fail_runc: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec() {
|
||||
let opts = ExecOpts::new();
|
||||
let ok_runc = ok_client();
|
||||
let proc = dummy_process();
|
||||
ok_runc
|
||||
.exec("fake-id", &proc, Some(&opts))
|
||||
.expect("true failed.");
|
||||
eprintln!("ok_runc succeeded.");
|
||||
let fail_runc = fail_client();
|
||||
match fail_runc.exec("fake-id", &proc, Some(&opts)) {
|
||||
Ok(_) => panic!("fail_runc returned exit status 0."),
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
}) => {
|
||||
if status.code().unwrap() == 1 && stdout.is_empty() && stderr.is_empty() {
|
||||
eprintln!("fail_runc succeeded.");
|
||||
} else {
|
||||
panic!("unexpected outputs from fail_runc.")
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("unexpected error from fail_runc: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete() {
|
||||
let opts = DeleteOpts::new();
|
||||
let ok_runc = ok_client();
|
||||
ok_runc
|
||||
.delete("fake-id", Some(&opts))
|
||||
.expect("true failed.");
|
||||
eprintln!("ok_runc succeeded.");
|
||||
let fail_runc = fail_client();
|
||||
match fail_runc.delete("fake-id", Some(&opts)) {
|
||||
Ok(_) => panic!("fail_runc returned exit status 0."),
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
}) => {
|
||||
if status.code().unwrap() == 1 && stdout.is_empty() && stderr.is_empty() {
|
||||
eprintln!("fail_runc succeeded.");
|
||||
} else {
|
||||
panic!("unexpected outputs from fail_runc.")
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("unexpected error from fail_runc: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_create() {
|
||||
let opts = CreateOpts::new();
|
||||
let ok_runc = ok_async_client();
|
||||
let ok_task = tokio::spawn(async move {
|
||||
ok_runc
|
||||
.create("fake-id", "fake-bundle", Some(&opts))
|
||||
.await
|
||||
.expect("true failed.");
|
||||
eprintln!("ok_runc succeeded.");
|
||||
});
|
||||
|
||||
let opts = CreateOpts::new();
|
||||
let fail_runc = fail_async_client();
|
||||
let fail_task = tokio::spawn(async move {
|
||||
match fail_runc
|
||||
.create("fake-id", "fake-bundle", Some(&opts))
|
||||
.await
|
||||
{
|
||||
Ok(_) => panic!("fail_runc returned exit status 0."),
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
}) => {
|
||||
if status.code().unwrap() == 1 && stdout.is_empty() && stderr.is_empty() {
|
||||
eprintln!("fail_runc succeeded.");
|
||||
} else {
|
||||
panic!("unexpected outputs from fail_runc.")
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("unexpected error from fail_runc: {:?}", e),
|
||||
}
|
||||
});
|
||||
|
||||
ok_task.await.expect("ok_task failed.");
|
||||
fail_task.await.expect("fail_task unexpectedly succeeded.");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_start() {
|
||||
let ok_runc = ok_async_client();
|
||||
let ok_task = tokio::spawn(async move {
|
||||
ok_runc.start("fake-id").await.expect("true failed.");
|
||||
eprintln!("ok_runc succeeded.");
|
||||
});
|
||||
|
||||
let fail_runc = fail_async_client();
|
||||
let fail_task = tokio::spawn(async move {
|
||||
match fail_runc.start("fake-id").await {
|
||||
Ok(_) => panic!("fail_runc returned exit status 0."),
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
}) => {
|
||||
if status.code().unwrap() == 1 && stdout.is_empty() && stderr.is_empty() {
|
||||
eprintln!("fail_runc succeeded.");
|
||||
} else {
|
||||
panic!("unexpected outputs from fail_runc.")
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("unexpected error from fail_runc: {:?}", e),
|
||||
}
|
||||
});
|
||||
|
||||
ok_task.await.expect("ok_task failed.");
|
||||
fail_task.await.expect("fail_task unexpectedly succeeded.");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_run() {
|
||||
let opts = CreateOpts::new();
|
||||
let ok_runc = Config::new()
|
||||
.command(CMD_TRUE)
|
||||
.build_async()
|
||||
.expect("unable to create runc instance");
|
||||
tokio::spawn(async move {
|
||||
ok_runc
|
||||
.create("fake-id", "fake-bundle", Some(&opts))
|
||||
.await
|
||||
.expect("true failed.");
|
||||
eprintln!("ok_runc succeeded.");
|
||||
});
|
||||
|
||||
let opts = CreateOpts::new();
|
||||
let fail_runc = Config::new()
|
||||
.command(CMD_FALSE)
|
||||
.build_async()
|
||||
.expect("unable to create runc instance");
|
||||
tokio::spawn(async move {
|
||||
match fail_runc
|
||||
.create("fake-id", "fake-bundle", Some(&opts))
|
||||
.await
|
||||
{
|
||||
Ok(_) => panic!("fail_runc returned exit status 0."),
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
}) => {
|
||||
if status.code().unwrap() == 1 && stdout.is_empty() && stderr.is_empty() {
|
||||
eprintln!("fail_runc succeeded.");
|
||||
} else {
|
||||
panic!("unexpected outputs from fail_runc.")
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("unexpected error from fail_runc: {:?}", e),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("tokio spawn falied.");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_delete() {
|
||||
let opts = DeleteOpts::new();
|
||||
let ok_runc = Config::new()
|
||||
.command(CMD_TRUE)
|
||||
.build_async()
|
||||
.expect("unable to create runc instance");
|
||||
tokio::spawn(async move {
|
||||
ok_runc
|
||||
.delete("fake-id", Some(&opts))
|
||||
.await
|
||||
.expect("true failed.");
|
||||
eprintln!("ok_runc succeeded.");
|
||||
});
|
||||
|
||||
let opts = DeleteOpts::new();
|
||||
let fail_runc = Config::new()
|
||||
.command(CMD_FALSE)
|
||||
.build_async()
|
||||
.expect("unable to create runc instance");
|
||||
tokio::spawn(async move {
|
||||
match fail_runc.delete("fake-id", Some(&opts)).await {
|
||||
Ok(_) => panic!("fail_runc returned exit status 0."),
|
||||
Err(Error::CommandFailed {
|
||||
status,
|
||||
stdout,
|
||||
stderr,
|
||||
}) => {
|
||||
if status.code().unwrap() == 1 && stdout.is_empty() && stderr.is_empty() {
|
||||
eprintln!("fail_runc succeeded.");
|
||||
} else {
|
||||
panic!("unexpected outputs from fail_runc.")
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("unexpected error from fail_runc: {:?}", e),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("tokio spawn falied.");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
use std::process::Output;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::error;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::oneshot::{Receiver, Sender};
|
||||
|
||||
// ProcessMonitor for handling runc process exit
|
||||
// Implementation is different from Go's, because if you return Sender in start() and want to
|
||||
// use it in wait(), then start and wait cannot be executed concurrently.
|
||||
// Alternatively, caller of start() and wait() have to prepare channel
|
||||
#[async_trait]
|
||||
pub trait ProcessMonitor {
|
||||
/// Caller cand choose [`std::mem::forget`] about resource
|
||||
/// associated to that command, e.g. file descriptors.
|
||||
async fn start(
|
||||
&self,
|
||||
mut cmd: tokio::process::Command,
|
||||
tx: Sender<Exit>,
|
||||
) -> std::io::Result<Output> {
|
||||
let chi = cmd.spawn()?;
|
||||
let pid = chi
|
||||
.id()
|
||||
.expect("failed to take pid of the container process.");
|
||||
let out = chi.wait_with_output().await?;
|
||||
let ts = OffsetDateTime::now_utc();
|
||||
match tx.send(Exit {
|
||||
ts,
|
||||
pid,
|
||||
status: out.status.code().unwrap(),
|
||||
}) {
|
||||
Ok(_) => Ok(out),
|
||||
Err(e) => {
|
||||
error!("command {:?} exited but receiver dropped.", cmd);
|
||||
error!("couldn't send messages: {:?}", e);
|
||||
Err(std::io::ErrorKind::ConnectionRefused.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn wait(&self, rx: Receiver<Exit>) -> std::io::Result<Exit> {
|
||||
rx.await.map_err(|_| {
|
||||
error!("sender dropped.");
|
||||
std::io::ErrorKind::BrokenPipe.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DefaultMonitor {}
|
||||
|
||||
impl ProcessMonitor for DefaultMonitor {}
|
||||
|
||||
impl DefaultMonitor {
|
||||
pub const fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Exit {
|
||||
pub ts: OffsetDateTime,
|
||||
pub pid: u32,
|
||||
pub status: i32,
|
||||
}
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Forked from https://github.com/pwFoo/rust-runc/blob/313e6ae5a79b54455b0a242a795c69adf035141a/src/lib.rs
|
||||
|
||||
/*
|
||||
* Copyright 2020 fsyncd, Berlin, Germany.
|
||||
* Additional material, copyright of the containerd authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::io::Io;
|
||||
use crate::utils::{self, ALL, CONSOLE_SOCKET, DETACH, FORCE, NO_NEW_KEYRING, NO_PIVOT, PID_FILE};
|
||||
|
||||
pub trait Args {
|
||||
type Output;
|
||||
fn args(&self) -> Self::Output;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CreateOpts {
|
||||
pub io: Option<Arc<dyn Io>>,
|
||||
/// Path to where a pid file should be created.
|
||||
pub pid_file: Option<PathBuf>,
|
||||
/// Path to where a console socket should be created.
|
||||
pub console_socket: Option<PathBuf>,
|
||||
/// Detach from the container's process (only available for run)
|
||||
pub detach: bool,
|
||||
/// Don't use pivot_root to jail process inside rootfs.
|
||||
pub no_pivot: bool,
|
||||
/// A new session keyring for the container will not be created.
|
||||
pub no_new_keyring: bool,
|
||||
}
|
||||
|
||||
impl Args for CreateOpts {
|
||||
type Output = Result<Vec<String>, Error>;
|
||||
fn args(&self) -> Self::Output {
|
||||
let mut args: Vec<String> = vec![];
|
||||
if let Some(pid_file) = &self.pid_file {
|
||||
args.push(PID_FILE.to_string());
|
||||
args.push(utils::abs_string(pid_file)?);
|
||||
}
|
||||
if let Some(console_socket) = &self.console_socket {
|
||||
args.push(CONSOLE_SOCKET.to_string());
|
||||
args.push(utils::abs_string(console_socket)?);
|
||||
}
|
||||
if self.no_pivot {
|
||||
args.push(NO_PIVOT.to_string());
|
||||
}
|
||||
if self.no_new_keyring {
|
||||
args.push(NO_NEW_KEYRING.to_string());
|
||||
}
|
||||
if self.detach {
|
||||
args.push(DETACH.to_string());
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
}
|
||||
|
||||
impl CreateOpts {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn io(mut self, io: Arc<dyn Io>) -> Self {
|
||||
self.io = Some(io);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pid_file<P>(mut self, pid_file: P) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.pid_file = Some(pid_file.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn console_socket<P>(mut self, console_socket: P) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.console_socket = Some(console_socket.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn detach(mut self, detach: bool) -> Self {
|
||||
self.detach = detach;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn no_pivot(mut self, no_pivot: bool) -> Self {
|
||||
self.no_pivot = no_pivot;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn no_new_keyring(mut self, no_new_keyring: bool) -> Self {
|
||||
self.no_new_keyring = no_new_keyring;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Container execution options
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ExecOpts {
|
||||
pub io: Option<Arc<dyn Io>>,
|
||||
/// Path to where a pid file should be created.
|
||||
pub pid_file: Option<PathBuf>,
|
||||
/// Path to where a console socket should be created.
|
||||
pub console_socket: Option<PathBuf>,
|
||||
/// Detach from the container's process (only available for run)
|
||||
pub detach: bool,
|
||||
}
|
||||
|
||||
impl Args for ExecOpts {
|
||||
type Output = Result<Vec<String>, Error>;
|
||||
fn args(&self) -> Self::Output {
|
||||
let mut args: Vec<String> = vec![];
|
||||
if let Some(pid_file) = &self.pid_file {
|
||||
args.push(PID_FILE.to_string());
|
||||
args.push(utils::abs_string(pid_file)?);
|
||||
}
|
||||
if let Some(console_socket) = &self.console_socket {
|
||||
args.push(CONSOLE_SOCKET.to_string());
|
||||
args.push(utils::abs_string(console_socket)?);
|
||||
}
|
||||
if self.detach {
|
||||
args.push(DETACH.to_string());
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecOpts {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn io(mut self, io: Arc<dyn Io>) -> Self {
|
||||
self.io = Some(io);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pid_file<P>(mut self, pid_file: P) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.pid_file = Some(pid_file.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn console_socket<P>(mut self, console_socket: P) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.console_socket = Some(console_socket.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn detach(mut self, detach: bool) -> Self {
|
||||
self.detach = detach;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Container deletion options
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DeleteOpts {
|
||||
/// Forcibly delete the container if it is still running
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
impl Args for DeleteOpts {
|
||||
type Output = Vec<String>;
|
||||
fn args(&self) -> Self::Output {
|
||||
let mut args: Vec<String> = vec![];
|
||||
if self.force {
|
||||
args.push(FORCE.to_string());
|
||||
}
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
impl DeleteOpts {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn force(mut self, force: bool) -> Self {
|
||||
self.force = force;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Container killing options
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct KillOpts {
|
||||
/// Seng the kill signal to all the processes inside the container
|
||||
pub all: bool,
|
||||
}
|
||||
|
||||
impl Args for KillOpts {
|
||||
type Output = Vec<String>;
|
||||
fn args(&self) -> Self::Output {
|
||||
let mut args: Vec<String> = vec![];
|
||||
if self.all {
|
||||
args.push(ALL.to_string());
|
||||
}
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
impl KillOpts {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn all(mut self, all: bool) -> Self {
|
||||
self.all = all;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use std::env;
|
||||
|
||||
const ARGS_FAIL_MSG: &str = "Args.args() failed.";
|
||||
|
||||
#[test]
|
||||
fn create_opts_test() {
|
||||
assert_eq!(
|
||||
CreateOpts::new().args().expect(ARGS_FAIL_MSG),
|
||||
vec![String::new(); 0]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CreateOpts::new().pid_file(".").args().expect(ARGS_FAIL_MSG),
|
||||
vec![
|
||||
"--pid-file".to_string(),
|
||||
env::current_dir()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.parse::<String>()
|
||||
.unwrap()
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CreateOpts::new()
|
||||
.console_socket("..")
|
||||
.args()
|
||||
.expect(ARGS_FAIL_MSG),
|
||||
vec![
|
||||
"--console-socket".to_string(),
|
||||
env::current_dir()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.parse::<String>()
|
||||
.unwrap()
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CreateOpts::new()
|
||||
.detach(true)
|
||||
.no_pivot(true)
|
||||
.no_new_keyring(true)
|
||||
.args()
|
||||
.expect(ARGS_FAIL_MSG),
|
||||
vec![
|
||||
"--no-pivot".to_string(),
|
||||
"--no-new-keyring".to_string(),
|
||||
"--detach".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_opts_test() {
|
||||
assert_eq!(
|
||||
ExecOpts::new().args().expect(ARGS_FAIL_MSG),
|
||||
vec![String::new(); 0]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ExecOpts::new().pid_file(".").args().expect(ARGS_FAIL_MSG),
|
||||
vec![
|
||||
"--pid-file".to_string(),
|
||||
env::current_dir()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.parse::<String>()
|
||||
.unwrap()
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ExecOpts::new()
|
||||
.console_socket("..")
|
||||
.args()
|
||||
.expect(ARGS_FAIL_MSG),
|
||||
vec![
|
||||
"--console-socket".to_string(),
|
||||
env::current_dir()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.parse::<String>()
|
||||
.unwrap()
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ExecOpts::new().detach(true).args().expect(ARGS_FAIL_MSG),
|
||||
vec!["--detach".to_string(),]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_opts_test() {
|
||||
assert_eq!(
|
||||
DeleteOpts::new().force(false).args(),
|
||||
vec![String::new(); 0]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
DeleteOpts::new().force(true).args(),
|
||||
vec!["--force".to_string()],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kill_opts_test() {
|
||||
assert_eq!(KillOpts::new().all(false).args(), vec![String::new(); 0]);
|
||||
|
||||
assert_eq!(KillOpts::new().all(true).args(), vec!["--all".to_string()],);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Forked from https://github.com/pwFoo/rust-runc/blob/313e6ae5a79b54455b0a242a795c69adf035141a/src/lib.rs
|
||||
|
||||
/*
|
||||
* Copyright 2020 fsyncd, Berlin, Germany.
|
||||
* Additional material, copyright of the containerd authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::options::Args;
|
||||
use crate::utils::{self, DEBUG, DEFAULT_COMMAND, LOG, LOG_FORMAT, ROOT, ROOTLESS, SYSTEMD_CGROUP};
|
||||
use crate::LogFormat;
|
||||
|
||||
/// Inner struct for runc configuration
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Config {
|
||||
/// This field is set to overrides the name of the runc binary. If [`None`], "runc" is used.
|
||||
command: Option<PathBuf>,
|
||||
/// Path to root directory of container rootfs.
|
||||
root: Option<PathBuf>,
|
||||
/// Debug logging. If true, debug level logs are emitted.
|
||||
debug: bool,
|
||||
/// Path to log file.
|
||||
log: Option<PathBuf>,
|
||||
/// Specifyng log format. Here, json or text is available. Default is "text" and interpreted as text if [`None`].
|
||||
log_format: Option<LogFormat>,
|
||||
// FIXME: implementation of pdeath_signal is suspended due to difficulties, maybe it's favorable to use signal-hook crate.
|
||||
// pdeath_signal: XXX,
|
||||
/// Using systemd cgroup.
|
||||
systemd_cgroup: bool,
|
||||
/// Setting process group ID(gpid).
|
||||
set_pgid: bool,
|
||||
// FIXME: implementation of extra_args is suspended due to difficulties.
|
||||
// criu: String,
|
||||
/// Setting of whether using rootless mode or not. If [`None`], "auto" settings is used. Note that "auto" is different from explicit "true" or "false".
|
||||
rootless: Option<bool>,
|
||||
// FIXME: implementation of extra_args is suspended due to difficulties.
|
||||
// extra_args: Vec<String>,
|
||||
/// Timeout settings for runc command. Default is 5 seconds.
|
||||
/// This will be used only in AsyncClient.
|
||||
timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn command<P>(&mut self, command: P)
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.command = Some(command.as_ref().to_path_buf());
|
||||
}
|
||||
|
||||
pub fn root<P>(&mut self, root: P)
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.root = Some(root.as_ref().to_path_buf());
|
||||
}
|
||||
|
||||
pub fn debug(&mut self, debug: bool) {
|
||||
self.debug = debug;
|
||||
}
|
||||
|
||||
pub fn log<P>(&mut self, log: P)
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.log = Some(log.as_ref().to_path_buf());
|
||||
}
|
||||
|
||||
pub fn log_format(&mut self, log_format: LogFormat) {
|
||||
self.log_format = Some(log_format);
|
||||
}
|
||||
|
||||
pub fn log_format_json(&mut self) {
|
||||
self.log_format = Some(LogFormat::Json);
|
||||
}
|
||||
|
||||
pub fn log_format_text(&mut self) {
|
||||
self.log_format = Some(LogFormat::Text);
|
||||
}
|
||||
|
||||
pub fn systemd_cgroup(&mut self, systemd_cgroup: bool) {
|
||||
self.systemd_cgroup = systemd_cgroup;
|
||||
}
|
||||
|
||||
// FIXME: criu is not supported now
|
||||
// pub fn criu(&mut self, criu: bool) {
|
||||
// self.criu = criu;
|
||||
// }
|
||||
|
||||
pub fn rootless(&mut self, rootless: bool) {
|
||||
self.rootless = Some(rootless);
|
||||
}
|
||||
|
||||
pub fn set_pgid(&mut self, set_pgid: bool) {
|
||||
self.set_pgid = set_pgid;
|
||||
}
|
||||
|
||||
pub fn rootless_auto(&mut self) {
|
||||
let _ = self.rootless.take();
|
||||
}
|
||||
|
||||
pub fn timeout(&mut self, millis: u64) {
|
||||
self.timeout = Some(Duration::from_millis(millis));
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Result<Runc, Error> {
|
||||
let command = utils::binary_path(
|
||||
self.command
|
||||
.clone()
|
||||
.unwrap_or_else(|| PathBuf::from(DEFAULT_COMMAND)),
|
||||
)
|
||||
.ok_or(Error::NotFound)?;
|
||||
Ok(Runc {
|
||||
command,
|
||||
root: self.root.clone(),
|
||||
debug: self.debug,
|
||||
log: self.log.clone(),
|
||||
log_format: self.log_format.clone().unwrap_or(LogFormat::Text),
|
||||
// self.pdeath_signal: self.pdeath_signal,
|
||||
systemd_cgroup: self.systemd_cgroup,
|
||||
set_pgid: self.set_pgid,
|
||||
// criu: self.criu,
|
||||
rootless: self.rootless,
|
||||
// extra_args: self.extra_args,
|
||||
timeout: self.timeout.unwrap_or_else(|| Duration::from_millis(5000)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner Runtime for RuncClient/RuncAsyncClient
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Runc {
|
||||
pub command: PathBuf,
|
||||
pub root: Option<PathBuf>,
|
||||
pub debug: bool,
|
||||
pub log: Option<PathBuf>,
|
||||
pub log_format: LogFormat,
|
||||
// pdeath_signal: XXX,
|
||||
pub set_pgid: bool,
|
||||
// criu: bool,
|
||||
pub systemd_cgroup: bool,
|
||||
pub rootless: Option<bool>,
|
||||
// extra_args: Vec<String>,
|
||||
pub timeout: Duration,
|
||||
}
|
||||
|
||||
impl Args for Runc {
|
||||
type Output = Result<Vec<String>, Error>;
|
||||
fn args(&self) -> Self::Output {
|
||||
let mut args: Vec<String> = vec![];
|
||||
if let Some(root) = &self.root {
|
||||
args.push(ROOT.to_string());
|
||||
args.push(utils::abs_string(root)?);
|
||||
}
|
||||
if self.debug {
|
||||
args.push(DEBUG.to_string());
|
||||
}
|
||||
if let Some(log_path) = &self.log {
|
||||
args.push(LOG.to_string());
|
||||
args.push(utils::abs_string(log_path)?);
|
||||
}
|
||||
args.push(LOG_FORMAT.to_string());
|
||||
args.push(self.log_format.to_string());
|
||||
// if self.criu {
|
||||
// args.push(CRIU.to_string());
|
||||
// }
|
||||
if self.systemd_cgroup {
|
||||
args.push(SYSTEMD_CGROUP.to_string());
|
||||
}
|
||||
if let Some(rootless) = self.rootless {
|
||||
let arg = format!("{}={}", ROOTLESS, rootless);
|
||||
args.push(arg);
|
||||
}
|
||||
// if self.extra_args.len() > 0 {
|
||||
// args.append(&mut self.extra_args.clone())
|
||||
// }
|
||||
Ok(args)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#![allow(unused)]
|
||||
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use path_absolutize::*;
|
||||
use tempfile::{Builder, NamedTempFile};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
// constants for flags
|
||||
pub const ALL: &str = "--all";
|
||||
pub const CONSOLE_SOCKET: &str = "--console-socket";
|
||||
// pub const CRIU: &str = "--criu";
|
||||
pub const DEBUG: &str = "--debug";
|
||||
pub const DETACH: &str = "--detach";
|
||||
pub const FORCE: &str = "--force";
|
||||
pub const LOG: &str = "--log";
|
||||
pub const LOG_FORMAT: &str = "--log-format";
|
||||
pub const NO_NEW_KEYRING: &str = "--no-new-keyring";
|
||||
pub const NO_PIVOT: &str = "--no-pivot";
|
||||
pub const PID_FILE: &str = "--pid-file";
|
||||
pub const ROOT: &str = "--root";
|
||||
pub const ROOTLESS: &str = "--rootless";
|
||||
pub const SYSTEMD_CGROUP: &str = "--systemd-cgroup";
|
||||
|
||||
// constants for log format
|
||||
pub const JSON: &str = "json";
|
||||
pub const TEXT: &str = "text";
|
||||
|
||||
// constant for command
|
||||
pub const DEFAULT_COMMAND: &str = "runc";
|
||||
|
||||
// helper to resolve path (such as path for runc binary, pid files, etc. )
|
||||
pub fn abs_path_buf<P>(path: P) -> Result<PathBuf, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(path
|
||||
.as_ref()
|
||||
.absolutize()
|
||||
.map_err(Error::InvalidPath)?
|
||||
.to_path_buf())
|
||||
}
|
||||
|
||||
pub fn abs_string<P>(path: P) -> Result<String, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(abs_path_buf(path)?
|
||||
.to_string_lossy()
|
||||
.parse::<String>()
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
pub fn temp_filename_in_runtime_dir() -> Result<String, Error> {
|
||||
env::var_os("XDG_RUNTIME_DIR")
|
||||
.map(|runtime_dir| {
|
||||
format!(
|
||||
"{}/runc-process-{}",
|
||||
runtime_dir.to_string_lossy().parse::<String>().unwrap(),
|
||||
Uuid::new_v4(),
|
||||
)
|
||||
})
|
||||
.ok_or(Error::SpecFileNotFound)
|
||||
}
|
||||
|
||||
pub fn make_temp_file_in_runtime_dir() -> Result<(NamedTempFile, String), Error> {
|
||||
let file_name = env::var_os("XDG_RUNTIME_DIR")
|
||||
.map(|runtime_dir| {
|
||||
format!(
|
||||
"{}/runc-process-{}",
|
||||
runtime_dir.to_string_lossy().parse::<String>().unwrap(),
|
||||
Uuid::new_v4(),
|
||||
)
|
||||
})
|
||||
.ok_or(Error::SpecFileNotFound)?;
|
||||
let temp_file = Builder::new()
|
||||
.prefix(&file_name)
|
||||
.tempfile()
|
||||
.map_err(Error::SpecFileCreationFailed)?;
|
||||
Ok((temp_file, file_name))
|
||||
}
|
||||
|
||||
pub fn binary_path<P>(path: P) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
env::var_os("PATH").and_then(|paths| {
|
||||
env::split_paths(&paths).find_map(|dir| {
|
||||
let full_path = dir.join(path.as_ref());
|
||||
if full_path.is_file() {
|
||||
Some(full_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct Metrics {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct ContainerCreate {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct ContentDelete {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct ImageCreate {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct Mount {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct NamespaceCreate {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct SnapshotPrepare {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct TaskCreate {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct Empty {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct ForwardRequest {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct Mount {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct CreateTaskRequest {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// This file is generated by rust-protobuf 2.27.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_27_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct Process {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ pub enum Error {
|
|||
MissingArg(String),
|
||||
/// Syntax error.
|
||||
#[error("Parse failed: {0}")]
|
||||
ParseError(String),
|
||||
ParseFailed(String),
|
||||
}
|
||||
|
||||
/// Parses command line arguments passed to the shim.
|
||||
|
|
@ -72,7 +72,7 @@ pub fn parse<S: AsRef<OsStr>>(args: &[S]) -> Result<Flags, Error> {
|
|||
FlagError::BadFlag { flag } => Error::InvalidArg(flag),
|
||||
FlagError::UnknownFlag { name } => Error::InvalidArg(name),
|
||||
FlagError::ArgumentNeeded { name } => Error::MissingArg(name),
|
||||
FlagError::ParseError { error } => Error::ParseError(format!("{:?}", error)),
|
||||
FlagError::ParseError { error } => Error::ParseFailed(format!("{:?}", error)),
|
||||
})?;
|
||||
|
||||
if let Some(action) = args.get(0) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "1.52"
|
||||
channel = "1.54"
|
||||
components = ["rustfmt", "clippy"]
|
||||
|
|
|
|||
Loading…
Reference in New Issue