Skip to content

Commit

Permalink
Merge pull request #29 from blake-mealey/dev/outputs
Browse files Browse the repository at this point in the history
added outputs command
  • Loading branch information
blake-mealey authored Nov 10, 2021
2 parents 5ba2c7d + 92dac54 commit 0d18b3c
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 124 deletions.
33 changes: 33 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,31 @@ fn get_app() -> App<'static, 'static> {
.takes_value(true),
),
)
.subcommand(SubCommand::with_name("outputs")
.about("Prints the outputs from the project to the console or a file")
.arg(
Arg::with_name("PROJECT")
.index(1)
.help("The project to print outputs from: either the path to a directory containing a 'rocat.yml' file or the path to a configuration file. Defaults to the current directory.")
.takes_value(true),
)
.arg(
Arg::with_name("output")
.long("output")
.short("o")
.help("A file path to print the outputs to")
.value_name("FILE")
.takes_value(true))
.arg(
Arg::with_name("format")
.long("format")
.short("f")
.help("The format to print the outputs in")
.value_name("FORMAT")
.takes_value(true)
.possible_values(&["json","yaml"])
.default_value("json"))
)
}

pub async fn run_with(args: Vec<String>) -> i32 {
Expand All @@ -27,6 +52,14 @@ pub async fn run_with(args: Vec<String>) -> i32 {
("deploy", Some(deploy_matches)) => {
commands::deploy::run(deploy_matches.value_of("PROJECT")).await
}
("outputs", Some(outputs_matches)) => {
commands::outputs::run(
outputs_matches.value_of("PROJECT"),
outputs_matches.value_of("output"),
outputs_matches.value_of("format").unwrap(),
)
.await
}
_ => unreachable!(),
}
}
Expand Down
128 changes: 4 additions & 124 deletions src/commands/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,135 +1,15 @@
use std::{
path::{Path, PathBuf},
process::Command,
str,
};
use std::str;

use yansi::Paint;

use crate::{
config::{load_config_file, Config, DeploymentConfig},
logger,
project::{load_project, Project},
resource_manager::RobloxResourceManager,
resources::{EvaluateResults, ResourceGraph, ResourceManager},
state::{get_desired_graph, get_previous_state, save_state, ResourceState},
resources::{EvaluateResults, ResourceManager},
state::save_state,
};

fn run_command(command: &str) -> std::io::Result<std::process::Output> {
if cfg!(target_os = "windows") {
return Command::new("cmd").arg("/C").arg(command).output();
} else {
return Command::new("sh").arg("-c").arg(command).output();
}
}

fn get_current_branch() -> Result<String, String> {
let output = run_command("git symbolic-ref --short HEAD");
let result = match output {
Ok(v) => v,
Err(e) => {
return Err(format!(
"Unable to determine git branch. Are you in a git repository?\n\t{}",
e
))
}
};

if !result.status.success() {
return Err("Unable to determine git branch. Are you in a git repository?".to_string());
}

let current_branch = str::from_utf8(&result.stdout).unwrap().trim();
if current_branch.is_empty() {
return Err("Unable to determine git branch. Are you in a git repository?".to_string());
}

Ok(current_branch.to_owned())
}

fn match_branch(branch: &str, patterns: &[String]) -> bool {
for pattern in patterns {
let glob_pattern = glob::Pattern::new(pattern);
if glob_pattern.is_ok() && glob_pattern.unwrap().matches(branch) {
return true;
}
}
false
}

fn parse_project(project: Option<&str>) -> Result<(PathBuf, PathBuf), String> {
let project = project.unwrap_or(".");
let project_path = Path::new(project).to_owned();

let (project_dir, config_file) = if project_path.is_dir() {
(project_path.clone(), project_path.join("rocat.yml"))
} else if project_path.is_file() {
(project_path.parent().unwrap().into(), project_path)
} else {
return Err(format!("Unable to load project path: {}", project));
};

if config_file.exists() {
return Ok((project_dir, config_file));
}

Err(format!("Config file {} not found", config_file.display()))
}

struct Project {
project_path: PathBuf,
next_graph: ResourceGraph,
previous_graph: ResourceGraph,
state: ResourceState,
deployment_config: DeploymentConfig,
config: Config,
}

async fn load_project(project: Option<&str>) -> Result<Option<Project>, String> {
let (project_path, config_file) = parse_project(project)?;

let config = load_config_file(&config_file)?;
logger::log(format!("Loaded config file {}", config_file.display()));

let current_branch = get_current_branch()?;

let deployment_config = config
.deployments
.iter()
.find(|deployment| match_branch(&current_branch, &deployment.branches));

let deployment_config = match deployment_config {
Some(v) => v,
None => {
logger::log(format!(
"No deployment configuration found for branch '{}'",
current_branch
));
return Ok(None);
}
};
logger::log(format!(
"Found deployment configuration '{}' for branch '{}'",
deployment_config.name, current_branch
));

// Get previous state
let state = get_previous_state(project_path.as_path(), &config, deployment_config).await?;

// Get our resource graphs
let previous_graph =
ResourceGraph::new(state.deployments.get(&deployment_config.name).unwrap());
let next_graph = get_desired_graph(project_path.as_path(), &config, deployment_config)?;

Ok(Some(Project {
project_path,
next_graph,
previous_graph,
state,
deployment_config: deployment_config.clone(),
config,
}))
}

pub async fn run(project: Option<&str>) -> i32 {
logger::start_action("Loading project:");
let Project {
Expand Down
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod deploy;
pub mod outputs;
65 changes: 65 additions & 0 deletions src/commands/outputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::{collections::BTreeMap, fs};

use yansi::Paint;

use crate::{
logger,
project::{load_project, Project},
resources::ResourceRef,
};

pub async fn run(project: Option<&str>, output: Option<&str>, format: &str) -> i32 {
logger::start_action("Load outputs:");
let Project { previous_graph, .. } = match load_project(project).await {
Ok(Some(v)) => v,
Ok(None) => {
logger::end_action("No outputs available");
return 0;
}
Err(e) => {
logger::end_action(Paint::red(e));
return 1;
}
};

let resources = previous_graph.get_resource_list();
let outputs_list: Vec<(ResourceRef, &BTreeMap<String, serde_yaml::Value>)> = resources
.iter()
.filter_map(|r| r.outputs.as_ref().map(|o| (r.get_ref(), o)))
.collect();

let mut outputs_map: BTreeMap<String, BTreeMap<String, BTreeMap<String, serde_yaml::Value>>> =
BTreeMap::new();
for ((resource_type, resource_id), outputs) in outputs_list {
let type_map = outputs_map
.entry(resource_type)
.or_insert_with(BTreeMap::new);
type_map.insert(resource_id, outputs.clone());
}

let outputs_string = match match format {
"json" => serde_json::to_string_pretty(&outputs_map).map_err(|e| e.to_string()),
"yaml" => serde_yaml::to_string(&outputs_map).map_err(|e| e.to_string()),
_ => Err(format!("Unknown format: {}", format)),
} {
Ok(v) => v,
Err(e) => {
logger::end_action(Paint::red(format!("Failed to serialize outputs: {}", e)));
return 1;
}
};
logger::end_action("Succeeded");

if let Some(output) = output {
if let Err(e) = fs::write(output, outputs_string)
.map_err(|e| format!("Unable to write outputs file: {}\n\t{}", output, e))
{
logger::log(Paint::red(e));
return 1;
}
} else {
logger::log(outputs_string);
}

0
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod cli;
mod commands;
mod config;
mod logger;
mod project;
mod resource_manager;
mod resources;
mod roblox_api;
Expand Down
128 changes: 128 additions & 0 deletions src/project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use std::{
path::{Path, PathBuf},
process::Command,
str,
};

use crate::{
config::{load_config_file, Config, DeploymentConfig},
logger,
resources::ResourceGraph,
state::{get_desired_graph, get_previous_state, ResourceState},
};

fn parse_project(project: Option<&str>) -> Result<(PathBuf, PathBuf), String> {
let project = project.unwrap_or(".");
let project_path = Path::new(project).to_owned();

let (project_dir, config_file) = if project_path.is_dir() {
(project_path.clone(), project_path.join("rocat.yml"))
} else if project_path.is_file() {
(project_path.parent().unwrap().into(), project_path)
} else {
return Err(format!("Unable to load project path: {}", project));
};

if config_file.exists() {
return Ok((project_dir, config_file));
}

Err(format!("Config file {} not found", config_file.display()))
}

fn run_command(command: &str) -> std::io::Result<std::process::Output> {
if cfg!(target_os = "windows") {
return Command::new("cmd").arg("/C").arg(command).output();
} else {
return Command::new("sh").arg("-c").arg(command).output();
}
}

fn get_current_branch() -> Result<String, String> {
let output = run_command("git symbolic-ref --short HEAD");
let result = match output {
Ok(v) => v,
Err(e) => {
return Err(format!(
"Unable to determine git branch. Are you in a git repository?\n\t{}",
e
))
}
};

if !result.status.success() {
return Err("Unable to determine git branch. Are you in a git repository?".to_string());
}

let current_branch = str::from_utf8(&result.stdout).unwrap().trim();
if current_branch.is_empty() {
return Err("Unable to determine git branch. Are you in a git repository?".to_string());
}

Ok(current_branch.to_owned())
}

fn match_branch(branch: &str, patterns: &[String]) -> bool {
for pattern in patterns {
let glob_pattern = glob::Pattern::new(pattern);
if glob_pattern.is_ok() && glob_pattern.unwrap().matches(branch) {
return true;
}
}
false
}

pub struct Project {
pub project_path: PathBuf,
pub next_graph: ResourceGraph,
pub previous_graph: ResourceGraph,
pub state: ResourceState,
pub deployment_config: DeploymentConfig,
pub config: Config,
}

pub async fn load_project(project: Option<&str>) -> Result<Option<Project>, String> {
let (project_path, config_file) = parse_project(project)?;

let config = load_config_file(&config_file)?;
logger::log(format!("Loaded config file {}", config_file.display()));

let current_branch = get_current_branch()?;

let deployment_config = config
.deployments
.iter()
.find(|deployment| match_branch(&current_branch, &deployment.branches));

let deployment_config = match deployment_config {
Some(v) => v,
None => {
logger::log(format!(
"No deployment configuration found for branch '{}'",
current_branch
));
return Ok(None);
}
};
logger::log(format!(
"Found deployment configuration '{}' for branch '{}'",
deployment_config.name, current_branch
));

// Get previous state
let state = get_previous_state(project_path.as_path(), &config, deployment_config).await?;

// Get our resource graphs
let previous_graph =
ResourceGraph::new(state.deployments.get(&deployment_config.name).unwrap());
let next_graph = get_desired_graph(project_path.as_path(), &config, deployment_config)?;

Ok(Some(Project {
project_path,
next_graph,
previous_graph,
state,
deployment_config: deployment_config.clone(),
config,
}))
}

0 comments on commit 0d18b3c

Please sign in to comment.