From 2ffb5ba02026d1865bb0d5e610d379d6613f60f1 Mon Sep 17 00:00:00 2001 From: Tip ten Brink <75669206+tiptenbrink@users.noreply.github.com> Date: Fri, 3 May 2024 15:11:38 +0200 Subject: [PATCH] use utf-8 path --- Cargo.lock | 8 ++++++++ Cargo.toml | 4 +++- src/archives.rs | 24 +++++++++-------------- src/commands.rs | 40 +++++++++++++++++++++++++------------ src/config.rs | 3 ++- src/filesystem.rs | 48 ++++++++++++++++++++++++++++++++++++--------- src/git.rs | 18 ++++++++--------- src/next/config.rs | 12 ++++++------ src/next/errors.rs | 2 ++ src/next/git.rs | 8 +++++--- src/next/process.rs | 16 +++++++-------- src/next/resolve.rs | 24 ++++++++++++----------- src/next/run.rs | 8 ++++---- src/next/state.rs | 31 +++++++++++++++++------------ src/process.rs | 10 ++++++---- src/state.rs | 24 +++++++++++------------ tests/run_tests.rs | 7 +++---- 17 files changed, 174 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38c34e8..0f852f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" + [[package]] name = "cc" version = "1.0.95" @@ -758,11 +764,13 @@ name = "tidploy" version = "0.14.0" dependencies = [ "base64", + "camino", "clap", "color-eyre", "directories", "duct", "keyring", + "once_cell", "relative-path", "rpassword", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3e31a24..6427aa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,4 +26,6 @@ tracing-error = "=0.2.0" directories = "=5.0.1" color-eyre = "=0.6.3" test-log = { version="=0.2.15", default-features = false, features = ["trace"] } -duct = "=0.13.7" \ No newline at end of file +duct = "=0.13.7" +camino = "1.1.6" +once_cell = "1.19.0" \ No newline at end of file diff --git a/src/archives.rs b/src/archives.rs index af0004c..73f91e2 100644 --- a/src/archives.rs +++ b/src/archives.rs @@ -2,16 +2,16 @@ use crate::errors::{RepoError, TarError}; use crate::filesystem::{FileError, FileErrorKind}; use crate::process::process_out; use std::fs; -use std::path::{Path, PathBuf}; +use camino::{Utf8Path, Utf8PathBuf}; use std::process::Command as Cmd; use tracing::debug; pub(crate) fn make_archive( - archives_path: &Path, - current_dir: &Path, + archives_path: &Utf8Path, + current_dir: &Utf8Path, source_name: &str, target_name: &str, -) -> Result { +) -> Result { if !archives_path.exists() { fs::create_dir_all(archives_path).map_err(|e| { RepoError::from_io( @@ -24,10 +24,7 @@ pub(crate) fn make_archive( let archive_name = format!("{}.tar.gz", target_name); let archive_path = archives_path.join(archive_name); - let archive_path_name = archive_path.to_str().ok_or(FileError { - msg: format!("Cannot represent path {:?} as string!", archive_path), - source: FileErrorKind::InvalidPath, - })?; + let archive_path_name = archive_path.as_str(); if archive_path.exists() { return Ok(archive_path); @@ -56,14 +53,11 @@ pub(crate) fn make_archive( } pub(crate) fn extract_archive( - archive_path: &Path, - current_dir: &Path, + archive_path: &Utf8Path, + current_dir: &Utf8Path, target_name: &str, -) -> Result { - let archive_path_name = archive_path.to_str().ok_or(FileError { - msg: format!("Cannot represent path {:?} as string!", archive_path), - source: FileErrorKind::InvalidPath, - })?; +) -> Result { + let archive_path_name = archive_path.as_str(); let target_path = current_dir.join(target_name); debug!("Extracting archive {:?} to {:?}", archive_path, target_path); diff --git a/src/commands.rs b/src/commands.rs index d764349..d457ce8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,9 +1,9 @@ -use std::path::{Path, PathBuf}; +use camino::{Utf8Path, Utf8PathBuf}; use std::process::ExitCode; use crate::archives::{extract_archive, make_archive}; use crate::errors::{ProcessError, RepoError}; -use crate::filesystem::get_dirs; +use crate::filesystem::{get_dirs, FileError, FileErrorKind}; use crate::git::{checkout, checkout_path, repo_clone, Repo}; use crate::next::commands::{match_command, NextSub}; use crate::next::run::run_command_input_old_state; @@ -111,12 +111,16 @@ enum ErrorRepr { Repo(#[from] RepoError), } -fn create_repo(repo: Repo) -> Result { - let cache_dir = get_dirs().cache.as_path(); +fn create_repo(repo: Repo) -> Result { + + let cache_dir = get_dirs().map_err(|e| RepoError::File(FileError { + msg: "Error getting dirs!".to_owned(), + source: e + }))?.cache.clone(); let repo_name = repo.dir_name(); let repo_path = cache_dir.join(&repo_name); - repo_clone(cache_dir, &repo_name, &repo.url)?; + repo_clone(&cache_dir, &repo_name, &repo.url)?; Ok(repo_path) } @@ -124,7 +128,7 @@ fn create_repo(repo: Repo) -> Result { fn switch_to_revision( cli_state: CliEnvState, state: State, - repo_path: &Path, + repo_path: &Utf8Path, ) -> Result { let commit_short = &state.commit_sha[0..7]; let deploy_path_str = format!("{:?}", state.deploy_path); @@ -153,8 +157,11 @@ fn switch_to_revision( Ok(state) } -fn prepare_from_state(state: &State, repo_path: &Path) -> Result<(), ErrorRepr> { - let cache_dir = get_dirs().cache.as_path(); +fn prepare_from_state(state: &State, repo_path: &Utf8Path) -> Result<(), ErrorRepr> { + let cache_dir = get_dirs().map_err(|e| ErrorRepr::Repo(RepoError::File(FileError { + msg: "Cache dir not UTF-8!".to_owned(), + source: e + })))?.cache.clone(); let archives = cache_dir.join("archives"); let deploy_encoded = B64USNP.encode(state.deploy_path.as_str()); let archive_name = format!( @@ -164,8 +171,8 @@ fn prepare_from_state(state: &State, repo_path: &Path) -> Result<(), ErrorRepr> make_archive( &archives, - cache_dir, - repo_path.file_name().unwrap().to_string_lossy().as_ref(), + &cache_dir, + repo_path.file_name().unwrap(), &archive_name, ) .map_err(ErrorRepr::Repo)?; @@ -212,7 +219,10 @@ fn prepare_command( let _prep_enter = prepare_san.enter(); let repo_path = if no_create { - let cache_dir = get_dirs().cache.as_path(); + let cache_dir = get_dirs().map_err(|e| ErrorRepr::Repo(RepoError::File(FileError { + msg: "Cache dir not UTF-8!".to_owned(), + source: e + })))?.cache.clone(); let repo_path = cache_dir.join(repo.dir_name()); if !repo_path.exists() { @@ -287,8 +297,12 @@ pub fn run_cli() -> Result { enter_dl.exit(); let state = prepare_command(cli_state.clone(), no_create, state.repo)?.unwrap(); - let cache_dir = get_dirs().cache.as_path(); - let tmp_dir = get_dirs().tmp.as_path(); + let dirs = get_dirs().map_err(|e| ErrorRepr::Repo(RepoError::File(FileError { + msg: "Cache dir not UTF-8!".to_owned(), + source: e + })))?; + let cache_dir = dirs.cache.as_path(); + let tmp_dir = dirs.tmp.as_path(); let deploy_encoded = B64USNP.encode(state.deploy_path.as_str()); let archive_name = format!( "{}_{}_{}", diff --git a/src/config.rs b/src/config.rs index ff94ec3..ee9c59c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use camino::Utf8Path; use relative_path::{RelativePath, RelativePathBuf}; use serde::Deserialize; use std::{ @@ -127,7 +128,7 @@ fn overwrite_config(root_config: DployConfig, overwrite_config: DployConfig) -> /// Looks at config at start_path and appends levels from final_path, looking at a config at every level. It then /// combines them. pub(crate) fn traverse_configs( - start_path: &Path, + start_path: &Utf8Path, final_path: &RelativePath, ) -> Result { debug!( diff --git a/src/filesystem.rs b/src/filesystem.rs index 3589ddc..8ce3e68 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,6 +1,9 @@ +use camino::{Utf8Path, Utf8PathBuf}; use directories::ProjectDirs; -use std::{env, io::Error as StdIOError, path::PathBuf, sync::OnceLock}; +use relative_path::{RelativePath, RelativePathBuf}; +use std::{env, io::Error as StdIOError}; use thiserror::Error as ThisError; +use once_cell::sync::OnceCell; #[derive(ThisError, Debug)] #[error("{msg} {source}")] @@ -17,23 +20,50 @@ pub(crate) enum FileErrorKind { InvalidPath, } -pub(crate) fn get_current_dir() -> Result { - env::current_dir().map_err(FileErrorKind::IO) +pub(crate) fn get_current_dir() -> Result { + let current_dir = env::current_dir().map_err(FileErrorKind::IO)?; + Utf8PathBuf::from_path_buf(current_dir).map_err(|_e| FileErrorKind::InvalidPath) } pub(crate) struct Dirs { - pub(crate) cache: PathBuf, - pub(crate) tmp: PathBuf, + pub(crate) cache: Utf8PathBuf, + pub(crate) tmp: Utf8PathBuf, } -pub(crate) fn get_dirs() -> &'static Dirs { - static DIRS: OnceLock = OnceLock::new(); - DIRS.get_or_init(|| { +pub(crate) fn get_dirs() -> Result<&'static Dirs, FileErrorKind> { + static DIRS: OnceCell = OnceCell::new(); + DIRS.get_or_try_init(|| { let project_dirs = ProjectDirs::from("", "", "tidploy").unwrap(); let cache = project_dirs.cache_dir().to_owned(); let tmp = env::temp_dir(); + let cache = Utf8PathBuf::from_path_buf(cache).map_err(|_e| FileErrorKind::InvalidPath)?; + let tmp = Utf8PathBuf::from_path_buf(tmp).map_err(|_e| FileErrorKind::InvalidPath)?; - Dirs { cache, tmp } + Ok(Dirs { cache, tmp }) }) } + +pub trait WrapToPath { + fn to_utf8_path>(&self, path: P) -> Utf8PathBuf; +} + +impl WrapToPath for RelativePath +{ + fn to_utf8_path>(&self, path: P) -> Utf8PathBuf { + let path = path.as_ref().as_std_path(); + let std_path = self.to_path(path); + // Since we started with Utf8Path, we know this will work + Utf8PathBuf::from_path_buf(std_path).unwrap() + } +} + +impl WrapToPath for RelativePathBuf +{ + fn to_utf8_path>(&self, path: P) -> Utf8PathBuf { + let path = path.as_ref().as_std_path(); + let std_path = self.to_path(path); + // Since we started with Utf8Path, we know this will work + Utf8PathBuf::from_path_buf(std_path).unwrap() + } +} \ No newline at end of file diff --git a/src/git.rs b/src/git.rs index 6421e3f..89bb701 100644 --- a/src/git.rs +++ b/src/git.rs @@ -2,12 +2,12 @@ use crate::errors::{GitError, RepoError, RepoParseError}; use base64::engine::general_purpose::URL_SAFE_NO_PAD as B64USNP; use base64::Engine; +use camino::Utf8Path; use relative_path::RelativePath; use spinoff::{spinners, Spinner}; use std::ffi::OsStr; use std::fs; -use std::path::Path; use std::process::{Command as Cmd, Stdio}; use tracing::debug; @@ -70,7 +70,7 @@ pub(crate) fn parse_repo_url(url: String) -> Result { } fn run_git>( - current_dir: &Path, + current_dir: &Utf8Path, args: Vec, op_name: &'static str, ) -> Result { @@ -100,7 +100,7 @@ fn run_git>( .to_owned()) } -pub(crate) fn git_root_origin_url(path: &Path) -> Result { +pub(crate) fn git_root_origin_url(path: &Utf8Path) -> Result { let args = vec!["config", "--get", "remote.origin.url"]; let url = run_git(path, args, "get git root origin url")?; @@ -110,20 +110,20 @@ pub(crate) fn git_root_origin_url(path: &Path) -> Result { Ok(url) } -pub(crate) fn git_root_dir(path: &Path) -> Result { +pub(crate) fn git_root_dir(path: &Utf8Path) -> Result { let args = vec!["rev-parse", "--show-toplevel"]; run_git(path, args, "get git root dir") } -pub(crate) fn rev_parse_tag(tag: &str, path: &Path) -> Result { +pub(crate) fn rev_parse_tag(tag: &str, path: &Utf8Path) -> Result { let args = vec!["rev-parse", tag]; run_git(path, args, "rev parse tag") } pub(crate) fn repo_clone( - current_dir: &Path, + current_dir: &Utf8Path, target_name: &str, repo_url: &str, ) -> Result<(), RepoError> { @@ -188,7 +188,7 @@ pub(crate) fn repo_clone( Ok(()) } -pub(crate) fn checkout(repo_path: &Path, commit_sha: &str) -> Result<(), RepoError> { +pub(crate) fn checkout(repo_path: &Utf8Path, commit_sha: &str) -> Result<(), RepoError> { if !repo_path.exists() { return Err(RepoError::NotCreated); } @@ -227,11 +227,11 @@ pub(crate) fn checkout(repo_path: &Path, commit_sha: &str) -> Result<(), RepoErr Ok(()) } -pub(crate) fn checkout_path(repo_path: &Path, deploy_path: &RelativePath) -> Result<(), RepoError> { +pub(crate) fn checkout_path(repo_path: &Utf8Path, deploy_path: &RelativePath) -> Result<(), RepoError> { checkout_paths(repo_path, vec![deploy_path]) } -pub(crate) fn checkout_paths(repo_path: &Path, paths: Vec<&RelativePath>) -> Result<(), RepoError> { +pub(crate) fn checkout_paths(repo_path: &Utf8Path, paths: Vec<&RelativePath>) -> Result<(), RepoError> { if !repo_path.exists() { return Err(RepoError::NotCreated); } diff --git a/src/next/config.rs b/src/next/config.rs index fcd043b..0f57287 100644 --- a/src/next/config.rs +++ b/src/next/config.rs @@ -2,14 +2,14 @@ use std::{ collections::HashMap, fs, ops::ControlFlow, - path::{Path, PathBuf}, }; +use camino::{Utf8Path, Utf8PathBuf}; use relative_path::{RelativePath, RelativePathBuf}; use serde::Deserialize; use tracing::debug; -use crate::{next::errors::WrapConfigErr, state::State}; +use crate::{filesystem::WrapToPath, next::errors::WrapConfigErr, state::State}; use super::errors::ConfigError; @@ -55,7 +55,7 @@ pub(crate) struct Config { pub(crate) state: Option } -pub(crate) fn load_dploy_config(config_dir_path: &Path) -> Result { +pub(crate) fn load_dploy_config(config_dir_path: &Utf8Path) -> Result { let toml_path = config_dir_path.join("tidploy.toml"); let json_path = config_dir_path.join("tidploy.json"); let choose_json = json_path.exists(); @@ -177,7 +177,7 @@ fn overwrite_config(root_config: Config, overwrite_config: Config) -> Config { } pub(crate) fn traverse_configs( - start_path: &Path, + start_path: &Utf8Path, final_path: &RelativePath, ) -> Result { debug!( @@ -187,11 +187,11 @@ pub(crate) fn traverse_configs( let root_config = load_dploy_config(start_path)?; - let paths: Vec = final_path + let paths: Vec = final_path .components() .scan(RelativePathBuf::new(), |state, component| { state.push(component); - Some(state.to_path(start_path)) + Some(state.to_utf8_path(start_path)) }) .collect(); diff --git a/src/next/errors.rs b/src/next/errors.rs index 843600a..b8f8f40 100644 --- a/src/next/errors.rs +++ b/src/next/errors.rs @@ -32,6 +32,8 @@ pub(crate) struct StateError { #[derive(ThisError, Debug)] pub(crate) enum StateErrorKind { + #[error("Path is not valid UTF-8!")] + InvalidPath, #[error("State manipulation failed due to IO error! {0}")] IO(#[from] IOError), #[error("{0}")] diff --git a/src/next/git.rs b/src/next/git.rs index c7dd0db..d27372f 100644 --- a/src/next/git.rs +++ b/src/next/git.rs @@ -1,12 +1,14 @@ +use camino::Utf8Path; + use super::{ errors::{GitError, GitProcessError}, process::process_complete_output, }; use core::fmt::Debug; -use std::{ffi::OsStr, path::Path}; +use std::ffi::OsStr; fn run_git + Debug>( - working_dir: &Path, + working_dir: &Utf8Path, args: Vec, op_name: &'static str, ) -> Result { @@ -28,7 +30,7 @@ fn run_git + Debug>( } } -pub(crate) fn git_root_dir(path: &Path) -> Result { +pub(crate) fn git_root_dir(path: &Utf8Path) -> Result { let args = vec!["rev-parse", "--show-toplevel"]; run_git(path, args, "get git root dir") diff --git a/src/next/process.rs b/src/next/process.rs index 5e1a5ed..93d3659 100644 --- a/src/next/process.rs +++ b/src/next/process.rs @@ -1,12 +1,12 @@ +use camino::{Utf8Path, Utf8PathBuf}; use color_eyre::eyre::{Context, Report}; use duct::{cmd, IntoExecutablePath}; use std::ffi::OsStr; use std::fmt::Debug; use std::io::{stdout, Read, Write}; -use std::path::PathBuf; use std::process::ExitStatus; use std::str; -use std::{collections::HashMap, io::BufReader, path::Path}; +use std::{collections::HashMap, io::BufReader}; use tracing::{span, Level}; use super::errors::{ProcessError, ProcessIOError}; @@ -30,12 +30,12 @@ pub(crate) fn process_complete_output( ) -> Result where // This is pretty bad... - P: Into + Debug + Clone, + P: Into + Debug + Clone, E: IntoExecutablePath + Debug + Clone, S: AsRef + Debug, { let output = cmd(program.clone(), &args) - .dir(working_dir.clone()) + .dir(working_dir.clone().into()) .stderr_to_stdout() .stdout_capture() .unchecked() @@ -60,8 +60,8 @@ where /// the envs of the tidploy process. `input_bytes` is useful mostly for testing, if set to None then the /// child process will just inherit the stdin of the tidploy process. pub(crate) fn run_entrypoint( - working_dir: &Path, - entrypoint: &Path, + working_dir: &Utf8Path, + entrypoint: &Utf8Path, envs: HashMap, input_bytes: Option>, ) -> Result { @@ -73,7 +73,7 @@ pub(crate) fn run_entrypoint( let mut combined_envs: HashMap<_, _> = std::env::vars().collect(); combined_envs.extend(envs); - let cmd_expr = cmd(entrypoint, Vec::::new()) + let cmd_expr = cmd(entrypoint.as_std_path(), Vec::::new()) .dir(working_dir) .full_env(&combined_envs) .stderr_to_stdout() @@ -91,7 +91,7 @@ pub(crate) fn run_entrypoint( let entry_span = span!( Level::DEBUG, "entrypoint", - path = entrypoint.to_string_lossy().as_ref() + path = entrypoint.as_str() ); let _enter = entry_span.enter(); diff --git a/src/next/resolve.rs b/src/next/resolve.rs index 531d975..d4edbe4 100644 --- a/src/next/resolve.rs +++ b/src/next/resolve.rs @@ -1,11 +1,13 @@ use std::{ env, - path::{Path, PathBuf}, }; +use camino::{Utf8Path, Utf8PathBuf}; use relative_path::{RelativePath, RelativePathBuf}; use tracing::instrument; +use crate::filesystem::WrapToPath; + use super::{ config::{merge_vars, traverse_configs, ArgumentConfig, ConfigScope, ConfigVar}, errors::ResolutionError, state::ResolveState, @@ -86,8 +88,8 @@ pub(crate) struct SecretScope { } pub(crate) struct RunResolved { - pub(crate) executable: PathBuf, - pub(crate) execution_path: PathBuf, + pub(crate) executable: Utf8PathBuf, + pub(crate) execution_path: Utf8PathBuf, pub(crate) envs: Vec, pub(crate) scope: SecretScope, } @@ -144,11 +146,11 @@ pub(crate) trait Resolve: Sized { fn merge_env_config( self, - state_root: &Path, + state_root: &Utf8Path, state_path: &RelativePath, ) -> Result; - fn resolve(self, resolve_root: &Path, name: &str, sub: &str, hash: &str) -> Resolved; + fn resolve(self, resolve_root: &Utf8Path, name: &str, sub: &str, hash: &str) -> Resolved; } fn resolve_scope( @@ -168,7 +170,7 @@ fn resolve_scope( impl Resolve for RunArguments { fn merge_env_config( self, - state_root: &Path, + state_root: &Utf8Path, state_path: &RelativePath, ) -> Result { let config = traverse_configs(state_root, state_path)?; @@ -185,14 +187,14 @@ impl Resolve for RunArguments { Ok(config_run.merge(merged_args)) } - fn resolve(self, resolve_root: &Path, name: &str, sub: &str, hash: &str) -> RunResolved { + 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("".to_owned())); let relative_exn_path = RelativePathBuf::from(self.execution_path.unwrap_or("".to_owned())); RunResolved { - executable: relative_exe.to_path(resolve_root), - execution_path: relative_exn_path.to_path(resolve_root), + executable: relative_exe.to_utf8_path(resolve_root), + execution_path: relative_exn_path.to_utf8_path(resolve_root), envs: self.envs, scope, } @@ -202,7 +204,7 @@ impl Resolve for RunArguments { impl Resolve for SecretArguments { fn merge_env_config( self, - state_root: &Path, + state_root: &Utf8Path, state_path: &RelativePath, ) -> Result { let config = traverse_configs(state_root, state_path)?; @@ -225,7 +227,7 @@ impl Resolve for SecretArguments { Ok(merged_args) } - fn resolve(self, _resolve_root: &Path, name: &str, sub: &str, hash: &str) -> SecretResolved { + fn resolve(self, _resolve_root: &Utf8Path, name: &str, sub: &str, hash: &str) -> SecretResolved { let scope = resolve_scope(self.scope_args, name, sub, hash); SecretResolved { diff --git a/src/next/run.rs b/src/next/run.rs index 72052bb..0484a6b 100644 --- a/src/next/run.rs +++ b/src/next/run.rs @@ -4,7 +4,7 @@ use tracing::{debug, instrument}; use crate::{ archives::extract_archive, - filesystem::get_dirs, + filesystem::{get_dirs, WrapToPath}, next::{ resolve::{merge_and_resolve, RunArguments, SecretScopeArguments}, secrets::secret_vars_to_envs, @@ -39,13 +39,13 @@ pub(crate) fn run_command_input_old_state( ) -> Result { // Only loads archive if it is given, otherwise path is None let state = if let Some(archive) = archive { - let cache_dir = get_dirs().cache.as_path(); + let cache_dir = get_dirs().wrap_err("Cache is not UTF-8!")?.cache.as_path(); let archive_path = cache_dir .join("archives") .join(&archive) .with_extension("tar.gz"); - let tmp_dir = get_dirs().tmp.as_path(); + let tmp_dir = get_dirs().wrap_err("Cache is not UTF-8!")?.tmp.as_path(); let extracted_path = extract_archive(&archive_path, tmp_dir, &archive).wrap_err("Repo error.")?; debug!("Extracted and loaded archive at {:?}", &extracted_path); @@ -76,7 +76,7 @@ pub(crate) fn run_command_input_old_state( // let state = extra_envs(state); let relative_path = RelativePathBuf::from(&state.exe_name); - let exe_path = relative_path.to_path(state.deploy_dir()); + let exe_path = relative_path.to_utf8_path(state.deploy_dir()); run_entrypoint(&state.deploy_dir(), &exe_path, state.envs, input_bytes) } diff --git a/src/next/state.rs b/src/next/state.rs index fca260a..b2004d0 100644 --- a/src/next/state.rs +++ b/src/next/state.rs @@ -1,8 +1,11 @@ -use std::{env::current_dir, path::PathBuf}; +use std::{env::current_dir}; +use camino::{Utf8Path, Utf8PathBuf}; use relative_path::RelativePathBuf; use tracing::{debug, instrument}; +use crate::filesystem::WrapToPath; + use super::{ config::{traverse_configs, ConfigAddress, ConfigVar, StateConfig}, errors::{AddressError, StateError, StateErrorKind, WrapStateErr}, git::git_root_dir }; @@ -44,7 +47,7 @@ impl StateIn { #[derive(Debug)] pub(crate) struct StatePaths { - pub(crate) context_root: PathBuf, + pub(crate) context_root: Utf8PathBuf, pub(crate) state_root: RelativePathBuf, pub(crate) state_path: RelativePathBuf, } @@ -55,9 +58,13 @@ impl StatePaths { fn new(state_in: StateIn) -> Result { let current_dir = current_dir().to_state_err("Getting current dir for new StatePaths".to_owned())?; + let current_dir = Utf8PathBuf::from_path_buf(current_dir).map_err(|_e| StateError { + msg: "Current directory is not valid UTF-8!".to_owned(), + source: StateErrorKind::InvalidPath.into() + })?; let context_root = match state_in.context { InferContext::Cwd => current_dir, - InferContext::Git => PathBuf::from( + InferContext::Git => Utf8PathBuf::from( git_root_dir(¤t_dir) .to_state_err("Getting Git root dir for new StatePaths".to_owned())?, ), @@ -88,7 +95,7 @@ pub(crate) fn parse_cli_vars(envs: Vec) -> Vec { #[derive(Debug, Clone, PartialEq)] pub(crate) enum Address { - Local(PathBuf), + Local(Utf8PathBuf), Git(GitAddress) } @@ -99,7 +106,7 @@ impl From for Address { url, git_ref }), - ConfigAddress::Local { path } => Self::Local(PathBuf::from(path)) + ConfigAddress::Local { path } => Self::Local(Utf8PathBuf::from(path)) } } } @@ -114,7 +121,7 @@ pub(crate) struct GitAddress { pub(crate) struct State { pub(crate) state_root: RelativePathBuf, pub(crate) state_path: RelativePathBuf, - pub(crate) context_root: PathBuf, + pub(crate) context_root: Utf8PathBuf, pub(crate) address: Option
, } @@ -151,9 +158,9 @@ impl State { #[derive(Debug)] pub(crate) struct ResolveState { - pub(crate) state_root: PathBuf, + pub(crate) state_root: Utf8PathBuf, pub(crate) state_path: RelativePathBuf, - pub(crate) resolve_root: PathBuf, + pub(crate) resolve_root: Utf8PathBuf, pub(crate) name: String, pub(crate) sub: String, pub(crate) hash: String, @@ -164,7 +171,7 @@ 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_path(&state.context_root); + let state_root_path = state.state_root.to_utf8_path(&state.context_root); let config = traverse_configs(&state_root_path, &state.state_path).to_state_err("Failed to read configs for determining new state.".to_owned())?; let new_state = config.state.map(|c| (&state).merge_config(c)).unwrap_or(state.clone()); if new_state == state { @@ -231,16 +238,16 @@ pub(crate) fn create_resolve_state(state_in: StateIn) -> Result, err_msg: String) -> Result { @@ -18,7 +19,7 @@ pub(crate) fn process_out(bytes: Vec, err_msg: String) -> Result>(e: impl Into, info: &str, p: P) -> ProcessError { +fn err_ctx>(e: impl Into, info: &str, p: P) -> ProcessError { let msg = format!( "IO error {} (running entrypoint at path: {:?})", info, @@ -30,13 +31,14 @@ fn err_ctx>(e: impl Into, info: &str, p: P) -> } } -pub(crate) fn run_entrypoint>( +pub(crate) fn run_entrypoint>( entrypoint_dir: P, entrypoint: &str, envs: HashMap, ) -> Result<(), ProcessError> { println!("Running {}!", &entrypoint); - let program_path = entrypoint_dir.as_ref().join(entrypoint); + let entrypoint_dir = entrypoint_dir.as_ref(); + let program_path = entrypoint_dir.join(entrypoint); let mut entrypoint_output = Cmd::new(&program_path) .current_dir(&entrypoint_dir) .envs(&envs) diff --git a/src/state.rs b/src/state.rs index de745e4..08baea9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,17 +1,16 @@ use crate::commands::{DEFAULT_GIT_LOCAL, DEFAULT_GIT_REMOTE, TIDPLOY_DEFAULT}; use crate::config::{merge_vars, traverse_configs, ConfigError, ConfigVar, DployConfig}; use crate::errors::{GitError, RelPathError, RepoParseError}; -use crate::filesystem::{get_current_dir, FileError}; +use crate::filesystem::{get_current_dir, FileError, WrapToPath}; use crate::git::{git_root_dir, git_root_origin_url, parse_repo_url, rev_parse_tag, Repo}; use crate::secret::{get_secret, AuthError}; +use camino::{Utf8Path, Utf8PathBuf}; use clap::ValueEnum; use relative_path::{RelativePath, RelativePathBuf}; use std::env::VarError; - -use std::path::{Path, PathBuf}; use std::{collections::HashMap, env}; use thiserror::Error as ThisError; @@ -55,12 +54,12 @@ pub(crate) struct State { pub(crate) commit_sha: String, pub(crate) envs: HashMap, pub(crate) exe_name: String, - pub(crate) root_dir: PathBuf, + pub(crate) root_dir: Utf8PathBuf, } impl State { - pub(crate) fn deploy_dir(&self) -> PathBuf { - let dir = self.deploy_path.to_path(&self.root_dir); + pub(crate) fn deploy_dir(&self) -> Utf8PathBuf { + let dir = self.deploy_path.to_utf8_path(&self.root_dir); debug!("Computed deploy_dir as {:?}", dir); dir } @@ -223,8 +222,7 @@ fn set_state( ReadRepoMethod::Value(value) => value, ReadRepoMethod::Default => TIDPLOY_DEFAULT.to_owned(), ReadRepoMethod::GitRootRemote => git_root_origin_url(&state.root_dir)?, - // We assume paths will be UTF-8, as our root dir is almost certainly set from a UTF-8 string - ReadRepoMethod::GitRoot => state.root_dir.to_str().unwrap().to_owned(), + ReadRepoMethod::GitRoot => state.root_dir.as_str().to_owned(), }; match repo_url.as_str() { @@ -302,7 +300,7 @@ fn set_state( pub(crate) fn create_state_create( cli_state: CliEnvState, - project_path: Option<&Path>, + project_path: Option<&Utf8Path>, deploy_path: Option<&RelativePath>, load_tag: bool, ) -> Result { @@ -323,7 +321,7 @@ pub(crate) fn create_state_run( cli_state: CliEnvState, exe_name: Option, envs: Vec, - path: Option<&Path>, + path: Option<&Utf8Path>, deploy_path: Option<&RelativePath>, load_tag: bool, ) -> Result { @@ -345,7 +343,7 @@ pub(crate) fn create_state_run( pub(crate) fn create_state( cli_state: CliEnvState, cli_run_state: Option, - project_path: Option<&Path>, + project_path: Option<&Utf8Path>, deploy_path: Option<&RelativePath>, load_tag: bool, ) -> Result { @@ -379,7 +377,7 @@ pub(crate) fn create_state( commit_sha: TIDPLOY_DEFAULT.to_owned(), envs: HashMap::::new(), exe_name: TIDPLOY_DEFAULT.to_owned(), - root_dir: PathBuf::new(), // always replaced + root_dir: Utf8PathBuf::new(), // always replaced }; debug!("Starting state is {:?}", state); @@ -415,7 +413,7 @@ pub(crate) fn create_state( state.root_dir = match state.context { StateContext::None => current_dir, StateContext::GitLocal | StateContext::GitRemote => { - Path::new(&git_root_dir(¤t_dir)?).to_owned() + Utf8Path::new(&git_root_dir(¤t_dir)?).to_owned() } }; diff --git a/tests/run_tests.rs b/tests/run_tests.rs index c85ee6b..ab400c9 100644 --- a/tests/run_tests.rs +++ b/tests/run_tests.rs @@ -1,5 +1,4 @@ -use std::path::PathBuf; - +use camino::Utf8PathBuf; use keyring::Entry; use test_log::test; @@ -120,8 +119,8 @@ fn test_secret_get() -> Result<(), CommandError> { let pass = "abc".to_owned(); let key = "key".to_owned(); let context_root = env!("CARGO_MANIFEST_DIR"); - let context_path = PathBuf::from(context_root); - let context = context_path.file_name().unwrap().to_str().unwrap(); + let context_path = Utf8PathBuf::from(context_root); + let context = context_path.file_name().unwrap(); let entry_key = format!("{}::tidploy_root::tidploy_default_hash:{}", context, key); let _entry = TestEntry::new("tidploy_test_service_get", &entry_key, &pass);