Skip to content

Commit

Permalink
converge attempt
Browse files Browse the repository at this point in the history
  • Loading branch information
tiptenbrink committed Jan 12, 2024
1 parent 0d086f3 commit f42e972
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 97 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"files.autoSave": "afterDelay",
"files.autoSaveDelay": 500
}
100 changes: 54 additions & 46 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ struct Cli {
#[arg(long, value_enum, global = true)]
context: Option<StateContext>,

/// UNFINISHED.
#[arg(long, global = true)]
network: Option<bool>,
local: Option<bool>,

/// Set the repository URL, defaults to 'default_infer', in which case it is inferred from the current repository. Set to 'default' to not set it.
/// Falls back to environment variable using TIDPLOY_REPO and then to config with key 'repo_url'
Expand Down Expand Up @@ -66,6 +65,10 @@ enum Commands {
#[arg(short = 'x', long = "exe")]
executable: Option<String>,

/// Don't clone a fresh repository. Will fail if it does not exist. WARNING: The repository might not be up-to-date.
#[arg(long)]
no_create: bool,

/// Variables to load. Supply as many pairs of <key> <env var name> as needed.
#[arg(short, num_args = 2)]
variables: Vec<String>,
Expand Down Expand Up @@ -99,39 +102,9 @@ enum ErrorRepr {
Repo(#[from] RepoError),
}

enum CreateRepo {
Clone(Repo),
Path(RelativePathBuf),
}

fn create_repo(network: bool, create_repo: CreateRepo) -> Result<PathBuf, RepoError> {
let repo = match create_repo {
CreateRepo::Clone(repo) => {
if !network {
return Err(RepoError::NeedsNetwork);
}

repo
}
CreateRepo::Path(source_path) => {
let source_path = source_path.normalize();
let url: String = source_path.clone().into();
let name = source_path
.file_name()
.ok_or(RepoParseError::InvalidPath(source_path.clone().into()))?
.to_owned();
let encoded_url = B64USNP.encode(&url);

Repo {
name,
url,
encoded_url,
}
}
};

fn create_repo(repo: Repo) -> Result<PathBuf, RepoError> {
let tmp_dir = Path::new(TMP_DIR);
let repo_name = format!("{}_{}", repo.name, repo.encoded_url);
let repo_name = repo.dir_name();
let repo_path = tmp_dir.join(&repo_name);

repo_clone(tmp_dir, &repo_name, &repo.url)?;
Expand Down Expand Up @@ -189,15 +162,14 @@ fn prepare_from_state(state: &State, repo_path: &Path) -> Result<(), ErrorRepr>

fn download_command(
cli_state: CliEnvState,
network: bool,
repo: Repo,
repo_only: bool,
) -> Result<Option<State>, ErrorRepr> {
// This will be exited when `download_command` returns
let download_span = span!(Level::DEBUG, "download");
let _dl_enter = download_span.enter();

let repo_path = create_repo(network, CreateRepo::Clone(repo)).map_err(ErrorRepr::Repo)?;
let repo_path = create_repo(repo).map_err(ErrorRepr::Repo)?;

if repo_only {
return Ok(None);
Expand All @@ -217,6 +189,42 @@ fn download_command(
Ok(Some(state))
}

fn prepare_command(
cli_state: CliEnvState,
no_create: bool,
repo: Repo,
) -> Result<Option<State>, ErrorRepr> {
// This will be exited when `download_command` returns
let prepare_san = span!(Level::DEBUG, "prepare");
let _prep_enter = prepare_san.enter();

let repo_path = if no_create {
let tmp_dir = Path::new(TMP_DIR);
let repo_path = tmp_dir.join(repo.dir_name());

if !repo_path.exists() {
return Err(RepoError::NotCreated.into())
}

repo_path
} else {
create_repo(repo).map_err(ErrorRepr::Repo)?
};

// The preswitch stage creates state from the recently created repo, determining which commit sha to use for
// the checkout and which deploy path to use
let head_span = span!(Level::DEBUG, "preswitch").entered();
let state =
create_state_create(cli_state.clone(), Some(&repo_path), true).map_err(ErrorRepr::Load)?;
head_span.exit();

let state = switch_to_revision(cli_state, state, &repo_path)?;

prepare_from_state(&state, &repo_path)?;

Ok(Some(state))
}

/// Adds a number of useful environment variables, such as the commit sha (both full and the first 7 characters) as well as the tag.
fn extra_envs(mut state: State) -> State {
let commit_long = state.commit_sha.clone();
Expand Down Expand Up @@ -245,7 +253,6 @@ pub(crate) fn run_cli() -> Result<(), Error> {

let cli_state = CliEnvState {
context: args.context,
network: args.network,
repo_url: args.repo,
deploy_path: args.deploy_pth,
tag: args.tag,
Expand All @@ -266,26 +273,26 @@ pub(crate) fn run_cli() -> Result<(), Error> {
Commands::Download { repo_only } => {
let state =
create_state_create(cli_state.clone(), None, false).map_err(ErrorRepr::Load)?;
download_command(cli_state, state.network, state.repo, repo_only)?;
download_command(cli_state, state.repo, repo_only)?;

Ok(())
}
Commands::Deploy {
executable,
no_create,
variables,
} => {
// We drop the dpl_enter when exiting this scope
let deploy_span = span!(Level::DEBUG, "deploy");
let _dpl_enter = deploy_span.enter();

// This one we must manually exit so we use 'entered'. The predownload stage determines the "network" and "repo".
let enter_dl = span!(Level::DEBUG, "predownload").entered();
// This one we must manually exit so we use 'entered'. The preprepare stage determines the "repo".
let enter_dl = span!(Level::DEBUG, "preprepare").entered();
let state =
create_state_create(cli_state.clone(), None, false).map_err(ErrorRepr::Load)?;
enter_dl.exit();

let state =
download_command(cli_state.clone(), state.network, state.repo, false)?.unwrap();
let state = prepare_command(cli_state.clone(), no_create, state.repo)?.unwrap();

let tmp_dir = Path::new(TMP_DIR);
let deploy_encoded = B64USNP.encode(state.deploy_path.as_str());
Expand All @@ -302,7 +309,7 @@ pub(crate) fn run_cli() -> Result<(), Error> {
let target_path_root = tmp_dir.join(archive_name);
let target_path = state.deploy_path.to_path(target_path_root);
let state =
create_state_run(cli_state, executable, variables, Some(&target_path), true)
create_state_run(cli_state, executable, variables, Some(&target_path), true, false)
.map_err(ErrorRepr::Load)?;

let state = extra_envs(state);
Expand Down Expand Up @@ -339,12 +346,13 @@ pub(crate) fn run_cli() -> Result<(), Error> {
};
let path_ref = path.as_deref();

let state = create_state_run(cli_state, executable, variables, path_ref, true)
let state = create_state_run(cli_state, executable, variables, path_ref, true, true)
.map_err(ErrorRepr::Load)?;

let state = extra_envs(state);

run_entrypoint(state.current_dir, &state.exe_name, state.envs)

let entrypoint_dir = state.deploy_path.to_path(state.current_dir);
run_entrypoint(entrypoint_dir, &state.exe_name, state.envs)
.map_err(ErrorRepr::Exe)?;

Ok(())
Expand Down
94 changes: 69 additions & 25 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,43 +65,58 @@ pub(crate) fn relative_to_git_root() -> Result<String, GitError> {
.to_owned())
}

#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub(crate) struct Repo {
pub(crate) name: String,
pub(crate) encoded_url: String,
pub(crate) url: String,
}

impl Repo {
pub(crate) fn dir_name(&self) -> String {
return format!("{}_{}", self.name, self.encoded_url)
}
}

/// 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.
pub(crate) fn parse_repo_url(url: String) -> Result<Repo, RepoParseError> {
let url = url.strip_suffix("/").unwrap_or(&url).to_owned();
// We want the final part, after the slash, as the "file name"
let split_parts: Vec<&str> = url.split('/').collect();

if split_parts.len() <= 1 {
return Err(RepoParseError::InvalidURL(url));
}

// If last does not exist then the string is empty so invalid
let last_part = *split_parts
.last()
.ok_or(RepoParseError::InvalidURL(url.clone()))?;
.ok_or(RepoParseError::InvalidURL(url.to_owned()))?;

let first_parts = split_parts
.get(0..split_parts.len() - 1)
.map(|a| a.to_vec().join("/"));

let encoded_url = if let Some(pre_part) = first_parts {
// The first part will contain slashes and potentially other characters we don't want in a file name, so we
// encode it
let encoded_url = if split_parts.len() <= 1 {
// In this case the part before the slash is empty so no encoding necessary
"".to_owned()
} else {
// We get everything except the last part and then rejoin them using the slash we originally split them with
let pre_part = split_parts
.get(0..split_parts.len() - 1).unwrap()
.join("/");
debug!("Encoding parsed url pre_part: {}", pre_part);
// base64urlsafe-encode
B64USNP.encode(pre_part)
} else {
return Err(RepoParseError::InvalidURL(url));
};

// In case there is a file extension (such as `.git`), we don't want that part of the name
let split_parts_dot: Vec<&str> = last_part.split('.').collect();
if split_parts_dot.len() <= 1 {
return Err(RepoParseError::InvalidURL(url));
}

let name = (*split_parts_dot
.first()
.ok_or(RepoParseError::InvalidURL(url.clone()))?)
.to_owned();
let name = if split_parts_dot.len() <= 1 {
// In this case no "." exists and we return just the entire "file name"
last_part.to_owned()
} else {
// We get only the part that comes before the first .
(*split_parts_dot
.first()
.ok_or(RepoParseError::InvalidURL(url.clone()))?)
.to_owned()
};

Ok(Repo {
name,
Expand Down Expand Up @@ -233,8 +248,9 @@ pub(crate) fn checkout(repo_path: &Path, commit_sha: &str) -> Result<(), RepoErr
.stdout(Stdio::piped())
.output()
.map_err(|e| GitError::from_io(e, format!("IO failure for git clean {:?}!", repo_path)))?;

sp.success("Commit checked out!");

let success_msg = format!("Checked out {}!", commit_sha);
sp.success(&success_msg);

Ok(())
}
Expand All @@ -247,7 +263,7 @@ pub(crate) fn checkout_path(repo_path: &Path, deploy_path: &RelativePath) -> Res
let mut sp = Spinner::new(
spinners::Line,
format!(
"Sparse-checkout repository to deploy path {:?}...",
"Sparse-checkout repository to deploy path {}...",
deploy_path
),
None,
Expand All @@ -267,7 +283,35 @@ pub(crate) fn checkout_path(repo_path: &Path, deploy_path: &RelativePath) -> Res
)
})?;

sp.success("Sparse checked out repository to deploy path!");
let success_msg = format!("Sparse checked out repository to deploy path {}!", deploy_path);
sp.success(&success_msg);

Ok(())
}

#[cfg(test)]
mod tests {
use super::parse_repo_url;

#[test]
fn parse_test_git() {
let git_url = "https://github.com/tiptenbrink/tidploy.git".to_owned();
let encoded_url = "aHR0cHM6Ly9naXRodWIuY29tL3RpcHRlbmJyaW5r".to_owned();
let name = "tidploy".to_owned();
assert_eq!(parse_repo_url(git_url.clone()).unwrap().encoded_url, encoded_url);
assert_eq!(parse_repo_url(git_url.clone()).unwrap().name, name);
assert_eq!(parse_repo_url(git_url.clone()).unwrap().url, git_url);
}

#[test]
fn parse_test_local() {
let path = "/home/tiptenbrink/tidploy/".to_owned();
let path_no_slash = "/home/tiptenbrink/tidploy".to_owned();
let encoded_url = "L2hvbWUvdGlwdGVuYnJpbms".to_owned();
let name = "tidploy".to_owned();
assert_eq!(parse_repo_url(path.clone()).unwrap().encoded_url, encoded_url);
assert_eq!(parse_repo_url(path.clone()).unwrap().name, name);
assert_eq!(parse_repo_url(path).unwrap().url, path_no_slash);
assert_eq!(parse_repo_url(path_no_slash.clone()).unwrap().url, path_no_slash);
}
}
Loading

0 comments on commit f42e972

Please sign in to comment.