pub use bollard_buildkit_proto::fsutil;
pub use bollard_buildkit_proto::health;
pub use bollard_buildkit_proto::moby;
use bollard_buildkit_proto::moby::buildkit::v1::CacheOptionsEntry;
use std::collections::HashMap;
use std::fmt::Display;
use std::net::IpAddr;
use std::path::Path;
use std::path::PathBuf;
use bytes::Bytes;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ImageBuildFrontendOptions {
pub(crate) cacheto: Vec<CacheOptionsEntry>,
pub(crate) cachefrom: Vec<CacheOptionsEntry>,
pub(crate) image_resolve_mode: bool,
pub(crate) target: Option<String>,
pub(crate) nocache: bool,
pub(crate) buildargs: HashMap<String, String>,
pub(crate) labels: HashMap<String, String>,
pub(crate) platforms: Vec<ImageBuildPlatform>,
pub(crate) force_network_mode: ImageBuildNetworkMode,
pub(crate) extrahosts: Vec<ImageBuildHostIp>,
pub(crate) shmsize: u64,
pub(crate) secrets: HashMap<String, SecretSource>,
pub(crate) ssh: bool,
pub(crate) named_contexts: HashMap<String, NamedContext>,
pub(crate) dockerfile: Option<PathBuf>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SecretSource {
File(std::path::PathBuf),
Env(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImageBuildHostIp {
pub host: String,
pub ip: IpAddr,
}
impl Display for ImageBuildHostIp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}={}", self.host, self.ip)
}
}
#[derive(Default, Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ImageBuildNetworkMode {
#[default]
Bridge,
Host,
None,
Container(String),
}
impl Display for ImageBuildNetworkMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImageBuildNetworkMode::Bridge => write!(f, "default"),
ImageBuildNetworkMode::Host => write!(f, "host"),
ImageBuildNetworkMode::None => write!(f, "none"),
ImageBuildNetworkMode::Container(name) => write!(f, "container:{name}"),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ImageBuildPlatform {
pub architecture: String,
pub os: String,
pub variant: Option<String>,
}
impl Display for ImageBuildPlatform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let prefix = Path::new(&self.os).join(Path::new(&self.architecture));
if let Some(variant) = &self.variant {
write!(f, "{}", prefix.join(Path::new(&variant)).display())
} else {
write!(f, "{}", prefix.display())
}
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq)]
#[non_exhaustive]
pub enum ImageBuildOutputCompression {
Uncompressed,
#[default]
Gzip,
Estargz,
Zstd,
}
impl Display for ImageBuildOutputCompression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImageBuildOutputCompression::Uncompressed => write!(f, "uncompressed"),
ImageBuildOutputCompression::Gzip => write!(f, "gzip"),
ImageBuildOutputCompression::Estargz => write!(f, "estargz"),
ImageBuildOutputCompression::Zstd => write!(f, "zstd"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NamedContext {
pub path: String,
}
pub(crate) struct ImageBuildFrontendOptionsIngest {
pub cache_to: Vec<CacheOptionsEntry>,
pub cache_from: Vec<CacheOptionsEntry>,
pub frontend_attrs: HashMap<String, String>,
pub secret_sources: HashMap<String, SecretSource>,
pub ssh: bool,
}
impl ImageBuildFrontendOptions {
pub fn builder() -> ImageBuildFrontendOptionsBuilder {
ImageBuildFrontendOptionsBuilder::new()
}
pub(crate) fn consume(self) -> ImageBuildFrontendOptionsIngest {
let mut attrs = HashMap::new();
if let Some(dockerfile) = self.dockerfile {
attrs.insert(
String::from("filename"),
dockerfile.to_string_lossy().to_string(),
);
}
if self.image_resolve_mode {
attrs.insert(String::from("image-resolve-mode"), String::from("pull"));
} else {
attrs.insert(String::from("image-resolve-mode"), String::from("default"));
}
if let Some(target) = self.target {
attrs.insert(String::from("target"), target);
}
if self.nocache {
attrs.insert(String::from("no-cache"), String::new());
}
if !self.buildargs.is_empty() {
for (key, value) in self.buildargs {
attrs.insert(format!("build-arg:{}", key), value);
}
}
if !self.labels.is_empty() {
for (key, value) in self.labels {
attrs.insert(format!("label:{}", key), value);
}
}
if !self.platforms.is_empty() {
attrs.insert(
String::from("platform"),
self.platforms
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(","),
);
}
match self.force_network_mode {
ImageBuildNetworkMode::Host => {
attrs.insert(String::from("force-network-mode"), String::from("host"));
}
ImageBuildNetworkMode::None => {
attrs.insert(String::from("force-network-mode"), String::from("none"));
}
_ => (),
}
if !self.extrahosts.is_empty() {
attrs.insert(
String::from("add-hosts"),
self.extrahosts
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(","),
);
}
if self.shmsize > 0 {
attrs.insert(String::from("shm-size"), self.shmsize.to_string());
}
if !self.named_contexts.is_empty() {
attrs.insert(
String::from("frontend.caps"),
String::from("moby.buildkit.frontend.contexts+forward"),
);
for (name, context) in self.named_contexts {
attrs.insert(format!("context:{name}"), context.path);
}
}
ImageBuildFrontendOptionsIngest {
cache_to: self.cacheto,
cache_from: self.cachefrom,
frontend_attrs: attrs,
secret_sources: self.secrets,
ssh: self.ssh,
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ImageBuildFrontendOptionsBuilder {
inner: ImageBuildFrontendOptions,
}
impl ImageBuildFrontendOptionsBuilder {
pub fn new() -> Self {
Self {
inner: ImageBuildFrontendOptions {
..Default::default()
},
}
}
pub fn dockerfile(mut self, path: &Path) -> Self {
self.inner.dockerfile = Some(path.to_path_buf());
self
}
pub fn cacheto(mut self, value: &CacheOptionsEntry) -> Self {
self.inner.cacheto.push(value.to_owned());
self
}
pub fn cachefrom(mut self, value: &CacheOptionsEntry) -> Self {
self.inner.cachefrom.push(value.to_owned());
self
}
pub fn pull(mut self, pull: bool) -> Self {
self.inner.image_resolve_mode = pull;
self
}
pub fn target(mut self, target: &str) -> Self {
self.inner.target = Some(String::from(target));
self
}
pub fn nocache(mut self, nocache: bool) -> Self {
self.inner.nocache = nocache;
self
}
pub fn buildarg(mut self, key: &str, value: &str) -> Self {
self.inner
.buildargs
.insert(String::from(key), String::from(value));
self
}
pub fn label(mut self, key: &str, value: &str) -> Self {
self.inner
.labels
.insert(String::from(key), String::from(value));
self
}
pub fn platforms(mut self, value: &ImageBuildPlatform) -> Self {
self.inner.platforms.push(value.to_owned());
self
}
pub fn force_network_mode(mut self, value: &ImageBuildNetworkMode) -> Self {
value.clone_into(&mut self.inner.force_network_mode);
self
}
pub fn extrahost(mut self, value: &ImageBuildHostIp) -> Self {
self.inner.extrahosts.push(value.to_owned());
self
}
pub fn shmsize(mut self, value: u64) -> Self {
self.inner.shmsize = value;
self
}
pub fn set_secret(mut self, key: &str, value: &SecretSource) -> Self {
self.inner
.secrets
.insert(String::from(key), value.to_owned());
self
}
pub fn enable_ssh(mut self, value: bool) -> Self {
self.inner.ssh = value;
self
}
pub fn named_context(mut self, key: &str, value: NamedContext) -> Self {
self.inner.named_contexts.insert(String::from(key), value);
self
}
pub fn build(self) -> ImageBuildFrontendOptions {
self.inner
}
}
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum ImageBuildLoadInput {
Upload(Bytes),
}
#[cfg(test)]
mod tests {
use super::ImageBuildPlatform;
#[test]
fn test_imagebuildplatform_display() {
let platform = ImageBuildPlatform {
architecture: String::from("amd64"),
os: String::from("linux"),
variant: None,
};
assert_eq!(platform.to_string(), "linux/amd64");
}
#[test]
fn test_imagebuildplatform_display_with_variant() {
let platform = ImageBuildPlatform {
architecture: String::from("arm64"),
os: String::from("linux"),
variant: Some(String::from("v8")),
};
assert_eq!(platform.to_string(), "linux/arm64/v8");
}
}