Skip to content

Commit

Permalink
Build images (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
iamjpotts authored Nov 1, 2022
1 parent 40cce74 commit c42a2fd
Show file tree
Hide file tree
Showing 15 changed files with 910 additions and 56 deletions.
23 changes: 20 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ on:
branches: [master]

jobs:
clippy:
name: Cargo clippy
strategy:
matrix:
os:
- macOS-latest
- ubuntu-20.04
- windows-2022
toolchain:
- 1.64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
components: clippy
toolchain: ${{ matrix.toolchain }}
- run: cargo clippy -- -D warnings
timeout-minutes: 20

deny:
name: Cargo deny
strategy:
Expand Down Expand Up @@ -40,12 +60,10 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
components: clippy
toolchain: ${{ matrix.toolchain }}
# Docker is not installed by default on Mac runners. Installing Docker takes about 10 minutes.
- uses: docker-practice/actions-setup-docker@master
- run: docker version
- run: cargo clippy
- run: cargo test --no-run
- run: cargo test --no-fail-fast
timeout-minutes: 45
Expand All @@ -69,7 +87,6 @@ jobs:
components: clippy
toolchain: ${{ matrix.toolchain }}
- run: docker version
- run: cargo clippy
- run: cargo test --no-run
- run: cargo test --no-fail-fast
timeout-minutes: 30
Expand Down
20 changes: 4 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ mockito = "0.31.0"
native-tls = "0.2"
openssl = "0.10"
passivized_htpasswd = "0.0.2"
passivized_test_support = "0.0.4"
passivized_test_support = "0.0.5"
rand = "0.8"
serial_test = "0.9"
simple_logger = { version = "4.0", default-features = false, features = ["timestamps", "threads"] }
Expand Down
34 changes: 32 additions & 2 deletions src/client/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,45 @@ use hyper::StatusCode;

use crate::DockerEngineClient;
use crate::errors::DecUseError;
use crate::requests::CreateImageRequest;
use crate::responses::ListedImage;
use crate::imp::content_type;
use crate::model::Tar;
use crate::requests::{BuildImageRequest, CreateImageRequest};
use crate::responses::{BuildImageResponseStreamItem, ListedImage};

pub struct DecImages<'a> {
pub(super) client: &'a DockerEngineClient
}

impl <'a> DecImages<'a> {

/// Build a new image.
///
/// Context is a tar file that includes a Dockerfile and any files referenced by it.
///
/// See https://docs.docker.com/engine/api/v1.41/#tag/Image/operation/ImageBuild
///
/// Failure can be returned two ways:
/// 1. Via Result::Err
/// 2. Via Result::Ok with stream items containing error messages
pub async fn build(&self, request: BuildImageRequest, context: Tar) -> Result<Vec<BuildImageResponseStreamItem>, DecUseError> {
let uri = self.client.url.images().build(request)?;
let response = self.client.http
.post_with_auth_config(
uri,
&self.client.registry_auth,
content_type::TAR,
context.0
)?
.execute()
.await?;

let items = response
.assert_item_status(StatusCode::OK)?
.parse_stream()?;

Ok(items)
}

/// "Create an image by either pulling it from a registry or importing it."
///
/// See https://docs.docker.com/engine/api/v1.41/#tag/Image/operation/ImageCreate
Expand Down
156 changes: 154 additions & 2 deletions src/imp/api.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::hash::Hash;

use const_str::concat;
use serde::Serialize;

use crate::client::DOCKER_ENGINE_VERSION;
use crate::errors::DecCreateError;
use crate::imp::url::UrlBuilder;
use crate::imp::url_parser::is_http;
use crate::requests::{CreateImageRequest, ListContainersRequest, RemoveContainerArgs, WaitCondition};
use crate::requests::{BuildImageRequest, CreateImageRequest, ListContainersRequest, RemoveContainerArgs, WaitCondition};

pub(crate) const DOCKER_ENGINE_VERSION_PATH: &str = concat!("/", DOCKER_ENGINE_VERSION);

Expand Down Expand Up @@ -404,6 +407,65 @@ pub(crate) struct DockerEngineApiPathImages {

impl DockerEngineApiPathImages {

fn sz_map<K, V>(value: &HashMap<K, V>) -> Result<Option<String>, serde_json::Error>
where
K: Serialize + Eq + Hash,
V: Serialize
{
if value.is_empty() {
Ok(None)
}
else {
serde_json::to_string(value)
.map(Some)
}
}

fn sz_vec<A>(value: &Vec<A>) -> Result<Option<String>, serde_json::Error>
where
A: Serialize
{
if value.is_empty() {
Ok(None)
}
else {
serde_json::to_string(value)
.map(Some)
}
}

pub fn build(&self, request: BuildImageRequest) -> Result<String, DockerEngineApiBuilderError> {
Ok(self.base.builder()?
.join("build")?
.query()
.option("dockerfile", request.dockerfile)
.append_all("t", request.tags)
.option("extrahosts", request.extra_hosts)
.option("remote", request.remote)
.option("q", request.quiet)
.option("nocache", request.no_cache)
.option("cachefrom", Self::sz_vec(&request.cache_from)?)
.option("pull", request.pull)
.option("rm", request.remove_intermediates)
.option("forcerm", request.force_remove_intermediates)
.option("memory", request.memory_limit)
.option("memswap", request.memory_and_swap)
.option("cpushares", request.cpu_shares)
.option("cpusetcpus", request.cpu_set_cpus)
.option("cpuperiod", request.cpu_period)
.option("cpuquota", request.cpu_quota)
.option("buildargs", Self::sz_map(&request.build_args)?)
.option("shmsize", request.shm_size_bytes)
.option("squash", request.squash)
.option("labels", Self::sz_map(&request.labels)?)
.option("networkmode", request.network_mode)
.option("platform", request.platform)
.option("target", request.target)
.option("outputs", request.outputs)
.to_string()
)
}

pub fn create(&self, request: CreateImageRequest) -> Result<String, url::ParseError> {
Ok(self.base.builder()?
.join("images/create")?
Expand Down Expand Up @@ -842,7 +904,43 @@ mod test_docker_engine_api_path {
}

mod images {
use crate::imp::api::DockerEngineApi;
use crate::imp::api::{DOCKER_ENGINE_VERSION_PATH, DockerEngineApi};
use crate::requests::BuildImageRequest;

#[test]
pub fn build_typical() {
let api = DockerEngineApi::with_server("http://a".into())
.unwrap();

let request = BuildImageRequest::default()
.tag("foo:bar");

let actual = api.images().build(request)
.unwrap();

assert_eq!(format!("http://a{}/build?t=foo%3Abar", DOCKER_ENGINE_VERSION_PATH), actual);
}

#[test]
pub fn build_labeled() {
let api = DockerEngineApi::with_server("http://a".into())
.unwrap();

let request = BuildImageRequest::default()
.tag("qux")
.label("w", "x")
.label("y", "z");

let actual = api.images().build(request)
.unwrap();

assert!(actual.starts_with(&format!("http://a{}/build?t=qux&labels=%7B%22", DOCKER_ENGINE_VERSION_PATH)));

assert!(actual.contains('w'));
assert!(actual.contains('x'));
assert!(actual.contains('y'));
assert!(actual.contains('z'));
}

#[test]
pub fn json() {
Expand Down Expand Up @@ -992,4 +1090,58 @@ mod test_docker_engine_server {
assert_eq!(concat!("http://foo", DOCKER_ENGINE_VERSION_PATH), api.base);
}

}

#[cfg(test)]
mod test_sz_map {
use std::collections::HashMap;
use super::DockerEngineApiPathImages;

#[test]
fn empty() {
let input: HashMap<String, i32> = HashMap::new();

let actual = DockerEngineApiPathImages::sz_map(&input)
.unwrap();

assert_eq!(None, actual);
}

#[test]
fn populated() {
let input: HashMap<i32, String> = HashMap::from([
(123, "a".into()),
(456, "b".into())
]);

let actual = DockerEngineApiPathImages::sz_map(&input)
.unwrap();

assert!(actual.is_some());
}
}

#[cfg(test)]
mod test_sz_vec {
use super::DockerEngineApiPathImages;

#[test]
fn empty() {
let input: Vec<u16> = Vec::new();

let actual = DockerEngineApiPathImages::sz_vec(&input)
.unwrap();

assert_eq!(None, actual);
}

#[test]
fn populated() {
let input: Vec<u128> = vec![12, 34];

let actual = DockerEngineApiPathImages::sz_vec(&input)
.unwrap();

assert!(actual.is_some());
}
}
Loading

0 comments on commit c42a2fd

Please sign in to comment.