-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from blake-mealey/dev/outputs
added outputs command
- Loading branch information
Showing
6 changed files
with
232 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
pub mod deploy; | ||
pub mod outputs; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(¤t_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, | ||
})) | ||
} |