blob: c2b88138aca86b0eb8ca2c343d720492b2de45e0 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{anyhow, Context, Result},
errors::{ffx_bail, ffx_bail_with_code},
ffx_config::{
add, api::query::ConfigQuery, api::ConfigError, env_file, environment::Environment, file,
get, print_config, raw, remove, set, set_metrics_status, show_metrics_status, ConfigLevel,
},
ffx_config_plugin_args::{
AddCommand, AnalyticsCommand, AnalyticsControlCommand, ConfigCommand, EnvAccessCommand,
EnvCommand, EnvSetCommand, GetCommand, MappingMode, RemoveCommand, SetCommand, SubCommand,
},
ffx_core::ffx_plugin,
serde_json::Value,
std::collections::HashMap,
std::fs::{File, OpenOptions},
std::io::Write,
std::path::PathBuf,
};
#[ffx_plugin()]
pub async fn exec_config(config: ConfigCommand) -> Result<()> {
let writer = Box::new(std::io::stdout());
match &config.sub {
SubCommand::Env(env) => exec_env(env, writer),
SubCommand::Get(get_cmd) => exec_get(get_cmd, writer).await,
SubCommand::Set(set_cmd) => exec_set(set_cmd).await,
SubCommand::Remove(remove_cmd) => exec_remove(remove_cmd).await,
SubCommand::Add(add_cmd) => exec_add(add_cmd).await,
SubCommand::Analytics(analytics_cmd) => exec_analytics(analytics_cmd).await,
}
}
fn output<W: Write + Sync>(mut writer: W, value: Option<Value>) -> Result<()> {
match value {
Some(v) => writeln!(writer, "{}", v).map_err(|e| anyhow!("{}", e)),
// Use 2 error code so wrapper scripts don't need check for the string to differentiate
// errors.
None => ffx_bail_with_code!(2, "Value not found"),
}
}
fn output_array<W: Write + Sync>(
mut writer: W,
values: std::result::Result<Vec<Value>, ConfigError>,
) -> Result<()> {
match values {
Ok(v) => {
if v.len() == 1 {
writeln!(writer, "{}", v[0]).map_err(|e| anyhow!("{}", e))
} else {
writeln!(writer, "{}", Value::Array(v)).map_err(|e| anyhow!("{}", e))
}
}
// Use 2 error code so wrapper scripts don't need check for the string to differentiate
// errors.
Err(_) => ffx_bail_with_code!(2, "Value not found"),
}
}
async fn exec_get<W: Write + Sync>(get_cmd: &GetCommand, writer: W) -> Result<()> {
let query = ConfigQuery::new(
get_cmd.name.as_ref().map(|s| s.as_str()),
None,
get_cmd.build_dir.as_ref().map(|s| s.as_str()),
get_cmd.select,
);
match get_cmd.name.as_ref() {
Some(_) => match get_cmd.process {
MappingMode::Raw => {
let value: Option<Value> = raw(query).await?;
output(writer, value)
}
MappingMode::Substitute => {
let value: std::result::Result<Vec<Value>, _> = get(query).await;
output_array(writer, value)
}
MappingMode::File => {
let value = file(query).await?;
output(writer, value)
}
},
None => print_config(writer, &get_cmd.build_dir).await,
}
}
async fn exec_set(set_cmd: &SetCommand) -> Result<()> {
set((&set_cmd.name, &set_cmd.level, &set_cmd.build_dir), set_cmd.value.clone()).await
}
async fn exec_remove(remove_cmd: &RemoveCommand) -> Result<()> {
remove((&remove_cmd.name, &remove_cmd.level, &remove_cmd.build_dir)).await
}
async fn exec_add(add_cmd: &AddCommand) -> Result<()> {
add(
(&add_cmd.name, &add_cmd.level, &add_cmd.build_dir),
Value::String(format!("{}", add_cmd.value)),
)
.await
}
fn exec_env_set<W: Write + Sync>(
mut writer: W,
env: &mut Environment,
s: &EnvSetCommand,
env_file: PathBuf,
) -> Result<()> {
if !env_file.exists() {
writeln!(writer, "\"{}\" does not exist, creating empty json file", env_file.display())?;
let mut file = File::create(&env_file).context("opening write buffer")?;
file.write_all(b"{}").context("writing configuration file")?;
file.sync_all().context("syncing configuration file to filesystem")?;
}
// Double check read/write permissions and create the file if it doesn't exist.
let _ = OpenOptions::new().read(true).write(true).create(true).open(&s.file)?;
match &s.level {
ConfigLevel::User => match env.user.as_mut() {
Some(v) => *v = s.file.to_string(),
None => env.user = Some(s.file.to_string()),
},
ConfigLevel::Build => match &s.build_dir {
Some(build_dir) => match env.build.as_mut() {
Some(b) => match b.get_mut(&s.file) {
Some(e) => *e = build_dir.to_string(),
None => {
b.insert(build_dir.to_string(), s.file.to_string());
}
},
None => {
let mut build = HashMap::new();
build.insert(build_dir.to_string(), s.file.to_string());
env.build = Some(build);
}
},
None => ffx_bail!("Missing --build-dir flag"),
},
ConfigLevel::Global => match env.global.as_mut() {
Some(v) => *v = s.file.to_string(),
None => env.global = Some(s.file.to_string()),
},
_ => ffx_bail!("This configuration is not stored in the environment."),
}
env.save(&env_file)
}
fn exec_env<W: Write + Sync>(env_command: &EnvCommand, mut writer: W) -> Result<()> {
let file = env_file().ok_or(anyhow!("Could not find environment file"))?;
let mut env = Environment::load(&file)?;
match &env_command.access {
Some(a) => match a {
EnvAccessCommand::Set(s) => exec_env_set(writer, &mut env, s, file),
EnvAccessCommand::Get(g) => {
writeln!(writer, "{}", env.display(&g.level))?;
Ok(())
}
},
None => {
writeln!(writer, "{}", env.display(&None))?;
Ok(())
}
}
}
async fn exec_analytics(analytics_cmd: &AnalyticsCommand) -> Result<()> {
let writer = Box::new(std::io::stdout());
match &analytics_cmd.sub {
AnalyticsControlCommand::Enable(_) => set_metrics_status(true).await?,
AnalyticsControlCommand::Disable(_) => set_metrics_status(false).await?,
AnalyticsControlCommand::Show(_) => show_metrics_status(writer).await?,
}
Ok(())
}
////////////////////////////////////////////////////////////////////////////////
// tests
#[cfg(test)]
mod test {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn test_exec_env_set_set_values() -> Result<()> {
let writer = Vec::<u8>::new();
let mut env = Environment::default();
let cmd = EnvSetCommand {
file: "test.json".to_string(),
level: ConfigLevel::User,
build_dir: None,
};
let tmp_file = NamedTempFile::new().expect("tmp access failed");
exec_env_set(writer, &mut env, &cmd, tmp_file.path().to_path_buf())?;
let result = Environment::load(&tmp_file)?;
assert_eq!(cmd.file, result.user.unwrap());
Ok(())
}
}