#![allow(missing_docs)] pub mod components;
pub(crate) mod app_config;
mod cache;
pub(crate) mod common;
pub(crate) mod component_config;
pub(crate) mod configuration_tree;
pub(crate) mod import_cache;
pub(crate) mod lockdown_config;
pub(crate) mod permissions;
pub(crate) mod test_config;
pub(crate) mod types_config;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub use app_config::*;
use asset_container::Asset;
pub use common::*;
pub use component_config::*;
pub use configuration_tree::*;
pub use lockdown_config::*;
pub use permissions::{Permissions, PermissionsBuilder};
pub use test_config::*;
use tracing::debug;
pub use types_config::*;
use wick_asset_reference::{AssetReference, FetchOptions};
use wick_interface_types::Field;
use wick_packet::validation::expect_configuration_matches;
use wick_packet::{Entity, RuntimeConfig};
use self::template_config::Renderable;
use crate::load::resolve_configuration;
use crate::lockdown::Lockdown;
use crate::{Error, Imports, RootConfig};
#[derive(Debug, Clone, property::Property)]
#[property(get(public), set(public), mut(public, suffix = "_mut"))]
pub struct UninitializedConfiguration {
pub(crate) manifest: WickConfiguration,
pub(crate) root_config: Option<RuntimeConfig>,
pub(crate) env: Option<HashMap<String, String>>,
pub(crate) lockdown_config: Option<LockdownConfiguration>,
}
impl UninitializedConfiguration {
#[must_use]
pub const fn new(manifest: WickConfiguration) -> Self {
Self {
manifest,
root_config: None,
env: None,
lockdown_config: None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn into_inner(self) -> WickConfiguration {
self.manifest
}
pub fn finish(mut self) -> Result<WickConfiguration, Error> {
debug!(root_config=?self.root_config, env=?self.env.as_ref().map(|c|format!("{} variables",c.len())), "initializing configuration");
expect_configuration_matches(
self
.manifest
.source()
.map_or("<unknown>", |p| p.to_str().unwrap_or("<invalid>")),
self.root_config.as_ref(),
self.manifest.config(),
)
.map_err(Error::ConfigurationInvalid)?;
self.manifest.set_env(self.env);
self.manifest.set_root_config(self.root_config);
self.manifest.initialize()?;
self.manifest.validate()?;
Ok(self.manifest)
}
}
impl Imports for UninitializedConfiguration {
fn imports(&self) -> &[Binding<ImportDefinition>] {
self.manifest.imports()
}
}
impl Lockdown for UninitializedConfiguration {
fn lockdown(&self, id: Option<&str>, lockdown: &LockdownConfiguration) -> Result<(), crate::lockdown::LockdownError> {
self.manifest.lockdown(id, lockdown)?;
Ok(())
}
}
#[derive(Debug, Clone, derive_asset_container::AssetManager, serde::Serialize)]
#[asset(asset(AssetReference))]
#[serde(untagged)]
pub enum WickConfiguration {
Component(ComponentConfiguration),
App(AppConfiguration),
Types(TypesConfiguration),
Tests(TestConfiguration),
Lockdown(LockdownConfiguration),
}
impl WickConfiguration {
pub async fn fetch_tree(
path: impl Into<AssetReference> + Send,
root_config: Option<RuntimeConfig>,
root_env: Option<HashMap<String, String>>,
options: FetchOptions,
) -> Result<ConfigurationTreeNode<WickConfiguration>, Error> {
let mut config = Self::fetch(path, options.clone()).await?;
let source = config.manifest.source().map(ToOwned::to_owned);
config
.manifest
.render_config(source.as_deref(), root_config.as_ref(), root_env.as_ref())?;
let renderer = |runtime_config: Option<RuntimeConfig>, mut child: UninitializedConfiguration| {
child.set_root_config(runtime_config);
child.finish()
};
let children = fetch_children(&config, options, &renderer).await?;
Ok(ConfigurationTreeNode::new(
Entity::LOCAL.into(),
config.finish()?,
children,
))
}
pub async fn fetch_uninitialized_tree(
path: impl Into<AssetReference> + Send,
options: FetchOptions,
) -> Result<ConfigurationTreeNode<UninitializedConfiguration>, Error> {
let config = Self::fetch(path, options.clone()).await?;
let children = fetch_children(&config, options, &|_, b| Ok(b)).await?;
Ok(ConfigurationTreeNode::new(Entity::LOCAL.into(), config, children))
}
pub async fn fetch(
asset: impl Into<AssetReference> + Send,
options: FetchOptions,
) -> Result<UninitializedConfiguration, Error> {
let asset: AssetReference = asset.into();
let cache_result = cache::CONFIG_CACHE.lock().get(&asset).cloned();
let config = if let Some(config) = cache_result {
tracing::trace!(cache_hit = true, asset=%asset.location(), "config::fetch");
config
} else {
tracing::trace!(cache_hit = false, asset=%asset.location(), "config::fetch");
let bytes = asset.fetch(options.clone()).await?;
let source = asset.path().unwrap_or_else(|e| PathBuf::from(format!("<ERROR:{}>", e)));
let config = WickConfiguration::load_from_bytes(&bytes, &Some(source))?;
config.manifest.update_baseurls();
match &config.manifest {
WickConfiguration::Component(c) => {
c.setup_cache(options).await?;
}
WickConfiguration::App(c) => {
c.setup_cache(options).await?;
}
WickConfiguration::Types(_) => {}
WickConfiguration::Tests(_) => {}
WickConfiguration::Lockdown(_) => {}
}
cache::CONFIG_CACHE.lock().insert(asset.clone(), config.clone());
config
};
Ok(config)
}
pub fn load_from_bytes(bytes: &[u8], source: &Option<PathBuf>) -> Result<UninitializedConfiguration, Error> {
let string = &String::from_utf8(bytes.to_vec()).map_err(|_| Error::Utf8)?;
resolve_configuration(string, source)
}
pub fn from_yaml(src: &str, source: &Option<PathBuf>) -> Result<UninitializedConfiguration, Error> {
resolve_configuration(src, source)
}
#[cfg(feature = "v1")]
pub fn into_v1_yaml(self) -> Result<String, Error> {
Ok(serde_yaml::to_string(&self.into_v1()?).unwrap())
}
#[cfg(feature = "v1")]
pub fn into_v1_json(self) -> Result<serde_json::Value, Error> {
Ok(serde_json::to_value(&self.into_v1()?).unwrap())
}
#[cfg(feature = "v1")]
fn into_v1(self) -> Result<crate::v1::WickConfig, Error> {
match self {
WickConfiguration::Component(c) => Ok(crate::v1::WickConfig::ComponentConfiguration(c.try_into()?)),
WickConfiguration::App(c) => Ok(crate::v1::WickConfig::AppConfiguration(c.try_into()?)),
WickConfiguration::Types(c) => Ok(crate::v1::WickConfig::TypesConfiguration(c.try_into()?)),
WickConfiguration::Tests(c) => Ok(crate::v1::WickConfig::TestConfiguration(c.try_into()?)),
WickConfiguration::Lockdown(c) => Ok(crate::v1::WickConfig::LockdownConfiguration(c.try_into()?)),
}
}
#[must_use]
pub fn name(&self) -> Option<&str> {
match self {
WickConfiguration::Component(v) => v.name().map(|s| s.as_str()),
WickConfiguration::App(v) => Some(v.name()),
WickConfiguration::Types(v) => v.name().map(|s| s.as_str()),
WickConfiguration::Tests(v) => v.name().map(|s| s.as_str()),
WickConfiguration::Lockdown(_) => None,
}
}
#[must_use]
pub fn metadata(&self) -> Option<&Metadata> {
match self {
WickConfiguration::Component(v) => v.metadata(),
WickConfiguration::App(v) => v.metadata(),
WickConfiguration::Types(v) => v.metadata(),
WickConfiguration::Tests(_v) => None,
WickConfiguration::Lockdown(_v) => None,
}
}
pub fn validate(&self) -> Result<(), Error> {
match self {
WickConfiguration::Component(v) => v.validate(),
WickConfiguration::App(v) => v.validate(),
WickConfiguration::Types(v) => v.validate(),
WickConfiguration::Tests(v) => v.validate(),
WickConfiguration::Lockdown(v) => v.validate(),
}
}
#[must_use]
fn config(&self) -> &[Field] {
match self {
WickConfiguration::Component(v) => v.config(),
WickConfiguration::App(_v) => Default::default(),
WickConfiguration::Types(_v) => Default::default(),
WickConfiguration::Tests(_v) => Default::default(),
WickConfiguration::Lockdown(_v) => Default::default(),
}
}
fn set_env(&mut self, env: Option<HashMap<String, String>>) -> &mut Self {
match self {
WickConfiguration::App(ref mut v) => {
v.env = env;
}
WickConfiguration::Component(_) => (),
WickConfiguration::Types(_) => (),
WickConfiguration::Tests(_) => (),
WickConfiguration::Lockdown(v) => v.env = env,
}
self
}
pub const fn kind(&self) -> ConfigurationKind {
match self {
WickConfiguration::Component(_) => ConfigurationKind::Component,
WickConfiguration::App(_) => ConfigurationKind::App,
WickConfiguration::Types(_) => ConfigurationKind::Types,
WickConfiguration::Tests(_) => ConfigurationKind::Tests,
WickConfiguration::Lockdown(_) => ConfigurationKind::Lockdown,
}
}
#[must_use]
pub fn resources(&self) -> &[Binding<ResourceDefinition>] {
match self {
WickConfiguration::Component(c) => c.resources(),
WickConfiguration::App(c) => c.resources(),
WickConfiguration::Types(_) => &[],
WickConfiguration::Tests(_) => &[],
WickConfiguration::Lockdown(_) => &[],
}
}
#[must_use]
pub fn version(&self) -> Option<&str> {
match self {
WickConfiguration::Component(v) => v.version(),
WickConfiguration::App(v) => v.version(),
WickConfiguration::Types(v) => v.version(),
WickConfiguration::Tests(_) => None,
WickConfiguration::Lockdown(_) => None,
}
}
#[must_use]
pub fn package(&self) -> Option<&PackageConfig> {
match self {
WickConfiguration::Component(v) => v.package(),
WickConfiguration::App(v) => v.package(),
WickConfiguration::Types(v) => v.package(),
WickConfiguration::Tests(_) => None,
WickConfiguration::Lockdown(_) => None,
}
}
#[allow(clippy::missing_const_for_fn)]
pub fn try_component_config(self) -> Result<ComponentConfiguration, Error> {
match self {
WickConfiguration::Component(v) => Ok(v),
_ => Err(Error::UnexpectedConfigurationKind(
ConfigurationKind::Component,
self.kind(),
)),
}
}
#[allow(clippy::missing_const_for_fn)]
pub fn try_app_config(self) -> Result<AppConfiguration, Error> {
match self {
WickConfiguration::App(v) => Ok(v),
_ => Err(Error::UnexpectedConfigurationKind(ConfigurationKind::App, self.kind())),
}
}
#[allow(clippy::missing_const_for_fn)]
pub fn try_test_config(self) -> Result<TestConfiguration, Error> {
match self {
WickConfiguration::Tests(v) => Ok(v),
_ => Err(Error::UnexpectedConfigurationKind(
ConfigurationKind::Tests,
self.kind(),
)),
}
}
#[allow(clippy::missing_const_for_fn)]
pub fn try_types_config(self) -> Result<TypesConfiguration, Error> {
match self {
WickConfiguration::Types(v) => Ok(v),
_ => Err(Error::UnexpectedConfigurationKind(
ConfigurationKind::Types,
self.kind(),
)),
}
}
#[allow(clippy::missing_const_for_fn)]
pub fn try_lockdown_config(self) -> Result<LockdownConfiguration, Error> {
match self {
WickConfiguration::Lockdown(v) => Ok(v),
_ => Err(Error::UnexpectedConfigurationKind(
ConfigurationKind::Lockdown,
self.kind(),
)),
}
}
fn initialize(&mut self) -> Result<&Self, Error> {
match self {
WickConfiguration::Component(v) => {
v.initialize()?;
}
WickConfiguration::App(v) => {
v.initialize()?;
}
WickConfiguration::Types(_) => (),
WickConfiguration::Tests(v) => {
v.initialize()?;
}
WickConfiguration::Lockdown(v) => {
v.initialize()?;
}
}
self.update_baseurls();
Ok(self)
}
pub fn set_source(&mut self, src: &Path) {
match self {
WickConfiguration::Component(v) => v.set_source(src),
WickConfiguration::App(v) => v.set_source(src),
WickConfiguration::Types(v) => v.set_source(src),
WickConfiguration::Tests(v) => v.set_source(src),
WickConfiguration::Lockdown(v) => v.set_source(src),
}
}
fn update_baseurls(&self) {
match self {
WickConfiguration::Component(v) => v.update_baseurls(),
WickConfiguration::App(v) => v.update_baseurls(),
WickConfiguration::Types(v) => v.update_baseurls(),
WickConfiguration::Tests(v) => v.update_baseurls(),
WickConfiguration::Lockdown(v) => v.update_baseurls(),
}
}
#[must_use]
pub fn source(&self) -> Option<&Path> {
match self {
WickConfiguration::Component(v) => v.source.as_deref(),
WickConfiguration::App(v) => v.source.as_deref(),
WickConfiguration::Types(v) => v.source.as_deref(),
WickConfiguration::Tests(v) => v.source.as_deref(),
WickConfiguration::Lockdown(v) => v.source.as_deref(),
}
}
}
impl Renderable for WickConfiguration {
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), crate::error::ManifestError> {
match self {
WickConfiguration::Component(c) => c.render_config(source, root_config, env),
WickConfiguration::App(c) => c.render_config(source, root_config, env),
WickConfiguration::Types(c) => c.render_config(source, root_config, env),
WickConfiguration::Tests(c) => c.render_config(source, root_config, env),
WickConfiguration::Lockdown(c) => c.render_config(source, root_config, env),
}
}
}
impl Lockdown for WickConfiguration {
fn lockdown(&self, id: Option<&str>, lockdown: &LockdownConfiguration) -> Result<(), crate::lockdown::LockdownError> {
match self {
WickConfiguration::Component(v) => v.lockdown(id, lockdown),
WickConfiguration::App(v) => v.lockdown(id, lockdown),
WickConfiguration::Types(_) => Ok(()),
WickConfiguration::Tests(_) => Ok(()),
WickConfiguration::Lockdown(_) => Ok(()),
}
}
}
impl Imports for WickConfiguration {
fn imports(&self) -> &[Binding<ImportDefinition>] {
match self {
WickConfiguration::Component(c) => c.import(),
WickConfiguration::App(c) => c.import(),
WickConfiguration::Types(_) => &[],
WickConfiguration::Tests(_) => &[],
WickConfiguration::Lockdown(_) => &[],
}
}
}
impl RootConfig for WickConfiguration {
fn root_config(&self) -> Option<&RuntimeConfig> {
match self {
WickConfiguration::App(v) => v.root_config.as_ref(),
WickConfiguration::Component(v) => v.root_config.as_ref(),
WickConfiguration::Types(_) => None,
WickConfiguration::Tests(_) => None,
WickConfiguration::Lockdown(_) => None,
}
}
fn set_root_config(&mut self, config: Option<RuntimeConfig>) {
match self {
WickConfiguration::App(v) => {
v.root_config = config;
}
WickConfiguration::Component(v) => {
v.root_config = config;
}
WickConfiguration::Types(_) => (),
WickConfiguration::Tests(_) => (),
WickConfiguration::Lockdown(_) => (),
}
}
}
#[derive(Debug, Clone, Copy)]
#[must_use]
pub enum ConfigurationKind {
App,
Component,
Types,
Tests,
Lockdown,
}
impl std::fmt::Display for ConfigurationKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConfigurationKind::App => write!(f, "wick/app"),
ConfigurationKind::Component => write!(f, "wick/component"),
ConfigurationKind::Types => write!(f, "wick/types"),
ConfigurationKind::Tests => write!(f, "wick/tests"),
ConfigurationKind::Lockdown => write!(f, "wick/lockdown"),
}
}
}