From 423a37b5cca2e9bfc9a9e333218b8c5f63118135 Mon Sep 17 00:00:00 2001 From: Tip ten Brink <75669206+tiptenbrink@users.noreply.github.com> Date: Thu, 16 May 2024 12:27:57 +0200 Subject: [PATCH] Large change to resolving logic --- examples/config/end/here/there/tidploy.toml | 2 +- .../config/move_resolve/inner/alternate.sh | 2 + .../config/move_resolve/inner/tidploy.toml | 5 + src/filesystem.rs | 51 +++- src/next/api.rs | 4 +- src/next/commands.rs | 17 +- src/next/config.rs | 109 +++++--- src/next/errors.rs | 4 +- src/next/git.rs | 37 ++- src/next/resolve.rs | 210 ++++++++++++--- src/next/run.rs | 16 +- src/next/state.rs | 245 +++++++++--------- 12 files changed, 465 insertions(+), 237 deletions(-) create mode 100755 examples/config/move_resolve/inner/alternate.sh create mode 100644 examples/config/move_resolve/inner/tidploy.toml diff --git a/examples/config/end/here/there/tidploy.toml b/examples/config/end/here/there/tidploy.toml index 27441ad..224dfa4 100644 --- a/examples/config/end/here/there/tidploy.toml +++ b/examples/config/end/here/there/tidploy.toml @@ -1,2 +1,2 @@ [argument] -executable = "here/there/example_im_there.sh" \ No newline at end of file +executable = "example_im_there.sh" \ No newline at end of file diff --git a/examples/config/move_resolve/inner/alternate.sh b/examples/config/move_resolve/inner/alternate.sh new file mode 100755 index 0000000..f64b263 --- /dev/null +++ b/examples/config/move_resolve/inner/alternate.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo "I'm in here!" \ No newline at end of file diff --git a/examples/config/move_resolve/inner/tidploy.toml b/examples/config/move_resolve/inner/tidploy.toml new file mode 100644 index 0000000..5521dd3 --- /dev/null +++ b/examples/config/move_resolve/inner/tidploy.toml @@ -0,0 +1,5 @@ +[state.address] +path = "." + +[argument] +executable = "alternate.sh" \ No newline at end of file diff --git a/src/filesystem.rs b/src/filesystem.rs index ac590b1..4880efe 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,4 +1,4 @@ -use camino::{Utf8Path, Utf8PathBuf}; +use camino::{Utf8Component, Utf8Path, Utf8PathBuf}; use directories::ProjectDirs; use once_cell::sync::OnceCell; use relative_path::{RelativePath, RelativePathBuf}; @@ -98,3 +98,52 @@ impl WrapToPath for RelativePathBuf { // } // } } + +pub trait PathClean { + fn clean(&self) -> Utf8PathBuf; +} + +impl PathClean for Utf8Path { + fn clean(&self) -> Utf8PathBuf { + clean(self) + } +} + +impl PathClean for Utf8PathBuf { + fn clean(&self) -> Utf8PathBuf { + clean(self) + } +} + +/// From https://github.com/danreeves/path-clean/blob/3876d7cb5367997bcda17ce165bf69c4f434cb93/src/lib.rs +/// By Dan Reeves, used under the Apache License 2.0 +/// Changed to work for Utf8Path +pub fn clean

(path: P) -> Utf8PathBuf +where + P: AsRef, +{ + let mut out = Vec::new(); + + for comp in path.as_ref().components() { + match comp { + Utf8Component::CurDir => (), + Utf8Component::ParentDir => match out.last() { + Some(Utf8Component::RootDir) => (), + Some(Utf8Component::Normal(_)) => { + out.pop(); + } + None + | Some(Utf8Component::CurDir) + | Some(Utf8Component::ParentDir) + | Some(Utf8Component::Prefix(_)) => out.push(comp), + }, + comp => out.push(comp), + } + } + + if !out.is_empty() { + out.iter().collect() + } else { + Utf8PathBuf::from(".") + } +} diff --git a/src/next/api.rs b/src/next/api.rs index 4c8fe22..0a6f1f0 100644 --- a/src/next/api.rs +++ b/src/next/api.rs @@ -36,14 +36,14 @@ impl GlobalArguments { fn run_in(&self) -> AddressIn { match self.address.clone() { Some(address) => address, - None => AddressIn::from_run(None, None, None), + None => AddressIn::from_run(None, None), } } fn secret_in(&self) -> AddressIn { match self.address.clone() { Some(address) => address, - None => AddressIn::from_secret(None, None, None), + None => AddressIn::from_secret(None, None), } } diff --git a/src/next/commands.rs b/src/next/commands.rs index 2ea9991..1ed7e3a 100644 --- a/src/next/commands.rs +++ b/src/next/commands.rs @@ -14,10 +14,9 @@ pub struct NextSub { /// the current directory or Git root dir #[arg(long = "resolve-root")] resolve_root: Option, - - /// Location relative to state root to stop reading configs, inclusive. - #[arg(long = "state-root")] - state_root: Option, + // /// Location relative to state root to stop reading configs, inclusive. + // #[arg(long = "state-root")] + // state_root: Option, } #[derive(Subcommand, Debug)] @@ -79,7 +78,7 @@ pub enum NextCommands { repo: Option, #[arg(long = "local")] - local: bool + local: bool, }, } @@ -101,7 +100,6 @@ pub enum AddressSubCommands { pub fn match_command(next_sub: NextSub, _cmd: Command) -> Result { let NextSub { subcommand, - state_root, resolve_root, } = next_sub; @@ -111,7 +109,7 @@ pub fn match_command(next_sub: NextSub, _cmd: Command) -> Result { - let addr_in = AddressIn::from_secret(resolve_root, state_path, state_root); + let addr_in = AddressIn::from_secret(resolve_root, state_path); secret_command(addr_in, cwd_infer, None, None, key, None)?; @@ -124,7 +122,7 @@ pub fn match_command(next_sub: NextSub, _cmd: Command) -> Result { - let addr_in = AddressIn::from_run(resolve_root, state_path, state_root); + let addr_in = AddressIn::from_run(resolve_root, state_path); let out = run_command( addr_in, git_infer, @@ -147,8 +145,7 @@ pub fn match_command(next_sub: NextSub, _cmd: Command) -> Result { - let addr_in = - AddressIn::from_deploy(repo, local, git_ref, resolve_root, state_path, state_root); + let addr_in = AddressIn::from_deploy(repo, local, git_ref, resolve_root, state_path); let out = run_command( addr_in, !cwd_infer, diff --git a/src/next/config.rs b/src/next/config.rs index b58b3e5..5d625fd 100644 --- a/src/next/config.rs +++ b/src/next/config.rs @@ -37,7 +37,8 @@ pub(crate) enum ConfigAddress { Local { path: String, state_path: Option, - state_root: Option, + // arg_root: Option, + // arg_path: Option, }, Git { url: String, @@ -45,14 +46,13 @@ pub(crate) enum ConfigAddress { git_ref: String, target_path: Option, state_path: Option, - state_root: Option, + // arg_root: Option, + // arg_path: Option, }, } #[derive(Deserialize, Debug)] pub(crate) struct StateConfig { - pub(crate) state_root: Option, - pub(crate) state_path: Option, pub(crate) address: Option, } @@ -93,6 +93,13 @@ pub(crate) fn load_dploy_config(config_dir_path: &Utf8Path) -> Result Result, ConfigError> { +// let config = load_dploy_config(config_dir_path)?; +// config.a + +// Ok(dploy_config) +// } + pub(crate) fn overwrite_option(original: Option, replacing: Option) -> Option { match replacing { Some(replacing) => Some(replacing), @@ -164,32 +171,40 @@ fn overwrite_arguments( } } -fn overwrite_state_config(base: StateConfig, replacing: StateConfig) -> StateConfig { - StateConfig { - state_path: replacing.state_path.or(base.state_path), - state_root: replacing.state_root.or(base.state_root), - address: replacing.address.or(base.address), - } -} - -fn overwrite_config(root_config: Config, overwrite_config: Config) -> Config { - Config { - argument: merge_option( - root_config.argument, - overwrite_config.argument, - &overwrite_arguments, - ), - state: merge_option( - root_config.state, - overwrite_config.state, - &overwrite_state_config, - ), - } +// fn overwrite_state_config(base: StateConfig, replacing: StateConfig) -> StateConfig { +// StateConfig { +// address: replacing.address.or(base.address), +// } +// } + +// fn overwrite_config(root_config: Config, overwrite_config: Config) -> Config { +// Config { +// argument: merge_option( +// root_config.argument, +// overwrite_config.argument, +// &overwrite_arguments, +// ), +// state: merge_option( +// root_config.state, +// overwrite_config.state, +// &overwrite_state_config, +// ), +// } +// } + +fn overwrite_arg_config( + root_config: Option, + overwrite_config: Option, +) -> Option { + merge_option(root_config, overwrite_config, &overwrite_arguments) } /// The relative path is normalized, so if it contains symlinks unexpected behavior might happen. /// This is designed to work only for simple descent down a directory. -fn get_component_paths(start_path: &Utf8Path, final_path: &RelativePath) -> Vec { +pub(crate) fn get_component_paths( + start_path: &Utf8Path, + final_path: &RelativePath, +) -> Vec { let paths: Vec = final_path .normalize() .components() @@ -202,26 +217,54 @@ fn get_component_paths(start_path: &Utf8Path, final_path: &RelativePath) -> Vec< paths } -/// Be sure the relative path is just a simple ./child/child/child2 ...etc relative path (the leading -/// ./ is optional) -pub(crate) fn traverse_configs( +// /// Be sure the relative path is just a simple ./child/child/child2 ...etc relative path (the leading +// /// ./ is optional) +// pub(crate) fn traverse_configs( +// start_path: &Utf8Path, +// final_path: &RelativePath, +// ) -> Result { +// debug!( +// "Traversing configs from {:?} to relative {:?}", +// start_path, final_path +// ); + +// let root_config = load_dploy_config(start_path)?; + +// let paths = get_component_paths(start_path, final_path); + +// let combined_config = paths.iter().try_fold(root_config, |state, path| { +// let inner_config = load_dploy_config(path); + +// match inner_config { +// Ok(config) => ControlFlow::Continue(overwrite_config(state, config)), +// Err(source) => ControlFlow::Break(source), +// } +// }); + +// match combined_config { +// ControlFlow::Break(e) => Err(e), +// ControlFlow::Continue(config) => Ok(config), +// } +// } + +pub(crate) fn traverse_arg_configs( start_path: &Utf8Path, final_path: &RelativePath, -) -> Result { +) -> Result, ConfigError> { debug!( "Traversing configs from {:?} to relative {:?}", start_path, final_path ); - let root_config = load_dploy_config(start_path)?; + let root_config = load_dploy_config(start_path)?.argument; let paths = get_component_paths(start_path, final_path); let combined_config = paths.iter().try_fold(root_config, |state, path| { - let inner_config = load_dploy_config(path); + let inner_config = load_dploy_config(path).map(|c| c.argument); match inner_config { - Ok(config) => ControlFlow::Continue(overwrite_config(state, config)), + Ok(config) => ControlFlow::Continue(overwrite_arg_config(state, config)), Err(source) => ControlFlow::Break(source), } }); diff --git a/src/next/errors.rs b/src/next/errors.rs index 9840b0b..4544bcc 100644 --- a/src/next/errors.rs +++ b/src/next/errors.rs @@ -34,8 +34,8 @@ pub(crate) enum StateErrorKind { InvalidPath, #[error("State manipulation failed due to IO error! {0}")] IO(#[from] IOError), - #[error("{0}")] - InvalidRoot(String), + // #[error("{0}")] + // InvalidRoot(String), #[error("{0}")] Secret(#[from] SecretError), #[error("{0}")] diff --git a/src/next/git.rs b/src/next/git.rs index 8deb188..cf6cec9 100644 --- a/src/next/git.rs +++ b/src/next/git.rs @@ -1,5 +1,5 @@ use camino::Utf8Path; -use relative_path::RelativePath; +use relative_path::RelativePathBuf; use spinoff::{spinners, Spinner}; use tracing::debug; @@ -11,7 +11,7 @@ use crate::{ use super::{ errors::{GitError, GitProcessError, StateError}, process::process_complete_output, - state::{parse_url_name, GitAddress, State}, + state::{parse_url_name, GitAddress, State, StateStep}, }; use core::fmt::Debug; use std::{ @@ -153,11 +153,10 @@ pub(crate) fn ls_remote(repo_dir: &Utf8Path, pattern: &str) -> Result, GitError>>()?; - sha_refs - .retain(|sr| { - // We don't care about the remotes of our remote - !(*sr.tag).contains("refs/remotes") - }); + sha_refs.retain(|sr| { + // We don't care about the remotes of our remote + !(*sr.tag).contains("refs/remotes") + }); let commit = if sha_refs.is_empty() { pattern @@ -230,12 +229,13 @@ fn str_last_n(input: &str, n: usize) -> &str { pub(crate) fn get_dir_from_git( address: GitAddress, - state_path: &RelativePath, - state_root: &RelativePath, + state_path: RelativePathBuf, + // state_root: &RelativePath, store_dir: &Utf8Path, ) -> Result { let url = if address.local { - git_root_dir(Utf8Path::new(&address.url)).to_state_err("Failed to get Git directory from local URL.")? + git_root_dir(Utf8Path::new(&address.url)) + .to_state_err("Failed to get Git directory from local URL.")? } else { address.url }; @@ -250,7 +250,7 @@ pub(crate) fn get_dir_from_git( repo_clone(store_dir, &dir_name, &url) .to_state_err("Error cloning repository in address.".to_owned())?; let meta_filename = format!("tidploy_repo_meta_{}", &dir_name); - let mut file = File::create(target_dir.join(&meta_filename)) + let mut file = File::create(target_dir.join(meta_filename)) .to_state_err("Failed to create metadata file!")?; let metadata = format!("url:{}\nname:{}", &url, &name); file.write_all(metadata.as_bytes()) @@ -262,17 +262,17 @@ pub(crate) fn get_dir_from_git( let commit_short = str_last_n(&commit, 10); let commit_dir = store_dir.join("c"); - let state_root_git = address.path.join(state_root); - let state_path_git = state_root.join(state_path); + //let state_root_git = address.path.join(state_root); + let state_path_git = address.path.join(&state_path); // Paths might not exist, so always do this - let mut paths = vec![state_root_git.as_str(), state_path_git.as_str()]; + let mut paths = vec![state_path_git.as_str()]; paths.sort(); let paths_name = paths.join("_"); let encoded_paths = hash_last_n(&paths_name, 8); let commit_path = commit_dir .join(&dir_name) - .join(&commit_short) + .join(commit_short) .join(&encoded_paths); if !commit_path.exists() { @@ -287,7 +287,7 @@ pub(crate) fn get_dir_from_git( remove_dir_all(commit_path.join(".git")) .to_state_err("Error removing .git directory.".to_owned())?; let meta_filename = format!("tidploy_deploy_meta_{}_{}", &commit_short, &encoded_paths); - let mut file = File::create(commit_path.join(&meta_filename)) + let mut file = File::create(commit_path.join(meta_filename)) .to_state_err("Failed to create metadata file!")?; let metadata = format!("commit:{}\npaths:{}", &commit, &paths_name); file.write_all(metadata.as_bytes()) @@ -297,9 +297,8 @@ pub(crate) fn get_dir_from_git( Ok(State { name, resolve_root: address.path.to_utf8_path(&commit_path), - state_root: state_root.to_owned(), - state_path: state_path.to_owned(), - address: None, + step: StateStep::Config, + state_path, }) } diff --git a/src/next/resolve.rs b/src/next/resolve.rs index e25a6b1..9561490 100644 --- a/src/next/resolve.rs +++ b/src/next/resolve.rs @@ -1,14 +1,20 @@ -use std::{env, fmt::Debug}; +use std::{env, fmt::Debug, ops::ControlFlow}; use camino::{Utf8Path, Utf8PathBuf}; use relative_path::{RelativePath, RelativePathBuf}; use tracing::{debug, instrument}; -use crate::filesystem::WrapToPath; +use crate::{ + filesystem::WrapToPath, + next::config::{get_component_paths, merge_option}, +}; use super::{ - config::{merge_vars, traverse_configs, ArgumentConfig, ConfigScope, ConfigVar}, - errors::ResolutionError, + config::{ + load_dploy_config, merge_vars, traverse_arg_configs, ArgumentConfig, Config, ConfigScope, + ConfigVar, + }, + errors::{ConfigError, ResolutionError, StateError, WrapStateErr}, state::ResolveState, }; @@ -20,8 +26,7 @@ pub(crate) struct SecretScopeArguments { pub(crate) require_hash: Option, } -impl SecretScopeArguments { - /// Overrides fields with other if other has them defined +impl Mergeable for SecretScopeArguments { fn merge(self, other: Self) -> Self { Self { service: other.service.or(self.service), @@ -43,15 +48,70 @@ impl From for SecretScopeArguments { } } +pub(crate) trait Mergeable { + fn merge(self, other: Self) -> Self; +} + +impl Mergeable for Option { + fn merge(self, other: Self) -> Self { + let run_merge = |a: T, b: T| -> T { a.merge(b) }; + + merge_option(self, other, &run_merge) + } +} + +pub(crate) trait Resolved { + fn resolve(self, resolve_root: &Utf8Path) -> U; +} + +pub(crate) trait Resolvable { + fn resolve_from(value: T, resolve_root: &Utf8Path) -> Self; +} + +impl> Resolved for T { + fn resolve(self, resolve_root: &Utf8Path) -> U { + U::resolve_from(self, resolve_root) + } +} + +impl Resolvable for Utf8PathBuf { + fn resolve_from(value: String, resolve_root: &Utf8Path) -> Utf8PathBuf { + let p = RelativePathBuf::from(value); + p.to_utf8_path(resolve_root) + } +} + +impl Resolvable for Option { + fn resolve_from(value: Config, resolve_root: &Utf8Path) -> Option { + value + .argument + .map(|c| RunArguments::from_config(c, resolve_root)) + } +} + +impl Resolvable for Option { + fn resolve_from(value: Config, _resolve_root: &Utf8Path) -> Option { + value + .argument + .and_then(|c| c.scope.map(SecretScopeArguments::from)) + } +} + +impl> Resolvable> for Option { + fn resolve_from(value: Option, resolve_root: &Utf8Path) -> Option { + value.map(|t| t.resolve(resolve_root)) + } +} + #[derive(Default)] pub(crate) struct RunArguments { - pub(crate) executable: Option, - pub(crate) execution_path: Option, + pub(crate) executable: Option, + pub(crate) execution_path: Option, pub(crate) envs: Vec, pub(crate) scope_args: SecretScopeArguments, } -impl RunArguments { +impl Mergeable for RunArguments { /// Overrides fields with other if other has them defined fn merge(self, other: Self) -> Self { Self { @@ -63,11 +123,11 @@ impl RunArguments { } } -impl From for RunArguments { - fn from(value: ArgumentConfig) -> Self { +impl RunArguments { + fn from_config(value: ArgumentConfig, resolve_root: &Utf8Path) -> Self { RunArguments { - executable: value.executable, - execution_path: value.execution_path, + executable: value.executable.resolve(resolve_root), + execution_path: value.execution_path.resolve(resolve_root), envs: value.envs.unwrap_or_default(), scope_args: value.scope.map(|s| s.into()).unwrap_or_default(), } @@ -126,7 +186,7 @@ fn env_secret_args() -> SecretArguments { } /// Note that `envs` cannot be set from env and must thus always be replaced with some sensible value. -fn env_run_args() -> RunArguments { +fn env_run_args(resolve_root: &Utf8Path) -> RunArguments { let scope_args = env_scope_args(); let mut run_arguments = RunArguments { scope_args, @@ -135,8 +195,10 @@ fn env_run_args() -> RunArguments { for (k, v) in env::vars() { match k.as_str() { - "TIDPLOY_RUN_EXECUTABLE" => run_arguments.executable = Some(v), - "TIDPLOY_RUN_EXECUTION_PATH" => run_arguments.execution_path = Some(v), + "TIDPLOY_RUN_EXECUTABLE" => run_arguments.executable = Some(v.resolve(resolve_root)), + "TIDPLOY_RUN_EXECUTION_PATH" => { + run_arguments.execution_path = Some(v.resolve(resolve_root)) + } _ => {} } } @@ -147,7 +209,7 @@ fn env_run_args() -> RunArguments { pub(crate) trait Resolve: Sized { fn merge_env_config( self, - state_root: &Utf8Path, + resolve_root: &Utf8Path, state_path: &RelativePath, ) -> Result; @@ -172,44 +234,109 @@ fn resolve_scope( } } -impl Resolve for RunArguments { - fn merge_env_config( - self, - state_root: &Utf8Path, - state_path: &RelativePath, - ) -> Result { - let config = traverse_configs(state_root, state_path)?; +// impl Resolve for RunArguments { +// fn merge_env_config( +// self, +// resolve_root: &Utf8Path, +// state_path: &RelativePath, +// ) -> Result { +// let config = traverse_arg_configs(resolve_root, state_path)?; + +// let run_args_env = env_run_args(); + +// let merged_args = run_args_env.merge(self); + +// let config_run = config.map(RunArguments::from).unwrap_or_default(); + +// Ok(config_run.merge(merged_args)) +// } + +// fn resolve(self, resolve_root: &Utf8Path, name: &str, sub: &str, hash: &str) -> RunResolved { +// let scope = resolve_scope(self.scope_args, name, sub, hash); + +// let relative_exe = RelativePathBuf::from(self.executable.unwrap_or("entrypoint.sh".to_owned())); +// let relative_exn_path = RelativePathBuf::from(self.execution_path.unwrap_or("".to_owned())); +// RunResolved { +// executable: relative_exe.to_utf8_path(resolve_root), +// execution_path: relative_exn_path.to_utf8_path(resolve_root), +// envs: self.envs, +// scope, +// } +// } +// } + +pub(crate) fn resolve_run( + resolve_state: ResolveState, + cli_args: RunArguments, +) -> Result { + let run_args_env = env_run_args(&resolve_state.resolve_root); + let merged_args = run_args_env.merge(cli_args); + + let config_args: Option = + traverse_args(&resolve_state.resolve_root, &resolve_state.state_path) + .to_state_err("Failed to traverse config.")?; + + let final_args = config_args.unwrap_or_default().merge(merged_args); + + let scope = resolve_scope( + final_args.scope_args, + &resolve_state.name, + &resolve_state.sub, + &resolve_state.hash, + ); + + let execution_path = final_args + .execution_path + .unwrap_or_else(|| resolve_state.resolve_root.clone()); + + let resolved = RunResolved { + executable: final_args + .executable + .unwrap_or_else(|| execution_path.join("entrypoint.sh")), + execution_path, + envs: final_args.envs, + scope, + }; - let run_args_env = env_run_args(); + Ok(resolved) +} - let merged_args = run_args_env.merge(self); +pub(crate) fn traverse_args + Mergeable>( + start_path: &Utf8Path, + final_path: &RelativePath, +) -> Result { + debug!( + "Traversing configs from {:?} to relative {:?}", + start_path, final_path + ); - let config_run = config.argument.map(RunArguments::from).unwrap_or_default(); + let root_config = load_dploy_config(start_path)?; + let root_args = U::resolve_from(root_config, start_path); - Ok(config_run.merge(merged_args)) - } + let paths = get_component_paths(start_path, final_path); - fn resolve(self, resolve_root: &Utf8Path, name: &str, sub: &str, hash: &str) -> RunResolved { - let scope = resolve_scope(self.scope_args, name, sub, hash); + let combined_config = paths.iter().try_fold(root_args, |state, path| { + let inner_config = load_dploy_config(path).map(|c| U::resolve_from(c, path)); - let relative_exe = RelativePathBuf::from(self.executable.unwrap_or("entrypoint.sh".to_owned())); - let relative_exn_path = RelativePathBuf::from(self.execution_path.unwrap_or("".to_owned())); - RunResolved { - executable: relative_exe.to_utf8_path(resolve_root), - execution_path: relative_exn_path.to_utf8_path(resolve_root), - envs: self.envs, - scope, + match inner_config { + Ok(config) => ControlFlow::Continue(state.merge(config)), + Err(source) => ControlFlow::Break(source), } + }); + + match combined_config { + ControlFlow::Break(e) => Err(e), + ControlFlow::Continue(config) => Ok(config), } } impl Resolve for SecretArguments { fn merge_env_config( self, - state_root: &Utf8Path, + resolve_root: &Utf8Path, state_path: &RelativePath, ) -> Result { - let config = traverse_configs(state_root, state_path)?; + let config = traverse_arg_configs(resolve_root, state_path)?; let secret_args_env = env_secret_args(); @@ -219,7 +346,6 @@ impl Resolve for SecretArguments { }; let config_scope = config - .argument .and_then(|a| a.scope) .map(SecretScopeArguments::from) .unwrap_or_default(); @@ -251,7 +377,7 @@ pub(crate) fn merge_and_resolve( unresolved_args: impl Resolve, state: ResolveState, ) -> Result { - let merged_args = unresolved_args.merge_env_config(&state.state_root, &state.state_path)?; + let merged_args = unresolved_args.merge_env_config(&state.resolve_root, &state.state_path)?; let resolved = merged_args.resolve(&state.resolve_root, &state.name, &state.sub, &state.hash); debug!("Resolved as {:?}", resolved); diff --git a/src/next/run.rs b/src/next/run.rs index 8cbb22e..ee8aa55 100644 --- a/src/next/run.rs +++ b/src/next/run.rs @@ -6,7 +6,7 @@ use crate::{ archives::extract_archive, filesystem::{get_dirs, WrapToPath}, next::{ - resolve::{merge_and_resolve, RunArguments, SecretScopeArguments}, + resolve::{resolve_run, Resolved, RunArguments, SecretScopeArguments}, secrets::secret_vars_to_envs, state::{create_resolve_state, parse_cli_vars, InferContext}, }, @@ -111,12 +111,6 @@ pub(crate) fn run_command_input( service: run_options.service, ..Default::default() }; - let run_args = RunArguments { - executable, - execution_path, - envs: parse_cli_vars(variables), - scope_args, - }; let infer_ctx = if git_infer { InferContext::Git } else { @@ -124,8 +118,14 @@ pub(crate) fn run_command_input( }; let resolve_state = create_resolve_state(addr_in, infer_ctx, state_options.unwrap_or_default())?; + let run_args = RunArguments { + executable: executable.resolve(&resolve_state.resolve_root), + execution_path: execution_path.resolve(&resolve_state.resolve_root), + envs: parse_cli_vars(variables), + scope_args, + }; - let run_resolved = merge_and_resolve(run_args, resolve_state)?; + let run_resolved = resolve_run(resolve_state, run_args)?; run_unit_input(run_resolved, run_options.input_bytes) } diff --git a/src/next/state.rs b/src/next/state.rs index 705616f..0ae55d0 100644 --- a/src/next/state.rs +++ b/src/next/state.rs @@ -2,12 +2,15 @@ use std::env::current_dir; use camino::{Utf8Path, Utf8PathBuf}; use relative_path::{RelativePath, RelativePathBuf}; -use tracing::{debug, instrument}; +use tracing::debug; -use crate::{filesystem::WrapToPath, next::git::git_root_origin_url}; +use crate::{ + filesystem::WrapToPath, + next::{config::load_dploy_config, git::git_root_origin_url}, +}; use super::{ - config::{traverse_configs, ConfigAddress, ConfigVar, StateConfig}, + config::{ConfigAddress, ConfigVar}, errors::{AddressError, StateError, StateErrorKind, WrapStateErr}, fs::get_dirs, git::get_dir_from_git, @@ -28,7 +31,6 @@ impl Default for InferContext { pub struct LocalAddressIn { pub resolve_root: Option, pub state_path: Option, - pub state_root: Option, } #[derive(Debug, Clone, Default)] pub struct GitAddressIn { @@ -37,7 +39,6 @@ pub struct GitAddressIn { pub git_ref: Option, pub target_resolve_root: Option, pub state_path: Option, - pub state_root: Option, } #[derive(Debug, Clone)] @@ -47,27 +48,17 @@ pub enum AddressIn { } impl AddressIn { - pub(crate) fn from_run( - resolve_root: Option, - state_path: Option, - state_root: Option, - ) -> Self { + pub(crate) fn from_run(resolve_root: Option, state_path: Option) -> Self { AddressIn::Local(LocalAddressIn { resolve_root, state_path, - state_root, }) } - pub(crate) fn from_secret( - resolve_root: Option, - state_path: Option, - state_root: Option, - ) -> Self { + pub(crate) fn from_secret(resolve_root: Option, state_path: Option) -> Self { AddressIn::Local(LocalAddressIn { resolve_root, state_path, - state_root, }) } @@ -77,7 +68,6 @@ impl AddressIn { git_ref: Option, target_resolve_root: Option, state_path: Option, - state_root: Option, ) -> Self { AddressIn::Git(GitAddressIn { url, @@ -85,7 +75,6 @@ impl AddressIn { git_ref, target_resolve_root, state_path, - state_root, }) } } @@ -107,7 +96,8 @@ pub(crate) fn parse_cli_vars(envs: Vec) -> Vec { pub(crate) struct Address { pub(crate) name: String, pub(crate) root: AddressRoot, - pub(crate) state_root: RelativePathBuf, + // pub(crate) arg_root: RelativePathBuf, + // pub(crate) arg_path: RelativePathBuf, pub(crate) state_path: RelativePathBuf, } @@ -132,7 +122,10 @@ fn url_local(url: String, local: bool, relative: &Utf8Path) -> String { if local { let path = Utf8Path::new(&url); if path.is_relative() { - RelativePath::new(&url).to_utf8_path(relative).as_str().to_owned() + RelativePath::new(&url) + .to_utf8_path(relative) + .as_str() + .to_owned() } else { url } @@ -152,7 +145,8 @@ impl Address { git_ref, target_path, state_path, - state_root, + // arg_root, + // arg_path } => { let local = local.unwrap_or_default(); let url = url_local(url, local, resolve_root); @@ -166,13 +160,15 @@ impl Address { path: RelativePathBuf::from(target_path.unwrap_or_default()), }), state_path: RelativePathBuf::from(state_path.unwrap_or_default()), - state_root: RelativePathBuf::from(state_root.unwrap_or_default()), + // arg_root: RelativePathBuf::from(arg_root.unwrap_or_default()), + // arg_path: RelativePathBuf::from(arg_path.unwrap_or_default()), } - } , + } ConfigAddress::Local { path, state_path, - state_root, + // arg_root, + // arg_path } => { let address_root = Utf8PathBuf::from(path.clone()); let address_rel = RelativePathBuf::from_path(&address_root).ok(); @@ -182,13 +178,15 @@ impl Address { address_root }; - let name = parse_url_name(root.as_str()).to_state_err("Error getting name from address root!")?; + let name = parse_url_name(root.as_str()) + .to_state_err("Error getting name from address root!")?; Address { name, root: AddressRoot::Local(root), state_path: RelativePathBuf::from(state_path.unwrap_or_default()), - state_root: RelativePathBuf::from(state_root.unwrap_or_default()), + // arg_root: RelativePathBuf::from(arg_root.unwrap_or_default()), + // arg_path: RelativePathBuf::from(arg_path.unwrap_or_default()), } } }; @@ -205,11 +203,10 @@ impl Address { local, git_ref, state_path, - state_root, target_resolve_root, }) => { let current_dir = get_current_dir()?; - + let url = if let Some(url) = url { url_local(url, local, ¤t_dir) } else { @@ -232,13 +229,13 @@ impl Address { path: RelativePathBuf::from(target_resolve_root.unwrap_or_default()), }), state_path: RelativePathBuf::from(state_path.unwrap_or_default()), - state_root: RelativePathBuf::from(state_root.unwrap_or_default()), + // arg_root: RelativePathBuf::from(arg_root.unwrap_or_default()), + // arg_path: RelativePathBuf::from(arg_path.unwrap_or_default()), } } AddressIn::Local(LocalAddressIn { resolve_root, state_path, - state_root, }) => { let resolve_root = resolve_root.map(Utf8PathBuf::from).unwrap_or_default(); let resolve_root_rel = RelativePathBuf::from_path(&resolve_root).ok(); @@ -266,7 +263,6 @@ impl Address { name, root: AddressRoot::Local(resolve_root), state_path: RelativePathBuf::from(state_path.unwrap_or_default()), - state_root: RelativePathBuf::from(state_root.unwrap_or_default()), } } }; @@ -285,84 +281,95 @@ pub(crate) struct GitAddress { pub(crate) path: RelativePathBuf, } +#[derive(Debug, Clone)] +pub(crate) enum StateStep { + None, + Address(Address), + Config, +} + #[derive(Debug, Clone)] pub(crate) struct State { pub(crate) name: String, - pub(crate) state_root: RelativePathBuf, - pub(crate) state_path: RelativePathBuf, + // pub(crate) arg_root: RelativePathBuf, + // pub(crate) arg_path: RelativePathBuf, pub(crate) resolve_root: Utf8PathBuf, - pub(crate) address: Option

, + pub(crate) state_path: RelativePathBuf, + pub(crate) step: StateStep, // pub(crate) address: Option
, } impl State { - fn merge_config(&self, other: StateConfig) -> Result { - let address = other - .address - .map(|a| Address::from_config_addr(a, &self.resolve_root)) - .transpose()?.or(self.address.clone()); - - let state = Self { - name: self.name.clone(), - state_path: other - .state_path - .map(Into::into) - .unwrap_or(self.state_path.clone()), - state_root: other - .state_root - .map(Into::into) - .unwrap_or(self.state_root.clone()), - resolve_root: self.resolve_root.clone(), - address, - }; - - Ok(state) - } - - /// Checks if a state is different to another one for the purposes of converging to a state. - fn same(&self, other: &Self) -> bool { - self.resolve_root == other.resolve_root - && self.state_path.normalize() == other.state_path.normalize() - && self.state_root.normalize() == other.state_root.normalize() - } + // fn merge_config(&self, other: StateConfig) -> Result { + // let address = other + // .address + // .map(|a| Address::from_config_addr(a, &self.resolve_root)) + // .transpose()?.or(self.address.clone()); + + // let state = Self { + // name: self.name.clone(), + // state_path: other + // .state_path + // .map(Into::into) + // .unwrap_or(self.state_path.clone()), + // state_root: other + // .state_root + // .map(Into::into) + // .unwrap_or(self.state_root.clone()), + // resolve_root: self.resolve_root.clone(), + // address, + // }; + + // Ok(state) + // } + + // /// Checks if a state is different to another one for the purposes of converging to a state. + // fn same(&self, other: &Self) -> bool { + // self.resolve_root == other.resolve_root + // && self.state_path.normalize() == other.state_path.normalize() + // && self.state_root.normalize() == other.state_root.normalize() + // } } #[derive(Debug)] pub(crate) struct ResolveState { - pub(crate) state_root: Utf8PathBuf, - pub(crate) state_path: RelativePathBuf, pub(crate) resolve_root: Utf8PathBuf, + pub(crate) state_path: RelativePathBuf, pub(crate) name: String, pub(crate) sub: String, pub(crate) hash: String, } -#[instrument(name = "converge", level = "debug", skip_all)] -fn converge_state(state: &State) -> Result { - let mut state = state.clone(); - let mut i = 0; - let iter = loop { - let state_root_path = state.state_root.to_utf8_path(&state.resolve_root); - let config = traverse_configs(&state_root_path, &state.state_path) - .to_state_err("Failed to read configs for determining new state.")?; - let new_state = if let Some(config_state) = config.state { - state.merge_config(config_state)? - } else { - break i + 1; - }; - debug!("New intermediate state {:?}", &new_state); - - let do_break = new_state.same(&state); - state = new_state; - if do_break { - break i + 1; - } - - i += 1; - }; - debug!("Converged to state {:?} in {} iterations.", &state, iter); - - Ok(state) -} +// #[instrument(name = "converge", level = "debug", skip_all)] +// fn converge_state(state: &State) -> Result { +// let mut state = state.clone(); +// let mut i = 0; +// //let state_root_path = state.state_root.to_utf8_path(&state.resolve_root); +// //let paths = get_component_paths(&state_root_path, &state.state_path); +// let iter = loop { + +// let state_path = state.state_path.to_utf8_path(&state.resolve_root); + +// let config = load_dploy_config(&state_path) +// .to_state_err("Failed to read configs for determining new state.")?; +// let new_state = if let Some(config_state) = config.state { +// state.merge_config(co/nfig_state)? +// } else { +// break i + 1; +// }; +// debug!("New intermediate state {:?}", &new_state); + +// let do_break = new_state.same(&state); +// state = new_state; +// if do_break { +// break i + 1; +// } + +// i += 1; +// }; +// debug!("Converged to state {:?} in {} iterations.", &state, iter); + +// Ok(state) +// } /// Parse a repo URL to extract a "name" from it, as well as encode the part before the name to still uniquely /// identify it. Only supports forward slashes as path seperator. @@ -394,23 +401,20 @@ pub(crate) fn parse_url_name(url: &str) -> Result { fn resolve_address(address: Address, store_dir: &Utf8Path) -> Result { debug!("Resolving address {:?}", address); - + let Address { name, state_path, - state_root, root, } = address; - match root { - AddressRoot::Git(addr) => get_dir_from_git(addr, &state_path, &state_root, store_dir), + AddressRoot::Git(addr) => get_dir_from_git(addr, state_path, store_dir), AddressRoot::Local(path) => Ok(State { name, resolve_root: path, state_path, - state_root, - address: None, + step: StateStep::Config, }), } } @@ -427,14 +431,26 @@ impl Default for StateOptions { } } -pub(crate) fn converge_address(address: Address, opt: StateOptions) -> Result { - let mut state = resolve_address(address, &opt.store_dir)?; - state = converge_state(&state)?; - - while let Some(address) = state.address.clone() { - state = resolve_address(address, &opt.store_dir)?; - debug!("Moved to address, new state is {:?}", state); - state = converge_state(&state)?; +pub(crate) fn converge_state(mut state: State, opt: StateOptions) -> Result { + loop { + match state.step { + StateStep::None => break, + StateStep::Address(address) => state = resolve_address(address, &opt.store_dir)?, + StateStep::Config => { + let config_dir = state.state_path.to_utf8_path(&state.resolve_root); + let config = load_dploy_config(&config_dir) + .to_state_err("Failed to load config.")? + .state; + let addr = config.and_then(|c| c.address); + match addr { + Some(addr) => { + state.step = + StateStep::Address(Address::from_config_addr(addr, &config_dir)?) + } + None => state.step = StateStep::None, + } + } + } } Ok(state) @@ -446,21 +462,12 @@ pub(crate) fn create_resolve_state( opt: StateOptions, ) -> Result { let address = Address::from_addr_in(addr_in, infer_ctx)?; - let state = converge_address(address, opt)?; - - - - // let name = state - // .resolve_root - // .file_name() - // .map(|s| s.to_string()) - // .ok_or_else(|| StateErrorKind::InvalidRoot(state.resolve_root.to_string())) - // .to_state_err("Getting context name from context root path for new state.")?; + let state = resolve_address(address, &opt.store_dir)?; + let state = converge_state(state, opt)?; let resolve_state = ResolveState { - state_root: state.state_root.to_utf8_path(&state.resolve_root), - state_path: state.state_path, resolve_root: state.resolve_root, + state_path: state.state_path, name: state.name, sub: "tidploy_root".to_owned(), hash: "todo_hash".to_owned(),