diff --git a/.circleci/config.yml b/.circleci/config.yml index 832dca83..d75559c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,7 +29,23 @@ jobs: - checkout - setup_remote_docker - run: docker build -t bollard . - - run: dockerfiles/bin/run_integration_tests.sh + - run: dockerfiles/bin/run_integration_tests.sh --tests + test_chrono: + docker: + - image: docker:20.10.16 + steps: + - checkout + - setup_remote_docker + - run: docker build -t bollard . + - run: dockerfiles/bin/run_integration_tests.sh --features chrono --tests + test_time: + docker: + - image: docker:20.10.16 + steps: + - checkout + - setup_remote_docker + - run: docker build -t bollard . + - run: dockerfiles/bin/run_integration_tests.sh --features time --tests test_doc: docker: - image: docker:20.10.16 @@ -37,7 +53,7 @@ jobs: - checkout - setup_remote_docker - run: docker build -t bollard . - - run: docker run -ti --rm bollard cargo test --target x86_64-unknown-linux-gnu --doc + - run: docker run -ti --rm bollard cargo test --features time --target x86_64-unknown-linux-gnu --doc test_clippy: docker: - image: docker:20.10.16 @@ -62,7 +78,7 @@ jobs: - checkout - setup_remote_docker - run: docker build -t bollard . - - run: docker run -ti --rm bollard bash -c "cargo fmt -- --check --verbose" + - run: docker run -ti --rm bollard bash -c "rustup component add rustfmt && cargo fmt -- --check --verbose" workflows: version: 2 test-image: @@ -70,6 +86,8 @@ workflows: - test_ssl - test_http - test_unix + - test_chrono + - test_time - test_doc - test_clippy - test_audit diff --git a/.dockerignore b/.dockerignore index ee35a944..89e45807 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,8 @@ .DS_Store .git Cargo.lock -**/target +/target +/codegen/target/generated-sources/target doc main.rs **/workspace diff --git a/Cargo.toml b/Cargo.toml index dd1ef330..d88892dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/fussybeaver/bollard" documentation = "https://docs.rs/bollard" readme = "README.md" keywords = ["docker"] -edition = "2018" +edition = "2021" [features] # Enable tests specifically for the http connector @@ -21,12 +21,14 @@ test_macos = [] # Enable rustls / ssl ssl = ["dirs-next", "hyper-rustls", "rustls", "rustls-native-certs", "rustls-pemfile", "webpki", "webpki-roots"] ct_logs = ["ssl", "ct-logs"] +chrono = ["dep:chrono", "bollard-stubs/chrono"] +time = ["dep:time", "bollard-stubs/time"] [dependencies] base64 = "0.13" -bollard-stubs = { version = "=1.42.0-rc.2" } +bollard-stubs = { version = "=1.42.0-rc.3", default-features = false } bytes = "1" -chrono = { version = "0.4", features = ["serde"] } +chrono = { version = "0.4", features = ["serde"], optional = true } ct-logs = { version = "0.9.0", optional = true } dirs-next = { version = "2.0", optional = true } futures-core = "0.3" @@ -46,6 +48,7 @@ serde_json = "1.0" serde_urlencoded = "0.7" tokio = { version = "1.7", features = ["time", "net", "io-util"] } thiserror = "1.0" +time = { version = "0.3", features = ["formatting", "parsing"], optional = true } tokio-util = { version = "0.7", features = ["codec"] } url = "2.2" webpki-roots = { version = "0.22", optional = true } diff --git a/Dockerfile b/Dockerfile index fd36eaec..88477aaf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,7 @@ -FROM ekidd/rust-musl-builder:stable AS builder +FROM rust:1.61.0-buster -WORKDIR /tmp/bollard +WORKDIR /usr/src/bollard -COPY . ./ - -RUN sudo chown -R rust:rust /tmp/bollard \ - && sudo groupadd --gid 999 docker \ - && sudo usermod -a -G docker rust +COPY . . RUN cargo build diff --git a/appveyor.yml b/appveyor.yml index 36d7cb4a..b7b23931 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,4 +40,4 @@ test_script: - ps: Set-Item -path env:RUST_BACKTRACE -value 1 - ps: Set-Item -path env:RUST_LOG -value "hyper=trace,bollard=debug" - ps: Set-Item -path env:REGISTRY_HTTP_ADDR -value localhost:5000 - - cargo test --verbose -- --nocapture --test-threads 1 + - cargo test --verbose --tests -- --nocapture --test-threads 1 diff --git a/codegen/pom.xml b/codegen/pom.xml index 6c73d3e2..c785764f 100644 --- a/codegen/pom.xml +++ b/codegen/pom.xml @@ -31,7 +31,7 @@ models.rs,lib.rs,Cargo.toml,config,README.md bollard-stubs - 1.42.0-rc.2 + 1.42.0-rc.3 diff --git a/codegen/src/main/java/bollard/BollardCodegen.java b/codegen/src/main/java/bollard/BollardCodegen.java index 9a526bb5..616623f1 100644 --- a/codegen/src/main/java/bollard/BollardCodegen.java +++ b/codegen/src/main/java/bollard/BollardCodegen.java @@ -22,6 +22,7 @@ public class BollardCodegen extends RustServerCodegen { public BollardCodegen() { super(); + typeMapping.put("DateTime", "BollardDate"); } // Declare custom additions to inline enums that are behaving differently @@ -123,9 +124,10 @@ public Map postProcessAllModels(Map objs) { } else if (prop.name.equals("_type")) { prop.name = "typ"; } - if (prop.dataFormat != null && prop.dataFormat.equals("dateTime")) { + if (prop.dataFormat != null && (prop.dataFormat.equals("dateTime") || prop.datatype.equals("BollardDate"))) { // set DateTime format on properties where appropriate - prop.datatype = "DateTime"; + prop.vendorExtensions.put("x-rustgen-is-datetime", true); + prop.datatype = "BollardDate"; } if (prop.isEnum) { if (enumToString.contains(model.classname)) { diff --git a/codegen/src/main/resources/bollard/Cargo.mustache b/codegen/src/main/resources/bollard/Cargo.mustache index d71d9a1c..b4b49b58 100644 --- a/codegen/src/main/resources/bollard/Cargo.mustache +++ b/codegen/src/main/resources/bollard/Cargo.mustache @@ -4,10 +4,11 @@ version = "{{appVersion}}" authors = [ "Bollard contributors" ] description = "Stubs used for the Bollard rust async Docker client API" license = "Apache-2.0" -edition = "2018" +edition = "2021" [dependencies] -chrono = { version = "0.4", features = ["serde"] } +chrono = { version = "0.4", features = ["serde"], optional = true } serde = { version = "1.0", features = ["derive"] } +time = { version = "0.3", features = ["formatting", "parsing"], optional = true } serde_with = "1.4" diff --git a/codegen/src/main/resources/bollard/models.mustache b/codegen/src/main/resources/bollard/models.mustache index 6a932c77..76f7d1ee 100644 --- a/codegen/src/main/resources/bollard/models.mustache +++ b/codegen/src/main/resources/bollard/models.mustache @@ -9,9 +9,6 @@ use std::collections::HashMap; use std::default::Default; use std::hash::Hash; -use chrono::DateTime; -use chrono::Utc; - fn deserialize_nonoptional_vec<'de, D: Deserializer<'de>, T: DeserializeOwned>( d: D, ) -> Result, D::Error> { @@ -24,6 +21,52 @@ fn deserialize_nonoptional_map<'de, D: Deserializer<'de>, T: DeserializeOwned>( serde::Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or(HashMap::new())) } +#[cfg(feature = "time")] +pub type BollardDate = time::OffsetDateTime; +#[cfg(feature = "chrono")] +pub type BollardDate = chrono::DateTime; +#[cfg(not(any(feature = "chrono", feature = "time")))] +pub type BollardDate = String; + +#[cfg(feature = "time")] +fn deserialize_timestamp<'de, D: Deserializer<'de>>( + d: D +) -> Result, D::Error> { + let opt: Option = serde::Deserialize::deserialize(d)?; + if let Some(s) = opt { + Ok(Some( + time::OffsetDateTime::parse(&s, &time::format_description::well_known::Rfc3339) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?, + )) + } else { + Ok(None) + } +} + +#[cfg(not(feature = "time"))] +fn deserialize_timestamp<'de, D: Deserializer<'de>>( + d: D +) -> Result, D::Error> { + serde::Deserialize::deserialize(d) +} + +#[cfg(feature = "time")] +fn serialize_timestamp(date: &Option, s: S) -> Result { + match date { + Some(inner) => Ok(s.serialize_str(&inner.format(&time::format_description::well_known::Rfc3339) + .map_err(|e| serde::ser::Error::custom(format!("{:?}", e)))?)?), + None => Ok(s.serialize_str("")?) + } +} + +#[cfg(not(feature = "time"))] +fn serialize_timestamp(date: &Option, s: S) -> Result { + match date { + Some(inner) => s.serialize_some(inner), + None => s.serialize_none() + } +} + {{#models}}{{#model}} {{#description}}/// {{{description}}} {{/description}}{{#isEnum}}/// Enumeration of values. @@ -68,10 +111,12 @@ pub struct {{classname}} { #[serde(deserialize_with = "deserialize_nonoptional_map")]{{/isListContainer}}{{#isListContainer}} #[serde(deserialize_with = "deserialize_nonoptional_vec")]{{/isListContainer}}{{/isContainer}}{{#isEnum}} #[serde(skip_serializing_if="Option::is_none")] - #[serde(with = "serde_with::rust::string_empty_as_none")]{{/isEnum}} + #[serde(with = "serde_with::rust::string_empty_as_none")]{{/isEnum}}{{#vendorExtensions.x-rustgen-is-datetime}} + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")]{{/vendorExtensions.x-rustgen-is-datetime}} pub {{name}}: {{#isEnum}}Option<{{classname}}{{enumName}}>{{/isEnum}}{{^isEnum}}{{#isListContainer}}Vec<{{#items}}{{{datatype}}}{{/items}}>{{/isListContainer}}{{^isListContainer}}{{#isContainer}}HashMap{{/isContainer}}{{^isContainer}}{{{datatype}}}{{/isContainer}}{{/isListContainer}}{{/isEnum}}{{#vendorExtensions}}{{/vendorExtensions}}, {{/required}}{{^required}} - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if="Option::is_none")]{{#vendorExtensions.x-rustgen-is-datetime}} + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")]{{/vendorExtensions.x-rustgen-is-datetime}} pub {{name}}: Option<{{#isEnum}}{{classname}}{{enumName}}{{/isEnum}}{{^isEnum}}{{#isListContainer}}Vec<{{#items}}{{{datatype}}}{{/items}}>{{/isListContainer}}{{^isListContainer}}{{#isContainer}}HashMap{{/isContainer}}{{^isContainer}}{{{datatype}}}{{/isContainer}}{{/isListContainer}}{{/isEnum}}{{#vendorExtensions}}{{/vendorExtensions}}>, {{/required}} diff --git a/codegen/target/generated-sources/Cargo.toml b/codegen/target/generated-sources/Cargo.toml index 099feabd..d9fe2924 100644 --- a/codegen/target/generated-sources/Cargo.toml +++ b/codegen/target/generated-sources/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "bollard-stubs" -version = "1.42.0-rc.2" +version = "1.42.0-rc.3" authors = [ "Bollard contributors" ] description = "Stubs used for the Bollard rust async Docker client API" license = "Apache-2.0" -edition = "2018" +edition = "2021" [dependencies] -chrono = { version = "0.4", features = ["serde"] } +chrono = { version = "0.4", features = ["serde"], optional = true } serde = { version = "1.0", features = ["derive"] } +time = { version = "0.3", features = ["formatting", "parsing"], optional = true } serde_with = "1.4" diff --git a/codegen/target/generated-sources/README.md b/codegen/target/generated-sources/README.md index 418bbcb1..595462c8 100644 --- a/codegen/target/generated-sources/README.md +++ b/codegen/target/generated-sources/README.md @@ -7,9 +7,9 @@ To see how to make this your own, look here: [README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md) -- API version: 1.42.0-rc.2 -- Code generation suffix: 1.42.0-rc.2 -- Build date: 2022-05-31T18:04:02.348+01:00 +- API version: 1.42.0-rc.3 +- Code generation suffix: 1.42.0-rc.3 +- Build date: 2022-06-18T09:45:11.451+01:00 This autogenerated project defines an API crate `bollard-stubs` which contains: * Data types representing the underlying data model. diff --git a/codegen/target/generated-sources/src/models.rs b/codegen/target/generated-sources/src/models.rs index ecf128b6..28b073f8 100644 --- a/codegen/target/generated-sources/src/models.rs +++ b/codegen/target/generated-sources/src/models.rs @@ -9,9 +9,6 @@ use std::collections::HashMap; use std::default::Default; use std::hash::Hash; -use chrono::DateTime; -use chrono::Utc; - fn deserialize_nonoptional_vec<'de, D: Deserializer<'de>, T: DeserializeOwned>( d: D, ) -> Result, D::Error> { @@ -24,6 +21,52 @@ fn deserialize_nonoptional_map<'de, D: Deserializer<'de>, T: DeserializeOwned>( serde::Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or(HashMap::new())) } +#[cfg(feature = "time")] +pub type BollardDate = time::OffsetDateTime; +#[cfg(feature = "chrono")] +pub type BollardDate = chrono::DateTime; +#[cfg(not(any(feature = "chrono", feature = "time")))] +pub type BollardDate = String; + +#[cfg(feature = "time")] +fn deserialize_timestamp<'de, D: Deserializer<'de>>( + d: D +) -> Result, D::Error> { + let opt: Option = serde::Deserialize::deserialize(d)?; + if let Some(s) = opt { + Ok(Some( + time::OffsetDateTime::parse(&s, &time::format_description::well_known::Rfc3339) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?, + )) + } else { + Ok(None) + } +} + +#[cfg(not(feature = "time"))] +fn deserialize_timestamp<'de, D: Deserializer<'de>>( + d: D +) -> Result, D::Error> { + serde::Deserialize::deserialize(d) +} + +#[cfg(feature = "time")] +fn serialize_timestamp(date: &Option, s: S) -> Result { + match date { + Some(inner) => Ok(s.serialize_str(&inner.format(&time::format_description::well_known::Rfc3339) + .map_err(|e| serde::ser::Error::custom(format!("{:?}", e)))?)?), + None => Ok(s.serialize_str("")?) + } +} + +#[cfg(not(feature = "time"))] +fn serialize_timestamp(date: &Option, s: S) -> Result { + match date { + Some(inner) => s.serialize_some(inner), + None => s.serialize_none() + } +} + /// Address represents an IPv4 or IPv6 IP address. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] @@ -94,12 +137,14 @@ pub struct BuildCache { /// Date and time at which the build cache was created in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, /// Date and time at which the build cache was last used in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "LastUsedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub last_used_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub last_used_at: Option, #[serde(rename = "UsageCount")] #[serde(skip_serializing_if="Option::is_none")] @@ -171,12 +216,14 @@ pub struct ClusterInfo { /// Date and time at which the swarm was initialised in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, /// Date and time at which the swarm was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "UpdatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub updated_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub updated_at: Option, #[serde(rename = "Spec")] #[serde(skip_serializing_if="Option::is_none")] @@ -235,11 +282,13 @@ pub struct Config { #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, #[serde(rename = "UpdatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub updated_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub updated_at: Option, #[serde(rename = "Spec")] #[serde(skip_serializing_if="Option::is_none")] @@ -1695,12 +1744,14 @@ pub struct HealthcheckResult { /// Date and time at which this check started in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "Start")] #[serde(skip_serializing_if="Option::is_none")] - pub start: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub start: Option, /// Date and time at which this check ended in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "End")] #[serde(skip_serializing_if="Option::is_none")] - pub end: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub end: Option, /// ExitCode meanings: - `0` healthy - `1` unhealthy - `2` reserved (considered unhealthy) - other values: error running probe #[serde(rename = "ExitCode")] @@ -2334,7 +2385,8 @@ pub struct ImageInspectMetadata { /// Date and time at which the image was last tagged in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. This information is only available if the image was tagged locally, and omitted otherwise. #[serde(rename = "LastTagTime")] #[serde(skip_serializing_if="Option::is_none")] - pub last_tag_time: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub last_tag_time: Option, } @@ -2934,7 +2986,8 @@ pub struct Network { #[serde(rename = "Created")] #[serde(skip_serializing_if="Option::is_none")] - pub created: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created: Option, #[serde(rename = "Scope")] #[serde(skip_serializing_if="Option::is_none")] @@ -3241,12 +3294,14 @@ pub struct Node { /// Date and time at which the node was added to the swarm in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, /// Date and time at which the node was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "UpdatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub updated_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub updated_at: Option, #[serde(rename = "Spec")] #[serde(skip_serializing_if="Option::is_none")] @@ -4425,11 +4480,13 @@ pub struct Secret { #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, #[serde(rename = "UpdatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub updated_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub updated_at: Option, #[serde(rename = "Spec")] #[serde(skip_serializing_if="Option::is_none")] @@ -4478,11 +4535,13 @@ pub struct Service { #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, #[serde(rename = "UpdatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub updated_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub updated_at: Option, #[serde(rename = "Spec")] #[serde(skip_serializing_if="Option::is_none")] @@ -4559,7 +4618,8 @@ pub struct ServiceJobStatus { /// The last time, as observed by the server, that this job was started. #[serde(rename = "LastExecution")] #[serde(skip_serializing_if="Option::is_none")] - pub last_execution: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub last_execution: Option, } @@ -4937,11 +4997,13 @@ pub struct ServiceUpdateStatus { #[serde(rename = "StartedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub started_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub started_at: Option, #[serde(rename = "CompletedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub completed_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub completed_at: Option, #[serde(rename = "Message")] #[serde(skip_serializing_if="Option::is_none")] @@ -5027,12 +5089,14 @@ pub struct Swarm { /// Date and time at which the swarm was initialised in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, /// Date and time at which the swarm was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds. #[serde(rename = "UpdatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub updated_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub updated_at: Option, #[serde(rename = "Spec")] #[serde(skip_serializing_if="Option::is_none")] @@ -6017,11 +6081,13 @@ pub struct Task { #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, #[serde(rename = "UpdatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub updated_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub updated_at: Option, /// Name of the task. #[serde(rename = "Name")] @@ -6744,7 +6810,8 @@ impl ::std::str::FromStr for TaskState { pub struct TaskStatus { #[serde(rename = "Timestamp")] #[serde(skip_serializing_if="Option::is_none")] - pub timestamp: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub timestamp: Option, #[serde(rename = "State")] #[serde(skip_serializing_if="Option::is_none")] @@ -6840,7 +6907,8 @@ pub struct Volume { /// Date/Time the volume was created. #[serde(rename = "CreatedAt")] #[serde(skip_serializing_if="Option::is_none")] - pub created_at: Option>, + #[serde(default, deserialize_with = "deserialize_timestamp", serialize_with = "serialize_timestamp")] + pub created_at: Option, /// Low-level details about the volume, provided by the volume driver. Details are returned as a map with key/value pairs: `{\"key\":\"value\",\"key2\":\"value2\"}`. The `Status` field is optional, and is omitted if the volume driver does not support this feature. #[serde(rename = "Status")] diff --git a/dockerfiles/bin/run_integration_tests.sh b/dockerfiles/bin/run_integration_tests.sh index 73c744ae..fa9d04bc 100755 --- a/dockerfiles/bin/run_integration_tests.sh +++ b/dockerfiles/bin/run_integration_tests.sh @@ -20,4 +20,4 @@ docker push localhost:5000/hello-world:linux docker push localhost:5000/fussybeaver/uhttpd docker push localhost:5000/alpine docker swarm init -docker run -e RUST_LOG=bollard=debug -e REGISTRY_PASSWORD -e REGISTRY_HTTP_ADDR=localhost:5000 -v /var/run/docker.sock:/var/run/docker.sock -ti --rm bollard cargo test -- --test-threads 1 +docker run -e RUST_LOG=bollard=debug -e REGISTRY_PASSWORD -e REGISTRY_HTTP_ADDR=localhost:5000 -v /var/run/docker.sock:/var/run/docker.sock -ti --rm bollard cargo test $@ -- --test-threads 1 diff --git a/examples/hoover.rs b/examples/hoover.rs index a20e4d74..5b74166f 100644 --- a/examples/hoover.rs +++ b/examples/hoover.rs @@ -5,8 +5,6 @@ use bollard::{ container::PruneContainersOptions, image::PruneImagesOptions, network::PruneNetworksOptions, volume::PruneVolumesOptions, }; -use chrono::{Duration, Utc}; - use std::collections::HashMap; const THRESHOLD_DAYS: i64 = 90; @@ -15,8 +13,29 @@ const THRESHOLD_DAYS: i64 = 90; async fn main() -> Result<(), Box> { let docker = Docker::connect_with_socket_defaults().unwrap(); - let date = Utc::now() - Duration::days(THRESHOLD_DAYS); - let timestamp = &date.timestamp().to_string()[..]; + #[cfg(feature = "time")] + let timestamp = { + let date = time::OffsetDateTime::now_utc() - time::Duration::days(THRESHOLD_DAYS); + &date.unix_timestamp().to_string()[..] + }; + + #[cfg(feature = "chrono")] + let timestamp = { + let date = chrono::Utc::now() - chrono::Duration::days(THRESHOLD_DAYS); + &date.timestamp().to_string()[..] + }; + + #[cfg(not(any(feature = "time", feature = "chrono")))] + let timestamp = { + use std::convert::TryInto; + let date = std::time::SystemTime::now() + - std::time::Duration::from_secs((THRESHOLD_DAYS * 86400).try_into().unwrap()); + &date + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + .to_string()[..] + }; let mut prune_filters = HashMap::new(); prune_filters.insert("until", vec![timestamp]); diff --git a/src/container.rs b/src/container.rs index db40a55e..5f97a15e 100644 --- a/src/container.rs +++ b/src/container.rs @@ -1,6 +1,5 @@ //! Container API: run docker containers and manage their lifecycle -use chrono::{DateTime, Utc}; use futures_core::Stream; use http::header::{CONNECTION, CONTENT_TYPE, UPGRADE}; use http::request::Builder; @@ -737,8 +736,26 @@ pub struct StorageStats { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[allow(missing_docs)] pub struct Stats { - pub read: DateTime, - pub preread: DateTime, + #[cfg(feature = "time")] + #[serde( + deserialize_with = "crate::docker::deserialize_rfc3339", + serialize_with = "crate::docker::serialize_rfc3339" + )] + pub read: time::OffsetDateTime, + #[cfg(feature = "time")] + #[serde( + deserialize_with = "crate::docker::deserialize_rfc3339", + serialize_with = "crate::docker::serialize_rfc3339" + )] + pub preread: time::OffsetDateTime, + #[cfg(feature = "chrono")] + pub read: chrono::DateTime, + #[cfg(feature = "chrono")] + pub preread: chrono::DateTime, + #[cfg(not(any(feature = "chrono", feature = "time")))] + pub read: String, + #[cfg(not(any(feature = "chrono", feature = "time")))] + pub preread: String, pub num_procs: u32, pub pids_stats: PidsStats, pub network: Option, diff --git a/src/docker.rs b/src/docker.rs index 23bc9c48..fa4694cc 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -15,7 +15,6 @@ use std::time::Duration; #[cfg(feature = "ct_logs")] use std::time::SystemTime; -use chrono::{DateTime, Utc}; use futures_core::Stream; use futures_util::future::FutureExt; use futures_util::future::TryFutureExt; @@ -187,8 +186,48 @@ where ) } +#[cfg(feature = "time")] +pub fn deserialize_rfc3339<'de, D: serde::Deserializer<'de>>( + d: D, +) -> Result { + let s: String = serde::Deserialize::deserialize(d)?; + time::OffsetDateTime::parse(&s, &time::format_description::well_known::Rfc3339) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) +} + +#[cfg(feature = "time")] +pub fn serialize_rfc3339( + date: &time::OffsetDateTime, + s: S, +) -> Result { + s.serialize_str( + &date + .format(&time::format_description::well_known::Rfc3339) + .map_err(|e| serde::ser::Error::custom(format!("{:?}", e)))?, + ) +} + +#[cfg(feature = "time")] +pub(crate) fn serialize_as_timestamp( + opt: &Option, + s: S, +) -> Result +where + S: serde::Serializer, +{ + match opt { + Some(t) => s.serialize_str(&format!( + "{}.{}", + t.unix_timestamp(), + t.unix_timestamp_nanos() + )), + None => s.serialize_str(""), + } +} + +#[cfg(feature = "chrono")] pub(crate) fn serialize_as_timestamp( - opt: &Option>, + opt: &Option, s: S, ) -> Result where diff --git a/src/read.rs b/src/read.rs index ff57c656..8983158d 100644 --- a/src/read.rs +++ b/src/read.rs @@ -348,7 +348,6 @@ mod tests { #[test] fn json_decode_lacking_newline() { - env_logger::try_init().unwrap(); let mut buf = BytesMut::from(&b"{}"[..]); let mut codec: JsonLineDecoder> = JsonLineDecoder::new(); diff --git a/src/system.rs b/src/system.rs index 74325045..0a3c02a3 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,6 +1,5 @@ //! System API: interface for interacting with the Docker server and/or Registry. -use chrono::{DateTime, Utc}; use futures_core::Stream; use http::request::Builder; use hyper::{Body, Method}; @@ -103,15 +102,14 @@ pub struct VersionComponents { /// ## Examples /// /// ```rust -/// # extern crate chrono; /// use bollard::system::EventsOptions; -/// use chrono::{Duration, Utc}; +/// use time::{Duration, OffsetDateTime}; /// use std::collections::HashMap; /// /// # fn main() { /// EventsOptions::{ -/// since: Some(Utc::now() - Duration::minutes(20)), -/// until: Some(Utc::now()), +/// since: Some(OffsetDateTime::now_utc() - Duration::minutes(20)), +/// until: Some(OffsetDateTime::now_utc()), /// filters: HashMap::new() /// }; /// # } @@ -122,11 +120,27 @@ where T: Into + Eq + Hash + Serialize, { /// Show events created since this timestamp then stream new events. + #[cfg(feature = "chrono")] #[serde(serialize_with = "crate::docker::serialize_as_timestamp")] - pub since: Option>, + pub since: Option>, /// Show events created until this timestamp then stop streaming. + #[cfg(feature = "chrono")] #[serde(serialize_with = "crate::docker::serialize_as_timestamp")] - pub until: Option>, + pub until: Option>, + /// Show events created since this timestamp then stream new events. + #[cfg(feature = "time")] + #[serde(serialize_with = "crate::docker::serialize_as_timestamp")] + pub since: Option, + /// Show events created until this timestamp then stop streaming. + #[cfg(feature = "time")] + #[serde(serialize_with = "crate::docker::serialize_as_timestamp")] + pub until: Option, + /// Show events created since this timestamp then stream new events. + #[cfg(not(any(feature = "time", feature = "chrono")))] + pub since: Option, + /// Show events created until this timestamp then stop streaming. + #[cfg(not(any(feature = "time", feature = "chrono")))] + pub until: Option, /// A JSON encoded value of filters (a `map[string][]string`) to process on the event list. Available filters: /// - `config=` config name or ID /// - `container=` container name or ID @@ -250,15 +264,15 @@ impl Docker { /// /// ```rust /// use bollard::system::EventsOptions; - /// use chrono::{Duration, Utc}; + /// use time::{Duration, OffsetDateTime}; /// use std::collections::HashMap; /// /// # use bollard::Docker; /// # let docker = Docker::connect_with_http_defaults().unwrap(); /// /// docker.events(Some(EventsOptions:: { - /// since: Some(Utc::now() - Duration::minutes(20)), - /// until: Some(Utc::now()), + /// since: Some(OffsetDateTime::now_utc() - Duration::minutes(20)), + /// until: Some(OffsetDateTime::now_utc()), /// filters: HashMap::new(), /// })); /// ``` diff --git a/tests/service_test.rs b/tests/service_test.rs index babc48f0..ebffc928 100644 --- a/tests/service_test.rs +++ b/tests/service_test.rs @@ -37,7 +37,6 @@ async fn service_create_test(docker: Docker) -> Result<(), Error> { } async fn service_list_test(docker: Docker) -> Result<(), Error> { - env_logger::init(); let image = if cfg!(windows) { format!("{}nanoserver/iis", registry_http_addr()) } else { diff --git a/tests/system_test.rs b/tests/system_test.rs index c9a3caba..a1c7c13b 100644 --- a/tests/system_test.rs +++ b/tests/system_test.rs @@ -5,7 +5,6 @@ use bollard::models::*; use bollard::system::*; use bollard::Docker; -use chrono::Utc; use futures_util::future; use futures_util::stream::select; use futures_util::stream::StreamExt; @@ -71,6 +70,7 @@ async fn events_test(docker: Docker) -> Result<(), Error> { Ok(()) } +#[cfg(any(feature = "time", feature = "chrono"))] async fn events_until_forever_test(docker: Docker) -> Result<(), Error> { let image = if cfg!(windows) { format!("{}hello-world:nanoserver", registry_http_addr()) @@ -78,7 +78,10 @@ async fn events_until_forever_test(docker: Docker) -> Result<(), Error> { format!("{}hello-world:linux", registry_http_addr()) }; - let start_time = Utc::now(); + #[cfg(feature = "time")] + let start_time = time::OffsetDateTime::now_utc(); + #[cfg(feature = "chrono")] + let start_time = chrono::Utc::now(); let stream = docker.events(Some(EventsOptions:: { since: Some(start_time), @@ -161,7 +164,7 @@ fn integration_test_events() { } #[test] -#[cfg(not(windows))] +#[cfg(all(not(windows), any(feature = "chrono", feature = "time")))] fn integration_test_events_until_forever() { connect_to_docker_and_run!(events_until_forever_test); }