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

Support gitlab tools #31

Merged
merged 10 commits into from
Jan 6, 2022
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

strategy:
matrix:
rust_version: [stable, "1.41.0"]
rust_version: [stable, "1.42.0"]

steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Foreman Changelog

## Unreleased Changes
- Support tools hosted on GitLab ([#31](https://github.com/Roblox/foreman/pull/31))
- Updated config format to support both GitHub and GitLab tools
- Added `foreman gitlab-auth` command for authenticating with GitLab.
- Logging improvements ([#30](https://github.com/Roblox/foreman/pull/30))
- Add commandline option to increase logging level (`-v`, `-vv`, etc)
- Add an INFO-level log explaining when a release version tag name doesn't match expected convention.
- Default logging to INFO level. Fixes ([#27]https://github.com/Roblox/foreman/issues/27).


## 1.0.2 (2020-05-20)
- Fixed Foreman not propagating error codes from underlying tools. ([#20](https://github.com/Roblox/foreman/pull/20))

Expand Down
7 changes: 7 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 @@ -26,4 +26,5 @@ serde_json = "1.0.47"
structopt = "0.3.20"
toml = "0.5.6"
toml_edit = "0.1.5"
urlencoding = "2.1.0"
zip = "0.5"
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,41 @@ You can download pre-built Foreman releases for Windows, macOS, and Linux from t
You can use the official [setup-foreman](https://github.com/rojo-rbx/setup-foreman) action to install Foreman as part of your GitHub Actions workflow.

### From Source
If you have [Rust](https://www.rust-lang.org/) 1.41.0 or newer installed, you can also compile Foreman by installing it from [crates.io](https://crates.io):
If you have [Rust](https://www.rust-lang.org/) 1.42.0 or newer installed, you can also compile Foreman by installing it from [crates.io](https://crates.io):

```bash
cargo install foreman
```

## Usage
Foreman downloads tools from GitHub and references them by their `user/repo` name, like `Roblox/foreman`.
Foreman downloads tools from GitHub or GitLab and references them by their `user/repo` name, like `Roblox/foreman`.

On first run (try `foreman list`), Foreman will create a `.foreman` directory in your user folder (usually `~/.foreman` on Unix systems, `%USERPROFILE%/.foreman` on Windows).

It's recommended that you **add `~/.foreman/bin` to your `PATH`** to make the tools that Foreman installs for you accessible on your system.

### Configuration File

Foreman uses [TOML](https://toml.io/en/) for its configuration file. It simply takes a single `tools` entry and an enumeration of the tools you need, which looks like this:

```toml
[tools]
rojo = { github = "rojo-rbx/rojo", version = "7.0.0" }
darklua = { gitlab = "seaofvoices/darklua", version = "0.7.0" }
```

As you may already have noticed, the tool name is located at the left side of `=` and the right side contains the information necessary to download it. For GitHub tools, use `github = "user/repo-name"` and for GitLab, use `gitlab = "user/repo-name"`.

Previously, foreman was only able to download tools from GitHub and the format used to be `source = "rojo-rbx/rojo"`. For backward compatibility, foreman still supports this format.

### System Tools
To start using Foreman to manage your system's default tools, create the file `~/.foreman/foreman.toml`.

A Foreman config that lists Rojo could look like:

```toml
[tools]
rojo = { source = "rojo-rbx/rojo", version = "0.5.0" }
rojo = { github = "rojo-rbx/rojo", version = "7.0.0" }
```

Run `foreman install` from any directory to have Foreman pick up and install the tools listed in your system's Foreman config.
Expand All @@ -53,7 +67,7 @@ A Foreman config that lists Remodel might look like this:

```toml
[tools]
remodel = { source = "rojo-rbx/remodel", version = "0.6.1" }
remodel = { github = "rojo-rbx/remodel", version = "0.9.1" }
```

Run `foreman install` to tell Foreman to install any new binaries from this config file.
Expand All @@ -65,6 +79,8 @@ To install tools from a private GitHub repository, Foreman supports authenticati

Use `foreman github-auth` to pass an authentication token to Foreman, or open `~/.foreman/auth.toml` and follow the contained instructions.

Similarly, for projects hosted on a GitLab repository, use `foreman gitlab-auth` to pass an authentication token to Foreman, or open `~/.foreman/auth.toml`.

## Troubleshooting
Foreman is a work in progress tool and has some known issues. Check out [the issue tracker](https://github.com/Roblox/foreman/issues) for known bugs.

Expand Down
8 changes: 8 additions & 0 deletions resources/default-auth.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
# `github` key. This is useful if you hit GitHub API rate limits or if you need
# to access private tools.

# github = "YOUR_TOKEN_HERE"

# For authenticating with GitLab.com, put a personal access token here under the
# `gitlab` key. This is useful if you hit GitLab API rate limits or if you need
# to access private tools.

# gitlab = "YOUR_TOKEN_HERE"

# You can also run `foreman github-auth` to update this file, optionally passing
# the token as the first argument.

Expand Down
20 changes: 18 additions & 2 deletions src/auth_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub static DEFAULT_AUTH_CONFIG: &str = include_str!("../resources/default-auth.t
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct AuthStore {
pub github: Option<String>,
pub gitlab: Option<String>,
}

impl AuthStore {
Expand All @@ -21,9 +22,16 @@ impl AuthStore {
Ok(contents) => {
let store: AuthStore = toml::from_slice(&contents).unwrap();

let mut found_credentials = false;
if store.github.is_some() {
log::debug!("Found GitHub credentials");
} else {
found_credentials = true;
}
if store.gitlab.is_some() {
log::debug!("Found GitLab credentials");
found_credentials = true;
}
if !found_credentials {
log::debug!("Found no credentials");
}

Expand All @@ -40,6 +48,14 @@ impl AuthStore {
}

pub fn set_github_token(token: &str) -> io::Result<()> {
Self::set_token("github", token)
}

pub fn set_gitlab_token(token: &str) -> io::Result<()> {
Self::set_token("gitlab", token)
}

fn set_token(key: &str, token: &str) -> io::Result<()> {
let contents = match fs::read_to_string(paths::auth_store()) {
Ok(contents) => contents,
Err(err) => {
Expand All @@ -52,7 +68,7 @@ impl AuthStore {
};

let mut store: Document = contents.parse().unwrap();
store["github"] = value(token);
store[key] = value(token);

let serialized = store.to_string();
fs::write(paths::auth_store(), serialized)
Expand Down
6 changes: 6 additions & 0 deletions src/ci_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ use serde::{Deserialize, Serialize};
#[serde(transparent)]
pub struct CiString(pub String);

impl From<&str> for CiString {
fn from(string: &str) -> Self {
Self(string.to_owned())
}
}

impl fmt::Display for CiString {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(&self.0)
Expand Down
130 changes: 124 additions & 6 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,73 @@
use std::{collections::HashMap, env, io};
use std::{collections::HashMap, env, fmt, io};

use semver::VersionReq;
use serde::{Deserialize, Serialize};

use crate::{fs, paths};
use crate::{ci_string::CiString, fs, paths, tool_provider::Provider};

#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigFile {
pub tools: HashMap<String, ToolSpec>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ToolSpec {
pub source: String,
pub version: VersionReq,
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolSpec {
Github {
// alias to `source` for backward compatibilty
#[serde(alias = "source")]
github: String,
version: VersionReq,
},
Gitlab {
gitlab: String,
version: VersionReq,
},
}

impl ToolSpec {
pub fn cache_key(&self) -> CiString {
match self {
ToolSpec::Github { github, .. } => CiString(github.clone()),
ToolSpec::Gitlab { gitlab, .. } => CiString(format!("gitlab@{}", gitlab)),
}
}

pub fn source(&self) -> &str {
match self {
ToolSpec::Github { github: source, .. } | ToolSpec::Gitlab { gitlab: source, .. } => {
source
}
}
}

pub fn version(&self) -> &VersionReq {
match self {
ToolSpec::Github { version, .. } | ToolSpec::Gitlab { version, .. } => version,
}
}

pub fn provider(&self) -> Provider {
match self {
ToolSpec::Github { .. } => Provider::Github,
ToolSpec::Gitlab { .. } => Provider::Gitlab,
}
}
}

impl fmt::Display for ToolSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}.com/{}@{}",
match self {
ToolSpec::Github { .. } => "github",
ToolSpec::Gitlab { .. } => "gitlab",
},
self.source(),
self.version(),
)
}
}

impl ConfigFile {
Expand Down Expand Up @@ -75,3 +129,67 @@ impl ConfigFile {
Ok(config)
}
}

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

fn new_github<S: Into<String>>(github: S, version: VersionReq) -> ToolSpec {
ToolSpec::Github {
github: github.into(),
version,
}
}

fn new_gitlab<S: Into<String>>(github: S, version: VersionReq) -> ToolSpec {
ToolSpec::Gitlab {
gitlab: github.into(),
version,
}
}

fn version(string: &str) -> VersionReq {
VersionReq::parse(string).unwrap()
}

mod deserialization {
use super::*;

#[test]
fn github_from_source_field() {
let github: ToolSpec =
toml::from_str(&[r#"source = "user/repo""#, r#"version = "0.1.0""#].join("\n"))
.unwrap();
assert_eq!(github, new_github("user/repo", version("0.1.0")));
}

#[test]
fn github_from_github_field() {
let github: ToolSpec =
toml::from_str(&[r#"github = "user/repo""#, r#"version = "0.1.0""#].join("\n"))
.unwrap();
assert_eq!(github, new_github("user/repo", version("0.1.0")));
}

#[test]
fn gitlab_from_gitlab_field() {
let gitlab: ToolSpec =
toml::from_str(&[r#"gitlab = "user/repo""#, r#"version = "0.1.0""#].join("\n"))
.unwrap();
assert_eq!(gitlab, new_gitlab("user/repo", version("0.1.0")));
}
}

#[test]
fn tool_cache_entry_is_backward_compatible() {
let github = new_github("user/repo", version("7.0.0"));
assert_eq!(github.cache_key(), "user/repo".into());
}

#[test]
fn tool_cache_entry_is_different_for_github_and_gitlab_identical_projects() {
let github = new_github("user/repo", version("7.0.0"));
let gitlab = new_gitlab("user/repo", version("7.0.0"));
assert_ne!(github.cache_key(), gitlab.cache_key());
}
}
Loading