use crate::DockerClient;
use crate::error::{DockerError, Result};
use bollard::auth::DockerCredentials;
use bollard::image::*;
use bollard::models::*;
use futures_util::StreamExt;
use std::collections::HashMap;
use tracing::{debug, info, warn};
mod builder;
pub use builder::*;
pub struct Images<'a> {
client: &'a DockerClient,
}
impl<'a> Images<'a> {
pub(crate) fn new(client: &'a DockerClient) -> Self {
Self { client }
}
pub fn build(&self, tag: impl Into<String>) -> ImageBuilder<'a> {
ImageBuilder::new(self.client, tag)
}
pub fn get(&self, name_or_id: impl Into<String>) -> ImageRef<'a> {
ImageRef::new(self.client, name_or_id.into())
}
pub async fn list(&self, all: bool) -> Result<Vec<ImageSummary>> {
let options = Some(ListImagesOptions::<String> {
all,
..Default::default()
});
self.client
.docker
.list_images(options)
.await
.map_err(|e| DockerError::Other(format!("Failed to list images: {}", e)))
}
pub async fn pull(
&self,
image: impl Into<String>,
credentials: Option<DockerCredentials>,
) -> Result<()> {
let image = image.into();
info!("Pulling image: {}", image);
let options = Some(CreateImageOptions::<String> {
from_image: image.clone(),
..Default::default()
});
let mut stream = self.client.docker.create_image(options, None, credentials);
while let Some(result) = stream.next().await {
match result {
Ok(info) => {
if let Some(status) = info.status {
debug!("Pull status: {}", status);
}
if let Some(error) = info.error {
return Err(DockerError::PullFailed(error));
}
}
Err(e) => {
return Err(DockerError::PullFailed(e.to_string()));
}
}
}
info!("Successfully pulled image: {}", image);
Ok(())
}
pub async fn search(
&self,
term: impl Into<String>,
limit: Option<i64>,
) -> Result<Vec<ImageSearchResponseItem>> {
let options = SearchImagesOptions {
term: term.into(),
limit: limit.map(|l| l as u64),
..Default::default()
};
self.client
.docker
.search_images(options)
.await
.map_err(|e| DockerError::Other(format!("Failed to search images: {}", e)))
}
pub async fn prune(&self, dangling_only: bool) -> Result<ImagePruneResponse> {
info!("Pruning unused images...");
let mut filters = HashMap::new();
if dangling_only {
filters.insert("dangling", vec!["true"]);
}
let options = Some(PruneImagesOptions { filters });
self.client
.docker
.prune_images(options)
.await
.map_err(|e| DockerError::Other(format!("Failed to prune images: {}", e)))
}
}
pub struct ImageRef<'a> {
client: &'a DockerClient,
name: String,
}
impl<'a> ImageRef<'a> {
pub(crate) fn new(client: &'a DockerClient, name: String) -> Self {
Self { client, name }
}
pub fn name(&self) -> &str {
&self.name
}
pub async fn inspect(&self) -> Result<ImageInspect> {
debug!("Inspecting image: {}", self.name);
self.client
.docker
.inspect_image(&self.name)
.await
.map_err(|e| DockerError::ImageNotFound(format!("{}: {}", self.name, e)))
}
pub async fn history(&self) -> Result<Vec<HistoryResponseItem>> {
debug!("Getting history for image: {}", self.name);
self.client
.docker
.image_history(&self.name)
.await
.map_err(|e| DockerError::ImageNotFound(format!("{}: {}", self.name, e)))
}
pub async fn tag(&self, new_tag: impl Into<String>) -> Result<()> {
let new_tag = new_tag.into();
info!("Tagging image {} as {}", self.name, new_tag);
let parts: Vec<&str> = new_tag.rsplitn(2, ':').collect();
let (repo, tag) = if parts.len() == 2 {
(parts[1], Some(parts[0]))
} else {
(new_tag.as_str(), None)
};
let options = TagImageOptions {
repo,
tag: tag.unwrap_or("latest"),
};
self.client
.docker
.tag_image(&self.name, Some(options))
.await
.map_err(|e| DockerError::Other(format!("Failed to tag image: {}", e)))
}
pub async fn push(&self, credentials: Option<DockerCredentials>) -> Result<()> {
info!("Pushing image: {}", self.name);
let tag = self.name.split(':').nth(1).unwrap_or("latest");
let options = Some(PushImageOptions { tag });
let mut stream = self
.client
.docker
.push_image(&self.name, options, credentials);
while let Some(result) = stream.next().await {
match result {
Ok(info) => {
if let Some(status) = info.status {
if status.contains("error") || status.contains("Error") {
warn!("Push error: {}", status);
} else {
debug!("Push status: {}", status);
}
}
if let Some(error) = info.error {
return Err(DockerError::PushFailed(error));
}
}
Err(e) => {
return Err(DockerError::PushFailed(e.to_string()));
}
}
}
info!("Successfully pushed image: {}", self.name);
Ok(())
}
pub async fn remove(&self, force: bool, noprune: bool) -> Result<Vec<ImageDeleteResponseItem>> {
info!("Removing image: {}", self.name);
let options = Some(RemoveImageOptions { force, noprune });
self.client
.docker
.remove_image(&self.name, options, None)
.await
.map_err(|e| DockerError::Other(format!("Failed to remove image: {}", e)))
}
pub async fn export(&self) -> Result<Vec<u8>> {
info!("Exporting image: {}", self.name);
let mut stream = self.client.docker.export_image(&self.name);
let mut data = Vec::new();
while let Some(chunk) = stream.next().await {
match chunk {
Ok(bytes) => data.extend_from_slice(&bytes),
Err(e) => {
return Err(DockerError::Other(format!("Failed to export image: {}", e)));
}
}
}
Ok(data)
}
}
impl DockerClient {
pub fn images(&self) -> Images<'_> {
Images::new(self)
}
}