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

APT-548 added generate-artifactory-path command to foreman #84

Merged
merged 3 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -30,7 +30,7 @@ jobs:

build:
needs: checks
timeout-minutes: 10
timeout-minutes: 15
strategy:
matrix:
os: [windows-latest, ubuntu-latest]
Expand Down
2 changes: 2 additions & 0 deletions src/artifact_choosing.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//After updating this file, consider updating artifactory_path.rs to reflect the operating systems and architectures that Foreman recognizes
Copy link
Contributor

Choose a reason for hiding this comment

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

I wish there was a better way to tie this together, but it doesn't seem like it. Luckily, since Matt added a fairly comprehensive set, it shouldn't need to change often.


#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
static PLATFORM_KEYWORDS: &[&str] = &["win64", "windows-x86_64", "windows"];

Expand Down
150 changes: 150 additions & 0 deletions src/artifactory_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use crate::error::{ForemanError, ForemanResult};
use semver::Version;
use std::io::{Error, ErrorKind};

// Redundant operating systems that Foreman recognizes are not included;
static VALID_OS: &[&str] = &["windows", "macos", "linux"];
static VALID_ARCH: &[&str] = &["x86_64", "arm64", "aarch64", "i686"];

pub fn generate_artifactory_path<S: Into<String>>(
repo: S,
tool_name: S,
version: S,
operating_system: S,
architecture: Option<S>,
) -> ForemanResult<String> {
let repo = repo.into();
let tool_name = tool_name.into();
let version = version.into();
let operating_system = operating_system.into();

check_valid_os(&operating_system)?;
check_valid_version(&version)?;
let mut full_tool_name = format!("{}-{}-{}", tool_name, version, operating_system);
if let Some(architecture) = architecture {
let architecture = architecture.into();
check_valid_arch(&architecture)?;
full_tool_name.push('-');
full_tool_name.push_str(&architecture);
}

full_tool_name.push_str(".zip");

Ok(format!(
"artifactory/{}/{}/{}/{}",
repo, tool_name, version, full_tool_name
))
}

fn check_valid_os(operating_system: &str) -> ForemanResult<()> {
if !VALID_OS.contains(&operating_system) {
return Err(ForemanError::io_error_with_context(
Error::new(ErrorKind::InvalidInput, "Invalid Argument"),
format!(
"Invalid operating system: {}. Please input a valid operating system: {}",
operating_system,
VALID_OS.join(", ")
),
));
} else {
Ok(())
}
}

fn check_valid_arch(architecture: &str) -> ForemanResult<()> {
if !VALID_ARCH.contains(&architecture) {
return Err(ForemanError::io_error_with_context(
Error::new(ErrorKind::InvalidInput, "Invalid Argument"),
format!(
"Invalid architecture: {}. Please input a valid architecture: {}",
architecture,
VALID_ARCH.join(", ")
),
));
} else {
Ok(())
}
}

fn check_valid_version(version: &str) -> ForemanResult<()> {
if !version.starts_with('v') {
return Err(ForemanError::io_error_with_context(
Error::new(ErrorKind::InvalidInput, "Invalid Argument"),
format!("Invalid version: {}. Versions must start with a v", version),
));
}

if let Err(err) = Version::parse(&version[1..]) {
Err(ForemanError::io_error_with_context(
Error::new(ErrorKind::InvalidInput, "Invalid Argument"),
format!("Invalid version: {}. Error: {}", version, err),
))
} else {
Ok(())
}
}

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

#[test]
fn simple_path() {
let path = generate_artifactory_path("repo", "tool_name", "v0.1.0", "macos", None).unwrap();
assert_eq!(
path,
"artifactory/repo/tool_name/v0.1.0/tool_name-v0.1.0-macos.zip"
Copy link
Contributor

Choose a reason for hiding this comment

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

One interesting detail here is that the version number isn't duplicated in the github releases, but it also probably isn't a problem (we only really look for the os/arch keywords, iirc). I think we should be fine as is, but it's something to call attention to in case it affects our ability to share the artifact-choosing/download implementation.

);
}

#[test]
fn simple_path_with_arch() {
let path = generate_artifactory_path("repo", "tool_name", "v0.1.0", "macos", Some("arm64"))
.unwrap();
assert_eq!(
path,
"artifactory/repo/tool_name/v0.1.0/tool_name-v0.1.0-macos-arm64.zip"
);
}

#[test]
fn invalid_version_no_v() {
let path = generate_artifactory_path("repo", "tool_name", "0.1.0", "macos", Some("arm64"))
.unwrap_err();
assert_eq!(
path.to_string(),
"Invalid version: 0.1.0. Versions must start with a v: Invalid Argument".to_string()
);
}
#[test]
fn invalid_version_incomplete() {
let path = generate_artifactory_path("repo", "tool_name", "v0.1", "macos", Some("arm64"))
.unwrap_err();
assert_eq!(
path.to_string(),
"Invalid version: v0.1. Error: unexpected end of input while parsing minor version number: Invalid Argument".to_string()
);
}

#[test]
fn invalid_operating_system() {
let path =
generate_artifactory_path("repo", "tool_name", "v0.1.0", "fake_os", Some("arm64"))
.unwrap_err();
assert_eq!(
path.to_string(),
"Invalid operating system: fake_os. Please input a valid operating system: windows, macos, linux: Invalid Argument".to_string()
);
}

#[test]
fn invalid_architecture() {
let path =
generate_artifactory_path("repo", "tool_name", "v0.1.0", "macos", Some("fake_arch"))
.unwrap_err();
assert_eq!(
path.to_string(),
"Invalid architecture: fake_arch. Please input a valid architecture: x86_64, arm64, aarch64, i686: Invalid Argument".to_string()
);
}
}
26 changes: 26 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod aliaser;
mod artifact_choosing;
mod artifactory_path;
mod auth_store;
mod ci_string;
mod config;
Expand Down Expand Up @@ -151,6 +152,12 @@ enum Subcommand {
/// This token can also be configured by editing ~/.foreman/auth.toml.
#[structopt(name = "gitlab-auth")]
GitLabAuth(GitLabAuthCommand),

/// Create a path to publish to artifactory
///
/// Foreman does not support uploading the artifactory itself but will generate the path it expects to find artifacts.
afujiwara-roblox marked this conversation as resolved.
Show resolved Hide resolved
#[structopt(name = "generate-artifactory-path")]
GenerateArtifactoryPath(GenerateArtifactoryPathCommand),
}

#[derive(Debug, StructOpt)]
Expand All @@ -169,6 +176,15 @@ struct GitLabAuthCommand {
token: Option<String>,
}

#[derive(Debug, StructOpt)]
struct GenerateArtifactoryPathCommand {
repo: String,
tool_name: String,
version: String,
operating_system: String,
architecture: Option<String>,
}

fn actual_main(paths: ForemanPaths) -> ForemanResult<()> {
let options = Options::from_args();

Expand Down Expand Up @@ -271,6 +287,16 @@ fn actual_main(paths: ForemanPaths) -> ForemanResult<()> {

println!("GitLab auth saved successfully.");
}
Subcommand::GenerateArtifactoryPath(subcommand) => {
let artifactory_path = artifactory_path::generate_artifactory_path(
subcommand.repo,
subcommand.tool_name,
subcommand.version,
subcommand.operating_system,
subcommand.architecture,
)?;
println!("{}", artifactory_path);
}
}

Ok(())
Expand Down
11 changes: 6 additions & 5 deletions tests/snapshots/help_command.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ FLAGS:
-v Logging verbosity. Supply multiple for more verbosity, up to -vvv

SUBCOMMANDS:
github-auth Set the GitHub Personal Access Token that Foreman should use with the GitHub API
gitlab-auth Set the GitLab Personal Access Token that Foreman should use with the GitLab API
help Prints this message or the help of the given subcommand(s)
install Install tools defined by foreman.toml
list List installed tools
generate-artifactory-path Create a path to publish to artifactory
github-auth Set the GitHub Personal Access Token that Foreman should use with the GitHub API
gitlab-auth Set the GitLab Personal Access Token that Foreman should use with the GitLab API
help Prints this message or the help of the given subcommand(s)
install Install tools defined by foreman.toml
list List installed tools


Loading