Skip to content

Commit

Permalink
Add command to add Artifactory Auth (#91)
Browse files Browse the repository at this point in the history
We can already read Artifactory tokens, but Foreman has no way of adding
the tokens them itself. This PR looks to add the command
`artifactory-auth`, that places the tokens in the expected format in the
expected location.

This PR also includes a minor version bump since we are adding behavior.
  • Loading branch information
afujiwara-roblox authored Mar 5, 2024
1 parent 75deeeb commit 2774595
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ default-members = [".", "artiaa_auth"]
[package]
name = "foreman"
description = "Toolchain manager for simple binary tools"
version = "1.5.0"
version = "1.6.0"
authors = [
"Lucien Greathouse <me@lpghatguy.com>",
"Matt Hargett <plaztiksyke@gmail.com>",
Expand Down
72 changes: 72 additions & 0 deletions src/artifactory_auth_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::error::ForemanError;
use crate::{error::ForemanResult, fs};
use artiaa_auth::{error::ArtifactoryAuthError, Credentials};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use std::{
ops::{Deref, DerefMut},
path::Path,
};
/// Contains stored user tokens that Foreman can use to download tools.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ArtifactoryAuthStore {
tokens: HashMap<String, Credentials>,
}

impl Deref for ArtifactoryAuthStore {
type Target = HashMap<String, Credentials>;

fn deref(&self) -> &Self::Target {
&self.tokens
}
}

impl DerefMut for ArtifactoryAuthStore {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.tokens
}
}

impl ArtifactoryAuthStore {
pub fn set_token(auth_file: &Path, key: &str, token: &str) -> ForemanResult<()> {
let contents = fs::try_read_to_string(auth_file)?;

let mut store: ArtifactoryAuthStore = if let Some(contents) = contents {
serde_json::from_str(&contents).map_err(|err: serde_json::Error| {
ForemanError::ArtiAAError {
error: ArtifactoryAuthError::auth_parsing(auth_file, err.to_string()),
}
})?
} else {
ArtifactoryAuthStore::default()
};

store.insert(
key.to_owned(),
Credentials {
username: "".to_owned(),
token: token.to_owned(),
},
);

let serialized =
serde_json::to_string_pretty(&store).map_err(|err: serde_json::Error| {
ForemanError::ArtiAAError {
error: ArtifactoryAuthError::auth_parsing(auth_file, err.to_string()),
}
})?;

if let Some(dir) = auth_file.parent() {
fs::create_dir_all(dir)?;
fs::write(auth_file, serialized)
} else {
Err(ForemanError::ArtiAAError {
error: ArtifactoryAuthError::auth_parsing(
auth_file,
"Could not find parent directory of auth file".to_owned(),
),
})
}
}
}
62 changes: 61 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod aliaser;
mod artifact_choosing;
mod artifactory_auth_store;
mod artifactory_path;
mod auth_store;
mod ci_string;
Expand All @@ -11,8 +12,13 @@ mod process;
mod tool_cache;
mod tool_provider;

use std::{env, ffi::OsStr};
use std::{
env,
ffi::OsStr,
io::{stdout, Write},
};

use artifactory_auth_store::ArtifactoryAuthStore;
use paths::ForemanPaths;
use structopt::StructOpt;

Expand Down Expand Up @@ -153,6 +159,11 @@ enum Subcommand {
#[structopt(name = "gitlab-auth")]
GitLabAuth(GitLabAuthCommand),

/// Set the Artifactory Token that Foreman should use with the
/// Artifactory API.
#[structopt(name = "artifactory-auth")]
ArtifactoryAuth(ArtifactoryAuthCommand),

/// Create a path to publish to artifactory
///
/// Foreman does not support uploading binaries to artifactory directly, but it can generate the path where it would expect to find a given artifact. Use this command to generate paths that can be input to generic artifactory upload solutions.
Expand All @@ -176,6 +187,12 @@ struct GitLabAuthCommand {
token: Option<String>,
}

#[derive(Debug, StructOpt)]
struct ArtifactoryAuthCommand {
url: Option<String>,
token: Option<String>,
}

#[derive(Debug, StructOpt)]
struct GenerateArtifactoryPathCommand {
repo: String,
Expand Down Expand Up @@ -297,11 +314,54 @@ fn actual_main(paths: ForemanPaths) -> ForemanResult<()> {
)?;
println!("{}", artifactory_path);
}
Subcommand::ArtifactoryAuth(subcommand) => {
let url = prompt_url(subcommand.url)?;

let token = prompt_auth_token(
subcommand.token,
"Artifactory",
"https://jfrog.com/help/r/jfrog-platform-administration-documentation/access-tokens",
)?;

ArtifactoryAuthStore::set_token(&paths.artiaa_path()?, &url, &token)?;
}
}

Ok(())
}

fn prompt_url(url: Option<String>) -> Result<String, ForemanError> {
match url {
Some(url) => Ok(url),
None => {
println!("Artifactory auth saved successfully.");
println!("Foreman requires a specific URL to authenticate to Artifactory.");
println!();

loop {
let mut input = String::new();

print!("Artifactory URL: ");
stdout().flush().map_err(|err| {
ForemanError::io_error_with_context(
err,
"an error happened trying to flush stdout",
)
})?;
std::io::stdin().read_line(&mut input).map_err(|err| {
ForemanError::io_error_with_context(err, "an error happened trying to read url")
})?;

if input.is_empty() {
println!("Token must be non-empty.");
} else {
break Ok(input);
}
}
}
}
}

fn prompt_auth_token(
token: Option<String>,
provider: &str,
Expand Down
2 changes: 1 addition & 1 deletion src/tool_provider/artifactory.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Slice of GitHub's API that Foreman consumes.
//! Slice of Artifactory's API that Foreman consumes.

use super::{Release, ReleaseAsset, ToolProviderImpl};
use crate::{
Expand Down
4 changes: 3 additions & 1 deletion tests/snapshots/help_command.snap
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
---
source: tests/cli.rs
assertion_line: 100
expression: content
---
foreman 1.5.0
foreman 1.6.0

USAGE:
foreman [FLAGS] <SUBCOMMAND>
Expand All @@ -13,6 +14,7 @@ FLAGS:
-v Logging verbosity. Supply multiple for more verbosity, up to -vvv

SUBCOMMANDS:
artifactory-auth Set the Artifactory Token that Foreman should use with the Artifactory API
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
Expand Down

0 comments on commit 2774595

Please sign in to comment.