Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/extends/base.yaml → examples/bases/base.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
image: docker.io/alpine
sidecars:
mongo:
image: docker.io/mongo
image: docker.io/mongo
6 changes: 6 additions & 0 deletions examples/bases/base2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mounts:
/test: {}

sidecars:
docker:
image: docker.io/docker
13 changes: 13 additions & 0 deletions examples/bases/overlay.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
vars:
test-x: 1

env:
TEST: '{{ test-x }}'

sidecars:
sql:
image: docker.io/postgres

bases:
- base.yaml
- base2.yaml
8 changes: 0 additions & 8 deletions examples/extends/overlay.yaml

This file was deleted.

87 changes: 52 additions & 35 deletions src/api/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ use colored::Colorize;

use super::{Api, ConfigApi};

pub struct ConfigBody {
pub body: String,
pub bases: Option<String>,
pub merged: Option<RoozCfg>,
}

#[async_trait::async_trait]
pub trait ConfigReader {
async fn read_file(&self, path: &str) -> Result<String, AnyError>;
Expand Down Expand Up @@ -45,13 +51,13 @@ impl<'a> ConfigApi<'a> {
Ok(())
}

pub async fn store_extends(&self, workspace_key: &str, body: &str) -> Result<(), AnyError> {
pub async fn store_bases(&self, workspace_key: &str, body: &str) -> Result<(), AnyError> {
let config_vol = RoozVolume::config_data(
workspace_key,
"/etc/rooz",
Some(
[(
ConfigType::Extends.file_path().to_string(),
ConfigType::Bases.file_path().to_string(),
body.to_string(),
)]
.into_iter()
Expand Down Expand Up @@ -201,46 +207,53 @@ impl<'a> ConfigApi<'a> {
child_path: &str,
child: RoozCfg,
depth: usize,
) -> Result<RoozCfg, AnyError> {
) -> Result<(RoozCfg, Vec<(String, RoozCfg)>), AnyError> {
let base_paths = match child.bases.as_ref() {
Some(p) if !p.is_empty() => p.clone(),
_ => return Ok((child, vec![])),
};

if depth >= Self::MAX_EXTENDS_DEPTH {
return Err(format!(
"extends nesting too deep (limit {})",
"bases nesting too deep (limit {})",
Self::MAX_EXTENDS_DEPTH
)
.into());
}

let extends_path = match child.extends.as_deref() {
Some(p) => p,
None => return Ok(child),
};

RoozCfg::validate_extends_path(extends_path)?;
RoozCfg::validate_base_list(&base_paths)?;

let parent_dir = std::path::Path::new(child_path)
.parent()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default();
let abs_extends = if parent_dir.is_empty() {
extends_path.to_string()
} else {
format!("{}/{}", parent_dir, extends_path)
};

let base_body = reader.read_file(&abs_extends).await?;
if base_body.is_empty() {
return Err(format!("extends '{}' not found or empty", extends_path).into());
}
let mut individual_bases: Vec<(String, RoozCfg)> = Vec::new();
let mut accumulated = RoozCfg::none();
for base_path in &base_paths {
let abs_path = if parent_dir.is_empty() {
base_path.to_string()
} else {
format!("{}/{}", parent_dir, base_path)
};

let base_fmt = FileFormat::from_path(extends_path);
let base = RoozCfg::deserialize_config(&base_body, base_fmt)?
.ok_or_else(|| format!("Failed to parse extends '{}': invalid config", extends_path))?;
let base_body = reader.read_file(&abs_path).await?;
if base_body.is_empty() {
return Err(format!("base '{}' not found or empty", base_path).into());
}

let mut effective_base =
Box::pin(self.resolve_extends_chain(reader, &abs_extends, base, depth + 1)).await?;
let base_fmt = FileFormat::from_path(base_path);
let base = RoozCfg::deserialize_config(&base_body, base_fmt)?
.ok_or_else(|| format!("Failed to parse base '{}': invalid config", base_path))?;

effective_base.from_config(&child);
Ok(effective_base)
let (resolved, _) =
Box::pin(self.resolve_extends_chain(reader, &abs_path, base, depth + 1)).await?;
individual_bases.push((base_path.clone(), resolved.clone()));
accumulated.from_config(&resolved);
}

accumulated.from_config(&child);
Ok((accumulated, individual_bases))
}

pub async fn read_config_body(
Expand All @@ -249,9 +262,7 @@ impl<'a> ConfigApi<'a> {
clone_dir: &str,
file_format: FileFormat,
exact_path: Option<&str>,
) -> Result<Option<(String, Option<String>)>, AnyError> {
use crate::config::config::RoozCfg;

) -> Result<Option<ConfigBody>, AnyError> {
let file_path = match exact_path {
Some(p) => format!("{}/{}", clone_dir, p.to_string()),
None => format!("{}/.rooz.{}", clone_dir, file_format.to_string()),
Expand Down Expand Up @@ -281,32 +292,38 @@ impl<'a> ConfigApi<'a> {

if let (Some(_), Some(cfg)) = (exact_path, RoozCfg::deserialize_config(&body, file_format)?)
{
if cfg.extends.is_some() {
if cfg.bases.is_some() {
let reader = ContainerReader {
api: self.api,
container_id,
};
let merged = self
let (merged, individual_bases) = self
.resolve_extends_chain(&reader, &file_path, cfg, 0)
.await?;
return Ok(Some((body, Some(merged.to_string(file_format)?))));
let bases_yaml = individual_bases
.iter()
.map(|(path, b)| b.to_string(file_format).map(|yaml| format!("# {}\n{}", path, yaml)))
.collect::<Result<Vec<_>, _>>()?
.join("\n---\n");
let bases_storage = if bases_yaml.is_empty() { None } else { Some(bases_yaml) };
return Ok(Some(ConfigBody { body, bases: bases_storage, merged: Some(merged) }));
}
}

Ok(Some((body, None)))
Ok(Some(ConfigBody { body, bases: None, merged: None }))
}

pub async fn try_read_config(
&self,
container_id: &str,
clone_dir: &str,
) -> Result<Option<(String, Option<String>, FileFormat)>, AnyError> {
let rooz_cfg = if let Some((body, extends_body)) = self
let rooz_cfg = if let Some(cb) = self
.read_config_body(&container_id, &clone_dir, FileFormat::Yaml, None)
.await?
{
log::debug!("Config file found (YAML)");
Some((body, extends_body, FileFormat::Yaml))
Some((cb.body, None, FileFormat::Yaml))
} else {
log::debug!("No valid config file found");
None
Expand Down
2 changes: 1 addition & 1 deletion src/api/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use bollard::{
};

use crate::model::types::{TargetDir, VolumeFilesSpec};
use crate::util::backend::ContainerBackend;

use bollard_stubs::models::{MountTypeEnum, NetworkingConfig};
use bollard_stubs::query_parameters::{UploadToContainerOptions, WaitContainerOptions};
use futures::{StreamExt, TryStreamExt, future};
Expand Down
6 changes: 3 additions & 3 deletions src/cmd/config/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ impl<'a> ConfigApi<'a> {
body
};

let extends_body = self.read(workspace_key, &ConfigType::Extends).await?;
if extends_body.is_empty() {
let bases_body = self.read(workspace_key, &ConfigType::Bases).await?;
if bases_body.is_empty() {
Some(body)
} else {
Some(format!("{}\n{}\n{}", body, EXTENDS_SEPARATOR, extends_body))
Some(format!("{}\n{}\n{}", body, EXTENDS_SEPARATOR, bases_body))
}
}
ConfigPart::Runtime => {
Expand Down
74 changes: 40 additions & 34 deletions src/cmd/new.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::api::VolumeApi;
use crate::api::config::LocalReader;
use crate::api::config::{ConfigBody, LocalReader};
use crate::model::types::VolumeResult;
use crate::{
api::WorkspaceApi,
Expand Down Expand Up @@ -216,7 +216,26 @@ impl<'a> WorkspaceApi<'a> {
format,
} => {
let body = value.to_string(format.clone())?;
(origin.to_string(), Some(body), None, Some(value.clone()))
let (cfg, base_body) = if value.bases.is_some() {
if let Ok(ConfigPath::File { path }) = ConfigPath::from_str(origin) {
let reader = LocalReader {};
let (merged, individual_bases) = self
.config
.resolve_extends_chain(&reader, &path, value.clone(), 0)
.await?;
let bases_yaml = individual_bases
.iter()
.map(|(p, b)| b.to_string(*format).map(|yaml| format!("# {}\n{}", p, yaml)))
.collect::<Result<Vec<_>, _>>()?
.join("\n---\n");
(Some(merged), if bases_yaml.is_empty() { None } else { Some(bases_yaml) })
} else {
(Some(value.clone()), None)
}
} else {
(Some(value.clone()), None)
};
(origin.to_string(), Some(body), base_body, cfg)
}
ConfigSource::Path { value: path } => match path {
ConfigPath::File { path } => {
Expand All @@ -227,14 +246,19 @@ impl<'a> WorkspaceApi<'a> {
let cfg = RoozCfg::deserialize_config(&body, fmt)?;

let (cfg, base_body) = match cfg {
Some(c) if c.extends.is_some() => {
Some(c) if c.bases.is_some() => {
let reader = LocalReader {};
let merged = self
let (merged, individual_bases) = self
.config
.resolve_extends_chain(&reader, path.as_str(), c, 0)
.await?;
let base_body = merged.to_string(fmt)?;
(Some(merged), Some(base_body))
let bases_yaml = individual_bases
.iter()
.map(|(path, b)| b.to_string(fmt).map(|yaml| format!("# {}\n{}", path, yaml)))
.collect::<Result<Vec<_>, _>>()?
.join("\n---\n");
let base_body = if bases_yaml.is_empty() { None } else { Some(bases_yaml) };
(Some(merged), base_body)
}
other => (other, None),
};
Expand All @@ -247,30 +271,12 @@ impl<'a> WorkspaceApi<'a> {
.await?;

let (rooz_cfg, main_body, base_body) = match result {
Some((body, extends_body)) => {
Some(ConfigBody { body, bases, merged }) => {
let fmt = FileFormat::from_path(&file_path);
let cfg = RoozCfg::deserialize_config(&body, fmt)?;
let merged = match cfg {
Some(c) if extends_body.is_some() => {
let eb = extends_body.as_deref().unwrap();
let ext_path = c.extends.as_deref().unwrap();
let ext_fmt = FileFormat::from_path(ext_path);
match RoozCfg::deserialize_config(eb, ext_fmt)? {
Some(mut base) => {
base.from_config(&c);
Some(base)
}
None => {
return Err(
"Failed to parse extends: invalid config"
.into(),
);
}
}
}
other => other,
};
(merged, Some(body), extends_body)
let cfg = merged
.map(Ok)
.unwrap_or_else(|| RoozCfg::deserialize_config(&body, fmt).map(|o| o.unwrap()))?;
(Some(cfg), Some(body), bases)
}
None => (None, None, None),
};
Expand All @@ -283,9 +289,9 @@ impl<'a> WorkspaceApi<'a> {
self.config
.store(workspace_key, &origin, &body.unwrap())
.await?;
if let Some(eb) = extends_body {
self.config.store_extends(workspace_key, &eb).await?;
}
self.config
.store_bases(workspace_key, extends_body.as_deref().unwrap_or(""))
.await?;

rooz_cfg
} else {
Expand Down Expand Up @@ -350,8 +356,8 @@ impl<'a> WorkspaceApi<'a> {
(Some((body, _extends_body, format)), _) => {
match RoozCfg::deserialize_config(body, *format)? {
Some(c) => {
if c.extends.is_some() {
return Err("'extends' is not supported in in-repo config (.rooz.yaml); use it in a --config file instead".into());
if c.bases.is_some() {
return Err("'bases' is not supported in in-repo config (.rooz.yaml); use it in a --config file instead".into());
}
cfg_builder.from_config(&c);
log::debug!("Config file applied.");
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ impl<'a> WorkspaceApi<'a> {
.git
.clone_config_repo(clone_env, &url, &file_path)
.await?;
if let Some((body, _)) = result {
original_body = body;
if let Some(cb) = result {
original_body = cb.body;
};
}
};
Expand Down
Loading
Loading