use crate::{dofigen_struct::*, DofigenContext, Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub(crate) const DOCKER_HUB_HOST: &str = "registry.hub.docker.com";
pub(crate) const DEFAULT_NAMESPACE: &str = "library";
const DEFAULT_TAG: &str = "latest";
const DEFAULT_PORT: u16 = 443;
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq)]
pub struct DockerTag {
pub digest: String,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq)]
pub struct ResourceVersion {
pub hash: String,
pub content: String,
}
impl ImageName {
pub fn fill(&self) -> Self {
Self {
host: self.host.clone().or(Some(DOCKER_HUB_HOST.to_string())),
port: self.port.clone().or(Some(DEFAULT_PORT)),
version: self
.version
.clone()
.or(Some(ImageVersion::Tag(DEFAULT_TAG.to_string()))),
..self.clone()
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct LockFile {
pub effective: String,
pub images: HashMap<String, HashMap<String, HashMap<String, HashMap<String, DockerTag>>>>,
pub resources: HashMap<String, ResourceVersion>,
}
impl LockFile {
fn images(&self) -> HashMap<ImageName, DockerTag> {
let mut images = HashMap::new();
for (host, namespaces) in self.images.clone() {
let (host, port) = if host.contains(":") {
let mut parts = host.split(":");
(
parts.next().unwrap().to_string(),
Some(parts.next().unwrap().parse().unwrap()),
)
} else {
(host, None)
};
for (namespace, repositories) in namespaces {
for (repository, tags) in repositories {
let path = if namespace == DEFAULT_NAMESPACE {
repository.clone()
} else {
format!("{}/{}", namespace, repository)
};
for (tag, digest) in tags {
images.insert(
ImageName {
host: Some(host.clone()),
port,
path: path.clone(),
version: Some(ImageVersion::Tag(tag)),
},
digest,
);
}
}
}
}
images
}
fn resources(&self) -> HashMap<Resource, ResourceVersion> {
self.resources
.clone()
.into_iter()
.map(|(path, content)| (path.parse().unwrap(), content))
.collect()
}
pub fn to_context(&self) -> DofigenContext {
DofigenContext::from(self.resources(), self.images())
}
pub fn from_context(effective: &Dofigen, context: &DofigenContext) -> Result<LockFile> {
let mut images = HashMap::new();
for (image, docker_tag) in context.used_image_tags() {
let host = format!("{}:{}", image.host.unwrap(), image.port.unwrap());
let (namespace, repository) = if image.path.contains("/") {
let mut parts = image.path.split("/");
let namespace = parts.next().unwrap();
let repository = parts.collect::<Vec<&str>>().join("/");
(namespace, repository)
} else {
(DEFAULT_NAMESPACE, image.path)
};
let tag = match image.version.unwrap() {
ImageVersion::Tag(tag) => Ok(tag),
_ => Err(Error::Custom("Image version is not a tag".to_string())),
}?;
images
.entry(host)
.or_insert_with(HashMap::new)
.entry(namespace.to_string())
.or_insert_with(HashMap::new)
.entry(repository.to_string())
.or_insert_with(HashMap::new)
.insert(tag, docker_tag);
}
let files = context
.used_resource_contents()
.iter()
.map(|(resource, content)| (resource.to_string(), content.clone()))
.collect();
Ok(LockFile {
effective: serde_yaml::to_string(effective).map_err(Error::from)?,
images,
resources: files,
})
}
}
pub trait Lock: Sized {
fn lock(&self, context: &mut DofigenContext) -> Result<Self>;
}
impl<T> Lock for Option<T>
where
T: Lock,
{
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
match self {
Some(t) => Ok(Some(t.lock(context)?)),
None => Ok(None),
}
}
}
impl<T> Lock for Vec<T>
where
T: Lock,
{
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
self.iter().map(|t| t.lock(context)).collect()
}
}
impl<K, V> Lock for HashMap<K, V>
where
K: Eq + std::hash::Hash + Clone,
V: Lock,
{
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
self.iter()
.map(|(key, value)| {
value
.lock(context)
.map(|locked_value| (key.clone(), locked_value))
})
.collect()
}
}
impl Lock for Dofigen {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
Ok(Self {
builders: self.builders.lock(context)?,
stage: self.stage.lock(context)?,
..self.clone()
})
}
}
impl Lock for Stage {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
Ok(Self {
from: self.from.lock(context)?,
copy: self.copy.lock(context)?,
run: self.run.lock(context)?,
root: self
.root
.as_ref()
.map(|root| root.lock(context))
.transpose()?,
..self.clone()
})
}
}
impl Lock for FromContext {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
match self {
Self::FromImage(image_name) => Ok(Self::FromImage(image_name.lock(context)?)),
other => Ok(other.clone()),
}
}
}
impl Lock for ImageName {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
match self.version.clone() {
Some(ImageVersion::Digest(_)) => Ok(self.clone()),
_ => Ok(Self {
version: Some(ImageVersion::Digest(
context.get_image_tag(self)?.digest.clone(),
)),
..self.clone()
}),
}
}
}
impl Lock for CopyResource {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
match self {
Self::Copy(resource) => Ok(Self::Copy(resource.lock(context)?)),
other => Ok(other.clone()),
}
}
}
impl Lock for Copy {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
Ok(Self {
from: self.from.lock(context)?,
..self.clone()
})
}
}
impl Lock for Run {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
Ok(Self {
bind: self.bind.lock(context)?,
cache: self.cache.lock(context)?,
..self.clone()
})
}
}
impl Lock for Bind {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
Ok(Self {
from: self.from.lock(context)?,
..self.clone()
})
}
}
impl Lock for Cache {
fn lock(&self, context: &mut DofigenContext) -> Result<Self> {
Ok(Self {
from: self.from.lock(context)?,
..self.clone()
})
}
}
impl Ord for DockerTag {
fn cmp(&self, _other: &Self) -> std::cmp::Ordering {
panic!("DockerTag cannot be ordered")
}
}
impl Ord for ResourceVersion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.hash.cmp(&other.hash)
}
}