Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow git remote repositories (https) as project locations #152

Merged
merged 4 commits into from
Jun 5, 2023
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
57 changes: 57 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ wws-project = { path = "./crates/project" }
wasmtime = "6.0.2"
wasmtime-wasi = "6.0.2"
wasi-common = "6.0.2"
path-slash = "0.2.1"
4 changes: 4 additions & 0 deletions crates/project/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ wws-store = { workspace = true }
url = "2.3.1"
sha256 = "1.1.1"
reqwest = "0.11"
git2 = "0.17.2"

[dev-dependencies]
path-slash = { workspace = true }
124 changes: 121 additions & 3 deletions crates/project/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,78 @@
// Copyright 2022 VMware, Inc.
// Copyright 2022-2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

mod fetch;
pub mod metadata;
pub mod options;
pub mod types;

use anyhow::Result;
use anyhow::{bail, Result};
use fetch::fetch_and_validate;
use metadata::{RemoteFile, Runtime};
use std::path::Path;
use options::Options;
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use types::git::prepare_git_project;
use wws_store::Store;

pub enum ProjectType {
Local,
Git,
}

/// Prepare a project from the given String. This argument could represent
/// different things:
///
/// - A local path
/// - A git repository
/// - Etc.
///
/// Depending on the type, the project preparation requires different steps.
/// For example, a git repository requires to clone it.
///
/// However, the result of any type is the same: a local folder to point to.
/// This is the value we return from this function.
pub async fn prepare_project(
location: &Path,
force_type: Option<ProjectType>,
options: Option<Options>,
) -> Result<PathBuf> {
let project_type = if force_type.is_some() {
force_type.unwrap()
} else {
identify_type(location)?
};

match project_type {
ProjectType::Local => Ok(PathBuf::from(location)),
ProjectType::Git => prepare_git_project(location, options),
}
}

/// Identify the type of the project based on different rules related to the location.
/// For example, an URL that ends in .git is considered a git repository. For any
/// unknown pattern, it will default to "Local"
pub fn identify_type(location: &Path) -> Result<ProjectType> {
if (location.starts_with("https://") || location.starts_with("http://"))
&& location
.extension()
.filter(|e| *e == OsStr::new("git"))
.is_some()
{
Ok(ProjectType::Git)
} else {
let path = Path::new(location);

if path.exists() {
Ok(ProjectType::Local)
} else {
bail!("The given path does not exist in the local filesystem.")
}
}
}

/// Install a runtime locally. It reads the provided configuration and
/// dowload the files. All files are saved in a store that references
/// the repository, the runtime name and version
Expand Down Expand Up @@ -89,3 +152,58 @@ async fn download_file(file: &RemoteFile, store: &Store) -> Result<()> {
let contents = fetch_and_validate(&file.url, &file.checksum).await?;
store.write(&[&file.filename], &contents)
}

#[cfg(test)]
mod tests {
use super::*;
use path_slash::PathBufExt as _;

#[test]
fn identify_local_locations() {
let tests = ["tests", "tests/data", "./tests", "./tests/data"];

for test in tests {
let file_route = PathBuf::from_slash(test);

match identify_type(&file_route) {
Ok(project_type) => {
assert!(matches!(project_type, ProjectType::Local));
}
Err(err) => panic!("Error identifying a the project type: {err}"),
}
}
}

#[test]
fn identify_local_error_when_missing() {
let tests = [
"missing",
"missing/missing",
"./missing/missing",
"./missing/missing",
];

for test in tests {
let file_route = PathBuf::from_slash(test);

match identify_type(&file_route) {
Ok(_) => {
panic!("The folder doesn't exist, so identifying it should fail.");
}
Err(err) => assert!(err.to_string().contains("does not exist")),
}
}
}

#[test]
fn identify_git_repository_locations() {
let location = Path::new("https://github.com/vmware-labs/wasm-workers-server.git");

match identify_type(location) {
Ok(project_type) => {
assert!(matches!(project_type, ProjectType::Git));
}
Err(err) => panic!("Error identifying a the project type: {err}"),
}
}
}
35 changes: 35 additions & 0 deletions crates/project/src/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Defines the different options to configure the project.
/// Every type has their own options.
#[derive(Default)]
pub struct Options {
/// Options for Git repositories
pub git: Option<GitOptions>,
/// Options for local repositories
pub local: Option<LocalOptions>,
}

/// For now, we don't have any particular option for this type.
/// I'm keeping it as a placeholder
#[derive(Default)]
pub struct LocalOptions {}

/// Defines a different reference when cloning the repository
pub enum GitReference {
/// Use a specific commit
Commit(String),
/// Use a specific tag
Tag(String),
/// Use a specific git branch
Branch(String),
}

/// The different git options you can configure.
#[derive(Default)]
pub struct GitOptions {
pub git_ref: Option<GitReference>,
/// Change the directory to run the workers
pub folder: Option<String>,
}
73 changes: 73 additions & 0 deletions crates/project/src/types/git.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::options::{GitReference, Options};
use anyhow::{anyhow, bail, Result};
use git2::{Oid, Repository};
use sha256::digest as sha256_digest;
use std::{
env::temp_dir,
fs::remove_dir_all,
path::{Path, PathBuf},
};

// Default remote for git repos
static DEFAULT_REMOTE: &str = "origin";

/// Prepare a project based on a git repository. This method
/// clones the repo locally and returns the path in which it's located.
pub fn prepare_git_project(location: &Path, options: Option<Options>) -> Result<PathBuf> {
let project_url = location
.to_str()
.ok_or(anyhow!("The project URL cannot be retrieved"))?;
// By default, we use temporary dirs
let mut dir = temp_dir().join(sha256_digest(project_url));

if dir.exists() {
// Clean up a previous download
remove_dir_all(&dir)?;
}

let repo = match Repository::clone(project_url, &dir) {
Ok(repo) => repo,
Err(e) => bail!("There was an error cloning the repository: {e}"),
};

if let Some(options) = options {
if let Some(git) = options.git {
if let Some(git_ref) = git.git_ref {
Comment on lines +36 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been looking for a better way to express this, but it requires let_chains and is unstable. :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, a new approach for "let chains" would be really great 😄. I will keep an eye on this. Thanks for the pointer.

match git_ref {
GitReference::Commit(commit) => {
let oid = Oid::from_str(&commit)?;
let commit = repo.find_commit(oid)?;
repo.checkout_tree(commit.as_object(), None)?;
}
GitReference::Tag(tag) => {
let mut remote = repo.find_remote(DEFAULT_REMOTE)?;
let tag_remote = format!("refs/tags/{tag}:refs/tags/{tag}");
remote.fetch(&[&tag_remote], None, None)?;

let oid = Oid::from_str(&tag)?;
let tag = repo.find_tag(oid)?;
repo.checkout_tree(tag.as_object(), None)?;
}
GitReference::Branch(branch) => {
let mut remote = repo.find_remote(DEFAULT_REMOTE)?;
let head_remote = format!("refs/heads/{branch}:refs/heads/{branch}");
remote.fetch(&[&head_remote], None, None)?;

let branch = repo.find_branch(&branch, git2::BranchType::Local)?;
let reference = branch.into_reference();
repo.checkout_tree(&reference.peel(git2::ObjectType::Tree)?, None)?;
}
}
}

if let Some(folder) = git.folder {
dir = dir.join(folder);
}
}
}

Ok(dir)
}
4 changes: 4 additions & 0 deletions crates/project/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright 2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

pub mod git;
Empty file.
2 changes: 1 addition & 1 deletion crates/router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ regex = "1"
wax = { git = "https://github.com/olson-sean-k/wax.git", rev = "6d66a10" }

[dev-dependencies]
path-slash = "0.2.1"
path-slash = { workspace = true }
Loading