Skip to content

Commit 1b3581d

Browse files
Merge branch 'master' into patch-1
2 parents 23fc300 + 3a7c1dc commit 1b3581d

10 files changed

+298
-31
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* rename `shiplift::rep::Config` to `shiplift::rep::ContainerConfig` [#264](https://github.com/softprops/shiplift/pull/264)
77
* add missing fields ([API version 1.41](https://docs.docker.com/engine/api/v1.41/#operation/ImageInspect)) to `ContainerConfig` [#264](https://github.com/softprops/shiplift/pull/264)
88
* add missing fields ([API version 1.41](https://docs.docker.com/engine/api/v1.41/#operation/ImageHistory)) to `History` [#264](https://github.com/softprops/shiplift/pull/264)
9+
* add missing fields to `NetworkEntry` [#254](https://github.com/softprops/shiplift/pull/254)
910

1011
# 0.7.0
1112

rustfmt.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#fn_args_layout
22
fn_args_layout = "Vertical"
3-
# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports
4-
merge_imports = true
3+
# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#imports_granularity
4+
imports_granularity="Crate"

src/container.rs

+140-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::{
2020
errors::{Error, Result},
2121
exec::{Exec, ExecContainerOptions},
2222
image::Config,
23-
network::{NetworkInfo, NetworkSettings},
23+
network::NetworkSettings,
2424
transport::Payload,
2525
tty::{self, Multiplexer as TtyMultiPlexer},
2626
};
@@ -31,6 +31,8 @@ use crate::datetime::datetime_from_unix_timestamp;
3131
use chrono::{DateTime, Utc};
3232

3333
/// Interface for accessing and manipulating a docker container
34+
///
35+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Container)
3436
pub struct Container<'docker> {
3537
docker: &'docker Docker,
3638
id: String,
@@ -57,13 +59,17 @@ impl<'docker> Container<'docker> {
5759
}
5860

5961
/// Inspects the current docker container instance's details
62+
///
63+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerInspect)
6064
pub async fn inspect(&self) -> Result<ContainerDetails> {
6165
self.docker
6266
.get_json::<ContainerDetails>(&format!("/containers/{}/json", self.id)[..])
6367
.await
6468
}
6569

6670
/// Returns a `top` view of information about the container process
71+
///
72+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerTop)
6773
pub async fn top(
6874
&self,
6975
psargs: Option<&str>,
@@ -79,6 +85,8 @@ impl<'docker> Container<'docker> {
7985
}
8086

8187
/// Returns a stream of logs emitted but the container instance
88+
///
89+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerLogs)
8290
pub fn logs(
8391
&self,
8492
opts: &LogsOptions,
@@ -106,32 +114,42 @@ impl<'docker> Container<'docker> {
106114
.await
107115
}
108116

109-
/// Attaches a `[TtyMultiplexer]` to the container.
117+
/// Attaches a [Multiplexer](crate::tty::Multiplexer) to the container.
118+
///
119+
/// The [Multiplexer](crate::tty::Multiplexer) implements Stream for returning Stdout and
120+
/// Stderr chunks. It also implements `[AsyncWrite]` for writing to Stdin.
110121
///
111-
/// The `[TtyMultiplexer]` implements Stream for returning Stdout and Stderr chunks. It also implements `[AsyncWrite]` for writing to Stdin.
122+
/// The multiplexer can be split into its read and write halves with the
123+
/// [split](crate::tty::Multiplexer::split) method
112124
///
113-
/// The multiplexer can be split into its read and write halves with the `[split](TtyMultiplexer::split)` method
125+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach)
114126
pub async fn attach(&self) -> Result<TtyMultiPlexer<'docker>> {
115127
let tcp_stream = self.attach_raw().await?;
116128

117129
Ok(TtyMultiPlexer::new(tcp_stream))
118130
}
119131

120132
/// Returns a set of changes made to the container instance
133+
///
134+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerChanges)
121135
pub async fn changes(&self) -> Result<Vec<Change>> {
122136
self.docker
123137
.get_json::<Vec<Change>>(&format!("/containers/{}/changes", self.id)[..])
124138
.await
125139
}
126140

127141
/// Exports the current docker container into a tarball
142+
///
143+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerExport)
128144
pub fn export(&self) -> impl Stream<Item = Result<Vec<u8>>> + 'docker {
129145
self.docker
130146
.stream_get(format!("/containers/{}/export", self.id))
131147
.map_ok(|c| c.to_vec())
132148
}
133149

134150
/// Returns a stream of stats specific to this container instance
151+
///
152+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStats)
135153
pub fn stats(&self) -> impl Stream<Item = Result<Stats>> + Unpin + 'docker {
136154
let codec = futures_codec::LinesCodec {};
137155

@@ -152,6 +170,8 @@ impl<'docker> Container<'docker> {
152170
}
153171

154172
/// Start the container instance
173+
///
174+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStart)
155175
pub async fn start(&self) -> Result<()> {
156176
self.docker
157177
.post(&format!("/containers/{}/start", self.id)[..], None)
@@ -160,6 +180,8 @@ impl<'docker> Container<'docker> {
160180
}
161181

162182
/// Stop the container instance
183+
///
184+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStop)
163185
pub async fn stop(
164186
&self,
165187
wait: Option<Duration>,
@@ -177,6 +199,8 @@ impl<'docker> Container<'docker> {
177199
}
178200

179201
/// Restart the container instance
202+
///
203+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRestart)
180204
pub async fn restart(
181205
&self,
182206
wait: Option<Duration>,
@@ -193,6 +217,8 @@ impl<'docker> Container<'docker> {
193217
}
194218

195219
/// Kill the container instance
220+
///
221+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerKill)
196222
pub async fn kill(
197223
&self,
198224
signal: Option<&str>,
@@ -209,6 +235,8 @@ impl<'docker> Container<'docker> {
209235
}
210236

211237
/// Rename the container instance
238+
///
239+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRename)
212240
pub async fn rename(
213241
&self,
214242
name: &str,
@@ -226,6 +254,8 @@ impl<'docker> Container<'docker> {
226254
}
227255

228256
/// Pause the container instance
257+
///
258+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerPause)
229259
pub async fn pause(&self) -> Result<()> {
230260
self.docker
231261
.post(&format!("/containers/{}/pause", self.id)[..], None)
@@ -234,6 +264,8 @@ impl<'docker> Container<'docker> {
234264
}
235265

236266
/// Unpause the container instance
267+
///
268+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerUnpause)
237269
pub async fn unpause(&self) -> Result<()> {
238270
self.docker
239271
.post(&format!("/containers/{}/unpause", self.id)[..], None)
@@ -242,6 +274,8 @@ impl<'docker> Container<'docker> {
242274
}
243275

244276
/// Wait until the container stops
277+
///
278+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerWait)
245279
pub async fn wait(&self) -> Result<Exit> {
246280
self.docker
247281
.post_json(format!("/containers/{}/wait", self.id), Payload::None)
@@ -251,6 +285,8 @@ impl<'docker> Container<'docker> {
251285
/// Delete the container instance
252286
///
253287
/// Use remove instead to use the force/v options.
288+
///
289+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerDelete)
254290
pub async fn delete(&self) -> Result<()> {
255291
self.docker
256292
.delete(&format!("/containers/{}", self.id)[..])
@@ -259,6 +295,8 @@ impl<'docker> Container<'docker> {
259295
}
260296

261297
/// Delete the container instance (todo: force/v)
298+
///
299+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRemove)
262300
pub async fn remove(
263301
&self,
264302
opts: RmContainerOptions,
@@ -272,6 +310,8 @@ impl<'docker> Container<'docker> {
272310
}
273311

274312
/// Execute a command in this container
313+
///
314+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Exec)
275315
pub fn exec(
276316
&self,
277317
opts: &ExecContainerOptions,
@@ -287,6 +327,8 @@ impl<'docker> Container<'docker> {
287327
/// directory, `path` should end in `/` or `/`. (assuming a path separator of `/`). If `path`
288328
/// ends in `/.` then this indicates that only the contents of the path directory should be
289329
/// copied. A symlink is always resolved to its target.
330+
///
331+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerArchive)
290332
pub fn copy_from(
291333
&self,
292334
path: &Path,
@@ -303,6 +345,8 @@ impl<'docker> Container<'docker> {
303345
///
304346
/// The file will be copied at the given location (see `path`) and will be owned by root
305347
/// with access mask 644.
348+
///
349+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/PutContainerArchive)
306350
pub async fn copy_file_into<P: AsRef<Path>>(
307351
&self,
308352
path: P,
@@ -332,6 +376,8 @@ impl<'docker> Container<'docker> {
332376
/// Copy a tarball (see `body`) to the container.
333377
///
334378
/// The tarball will be copied to the container and extracted at the given location (see `path`).
379+
///
380+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/PutContainerArchive)
335381
pub async fn copy_to(
336382
&self,
337383
path: &Path,
@@ -354,6 +400,8 @@ impl<'docker> Container<'docker> {
354400
}
355401

356402
/// Interface for docker containers
403+
///
404+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Containers)
357405
pub struct Containers<'docker> {
358406
docker: &'docker Docker,
359407
}
@@ -365,6 +413,8 @@ impl<'docker> Containers<'docker> {
365413
}
366414

367415
/// Lists the container instances on the docker host
416+
///
417+
/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerList)
368418
pub async fn list(
369419
&self,
370420
opts: &ContainerListOptions,
@@ -457,15 +507,17 @@ impl ContainerListOptionsBuilder {
457507
&mut self,
458508
filters: Vec<ContainerFilter>,
459509
) -> &mut Self {
460-
let mut param = HashMap::new();
510+
let mut param: HashMap<&str, Vec<String>> = HashMap::new();
461511
for f in filters {
462-
match f {
463-
ContainerFilter::ExitCode(c) => param.insert("exit", vec![c.to_string()]),
464-
ContainerFilter::Status(s) => param.insert("status", vec![s]),
465-
ContainerFilter::LabelName(n) => param.insert("label", vec![n]),
466-
ContainerFilter::Label(n, v) => param.insert("label", vec![format!("{}={}", n, v)]),
467-
ContainerFilter::Name(n) => param.insert("name", vec![n]),
512+
let (key, value) = match f {
513+
ContainerFilter::ExitCode(c) => ("exited", c.to_string()),
514+
ContainerFilter::Status(s) => ("status", s),
515+
ContainerFilter::LabelName(n) => ("label", n),
516+
ContainerFilter::Label(n, v) => ("label", format!("{}={}", n, v)),
517+
ContainerFilter::Name(n) => ("name", n.to_string()),
468518
};
519+
520+
param.entry(key).or_insert_with(Vec::new).push(value);
469521
}
470522
// structure is a a json encoded object mapping string keys to a list
471523
// of string values
@@ -1242,12 +1294,24 @@ pub struct Port {
12421294
#[derive(Clone, Debug, Serialize, Deserialize)]
12431295
pub struct Stats {
12441296
pub read: String,
1245-
pub networks: HashMap<String, NetworkInfo>,
1297+
pub networks: HashMap<String, NetworkStats>,
12461298
pub memory_stats: MemoryStats,
12471299
pub blkio_stats: BlkioStats,
12481300
pub cpu_stats: CpuStats,
12491301
}
12501302

1303+
#[derive(Clone, Debug, Serialize, Deserialize)]
1304+
pub struct NetworkStats {
1305+
pub rx_dropped: u64,
1306+
pub rx_bytes: u64,
1307+
pub rx_errors: u64,
1308+
pub tx_packets: u64,
1309+
pub tx_dropped: u64,
1310+
pub rx_packets: u64,
1311+
pub tx_errors: u64,
1312+
pub tx_bytes: u64,
1313+
}
1314+
12511315
#[derive(Clone, Debug, Serialize, Deserialize)]
12521316
pub struct MemoryStats {
12531317
pub max_usage: u64,
@@ -1363,6 +1427,7 @@ pub struct Exit {
13631427
#[cfg(test)]
13641428
mod tests {
13651429
use super::*;
1430+
use crate::container::ContainerFilter::{ExitCode, Label, LabelName, Status};
13661431

13671432
#[test]
13681433
fn container_options_simple() {
@@ -1516,6 +1581,69 @@ mod tests {
15161581
);
15171582
}
15181583

1584+
#[test]
1585+
fn container_list_options_multiple_labels() {
1586+
let options = ContainerListOptions::builder()
1587+
.filter(vec![
1588+
Label("label1".to_string(), "value".to_string()),
1589+
LabelName("label2".to_string()),
1590+
])
1591+
.build();
1592+
1593+
let form = form_urlencoded::Serializer::new(String::new())
1594+
.append_pair("filters", r#"{"label":["label1=value","label2"]}"#)
1595+
.finish();
1596+
1597+
assert_eq!(form, options.serialize().unwrap())
1598+
}
1599+
1600+
#[test]
1601+
fn container_list_options_exit_code() {
1602+
let options = ContainerListOptions::builder()
1603+
.filter(vec![ExitCode(0)])
1604+
.build();
1605+
1606+
let form = form_urlencoded::Serializer::new(String::new())
1607+
.append_pair("filters", r#"{"exited":["0"]}"#)
1608+
.finish();
1609+
1610+
assert_eq!(form, options.serialize().unwrap())
1611+
}
1612+
1613+
#[test]
1614+
fn container_list_options_status() {
1615+
let options = ContainerListOptions::builder()
1616+
.filter(vec![Status("running".to_string())])
1617+
.build();
1618+
1619+
let form = form_urlencoded::Serializer::new(String::new())
1620+
.append_pair("filters", r#"{"status":["running"]}"#)
1621+
.finish();
1622+
1623+
assert_eq!(form, options.serialize().unwrap())
1624+
}
1625+
1626+
#[test]
1627+
fn container_list_options_combined() {
1628+
let options = ContainerListOptions::builder()
1629+
.all()
1630+
.filter(vec![
1631+
Label("label1".to_string(), "value".to_string()),
1632+
LabelName("label2".to_string()),
1633+
ExitCode(0),
1634+
Status("running".to_string()),
1635+
])
1636+
.build();
1637+
1638+
let serialized = options.serialize().unwrap();
1639+
1640+
assert!(serialized.contains("all=true"));
1641+
assert!(serialized.contains("filters="));
1642+
assert!(serialized.contains("%22label%22%3A%5B%22label1%3Dvalue%22%2C%22label2%22%5D"));
1643+
assert!(serialized.contains("%22status%22%3A%5B%22running%22%5D"));
1644+
assert!(serialized.contains("%22exited%22%3A%5B%220%22%5D"));
1645+
}
1646+
15191647
#[cfg(feature = "chrono")]
15201648
#[test]
15211649
fn logs_options() {

0 commit comments

Comments
 (0)