blob: 84d27398ae1435eb22e9ad28f2c7c56da86d7d17 [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 argh::{ArgsInfo, FromArgs};
use ffx_config::api::query::SelectMode;
use ffx_config::{ConfigLevel, ConfigQuery, EnvironmentContext};
use ffx_core::ffx_command;
use std::path::PathBuf;
#[ffx_command]
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(
subcommand,
name = "config",
description = "View and switch default and user configurations"
)]
pub struct ConfigCommand {
#[argh(subcommand)]
pub sub: SubCommand,
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand)]
pub enum SubCommand {
CheckSshKeys(SshKeyCommand),
Env(EnvCommand),
Get(GetCommand),
Set(SetCommand),
Remove(RemoveCommand),
Add(AddCommand),
Analytics(AnalyticsCommand),
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "set", description = "set config settings")]
pub struct SetCommand {
#[argh(positional)]
/// name of the property to set
pub name: String,
#[argh(positional, from_str_fn(parse_set_value))]
/// value to associate with name
pub value: serde_json::Value,
#[argh(option, default = "ConfigLevel::User", short = 'l')]
/// config level. Possible values are "user", "build", "global". Defaults to "user".
pub level: ConfigLevel,
#[argh(option, short = 'b')]
/// an optional build directory to associate the build config provided - used for "build"
/// configs. If not provided, it may attempt to autodiscover your active build directory.
pub build_dir: Option<PathBuf>,
}
impl SetCommand {
pub fn query<'a>(&'a self, ctx: &'a EnvironmentContext) -> ConfigQuery<'a> {
ConfigQuery::new(
Some(self.name.as_str()),
Some(self.level),
SelectMode::default(),
Some(ctx),
)
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum MappingMode {
Raw,
Substitute,
File,
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(
subcommand,
name = "get",
description = "display config values",
error_code(2, "No value found")
)]
pub struct GetCommand {
#[argh(positional)]
/// name of the config property
pub name: Option<String>,
#[argh(
option,
from_str_fn(parse_mapping_mode),
default = "MappingMode::Substitute",
short = 'p'
)]
/// how to process results. Possible values are "r/raw", "s/sub/substitute", or "f/file".
/// Defaults to "substitute". Currently only supported if a name is given.
/// The process type "file" returns a scalar value. In the case of the configuration being
/// a list, it is treated as an ordered list of alternatives and takes the first value
/// that exists.
pub process: MappingMode,
#[argh(option, from_str_fn(parse_mode), default = "SelectMode::First", short = 's')]
/// how to collect results. Possible values are "first" and "all". Defaults to
/// "first". If the value is "first", the first value found in terms of priority is returned.
/// If the value is "all", all values across all configuration levels are aggregrated and
/// returned. Currently only supported if a name is given.
pub select: SelectMode,
#[argh(option, short = 'b')]
/// an optional build directory to associate the build config provided - used for "build"
/// configs. If not provided, it may attempt to autodiscover your active build directory.
pub build_dir: Option<PathBuf>,
}
impl GetCommand {
pub fn query<'a>(&'a self, ctx: &'a EnvironmentContext) -> ConfigQuery<'a> {
ConfigQuery::new(self.name.as_deref(), None, self.select, Some(ctx))
}
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(
subcommand,
name = "remove",
description = "remove config for a given level",
note = "This will remove the entire value for the given name. If the value is a subtree or \
array, the entire subtree or array will be removed. If you want to remove a specific value \
from an array, consider editing the configuration file directly. Configuration file \
locations can be found by running `ffx config env get` command."
)]
pub struct RemoveCommand {
#[argh(positional)]
/// name of the config property
pub name: String,
#[argh(option, default = "ConfigLevel::User", short = 'l')]
/// config level. Possible values are "user", "build", "global". Defaults to "user".
pub level: ConfigLevel,
#[argh(option, short = 'b')]
/// an optional build directory to associate the build config provided - used for "build"
/// configs. If not provided, it may attempt to autodiscover your active build directory.
pub build_dir: Option<PathBuf>,
}
impl RemoveCommand {
pub fn query<'a>(&'a self, ctx: &'a EnvironmentContext) -> ConfigQuery<'a> {
ConfigQuery::new(
Some(self.name.as_str()),
Some(self.level),
SelectMode::default(),
Some(ctx),
)
}
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(
subcommand,
name = "add",
description = "add config value the end of an array",
note = "This will always add to the end of an array. Adding to a subtree is not supported. \
If the current value is not an array, it will convert the value to an array. If you want \
to insert a value in a different position, consider editing the configuration file \
directly. Configuration file locations can be found by running `ffx config env get` \
command."
)]
pub struct AddCommand {
#[argh(positional)]
/// name of the property to set
pub name: String,
#[argh(positional)]
/// value to add to name
pub value: String,
#[argh(option, default = "ConfigLevel::User", short = 'l')]
/// config level. Possible values are "user", "build", "global". Defaults to "user".
pub level: ConfigLevel,
#[argh(option, short = 'b')]
/// an optional build directory to associate the build config provided - used for "build"
/// configs. If not provided, it may attempt to autodiscover your active build directory.
pub build_dir: Option<PathBuf>,
}
impl AddCommand {
pub fn query<'a>(&'a self, ctx: &'a EnvironmentContext) -> ConfigQuery<'a> {
ConfigQuery::new(
Some(self.name.as_str()),
Some(self.level),
SelectMode::default(),
Some(ctx),
)
}
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "env", description = "list environment settings")]
pub struct EnvCommand {
#[argh(subcommand)]
pub access: Option<EnvAccessCommand>,
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand)]
pub enum EnvAccessCommand {
Set(EnvSetCommand),
Get(EnvGetCommand),
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "set", description = "set environment settings")]
pub struct EnvSetCommand {
#[argh(positional)]
/// path to the config file for the configuration level provided
pub file: PathBuf,
#[argh(option, default = "ConfigLevel::User", short = 'l')]
/// config level. Possible values are "user", "build", "global". Defaults to "user".
pub level: ConfigLevel,
#[argh(option, short = 'b')]
/// an optional build directory to associate the build config provided - used for "build"
/// configs. If not provided, it may attempt to autodiscover your active build directory.
pub build_dir: Option<PathBuf>,
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "get", description = "list environment for a given level")]
pub struct EnvGetCommand {
#[argh(positional)]
/// config level. Possible values are "user", "build", "global".
pub level: Option<ConfigLevel>,
}
fn parse_set_value(value: &str) -> Result<serde_json::Value, String> {
serde_json::from_str(value).or_else(|_| Ok(serde_json::Value::String(value.to_string())))
}
fn parse_mapping_mode(value: &str) -> Result<MappingMode, String> {
match value {
"r" | "raw" => Ok(MappingMode::Raw),
"s" | "sub" | "substitute" => Ok(MappingMode::Substitute),
"f" | "file" => Ok(MappingMode::File),
_ => Err(String::from(
"Unrecognized value. Possible values are \"raw\", \"sub\", or \"file\".",
)),
}
}
fn parse_mode(value: &str) -> Result<SelectMode, String> {
match value {
"f" | "first" | "first_found" => Ok(SelectMode::First),
"a" | "all" | "add" | "additive" => Ok(SelectMode::All),
_ => Err(String::from(
"Unrecognized value. Possible values are \"first_found\" or \"additive\".",
)),
}
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "analytics", description = "enable or disable analytics")]
pub struct AnalyticsCommand {
#[argh(subcommand)]
pub sub: AnalyticsControlCommand,
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand)]
pub enum AnalyticsControlCommand {
EnableEnhanced(AnalyticsEnableEnhancedCommand),
Enable(AnalyticsEnableCommand),
Disable(AnalyticsDisableCommand),
Show(AnalyticsShowCommand),
}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(
subcommand,
name = "enable-enhanced",
description = "enable enhanced analytics (Googlers only)"
)]
pub struct AnalyticsEnableEnhancedCommand {}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "enable", description = "enable basic (redacted) analytics")]
pub struct AnalyticsEnableCommand {}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "disable", description = "disable analytics")]
pub struct AnalyticsDisableCommand {}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "show", description = "show analytics")]
pub struct AnalyticsShowCommand {}
#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(
subcommand,
name = "check-ssh-keys",
description = "check the ssh key configuration and create keys if needed."
)]
pub struct SshKeyCommand {}
#[cfg(test)]
mod tests {
use super::*;
const CMD_NAME: &'static [&'static str] = &["config"];
#[test]
fn test_env_get() {
fn check(args: &[&str], expected_level: Option<ConfigLevel>) {
assert_eq!(
ConfigCommand::from_args(CMD_NAME, args),
Ok(ConfigCommand {
sub: SubCommand::Env(EnvCommand {
access: Some(EnvAccessCommand::Get(EnvGetCommand {
level: expected_level,
})),
})
})
)
}
let levels = [
("build", Some(ConfigLevel::Build)),
("user", Some(ConfigLevel::User)),
("global", Some(ConfigLevel::Global)),
];
for level_opt in levels.iter() {
check(&["env", "get", &level_opt.0], level_opt.1);
}
}
#[test]
fn test_env_set() {
fn check(args: &[&str], expected_level: ConfigLevel) {
assert_eq!(
ConfigCommand::from_args(CMD_NAME, args),
Ok(ConfigCommand {
sub: SubCommand::Env(EnvCommand {
access: Some(EnvAccessCommand::Set(EnvSetCommand {
level: expected_level,
file: "/test/config.json".into(),
build_dir: Some("/test/".into()),
})),
})
})
)
}
let levels = [
("build", ConfigLevel::Build),
("user", ConfigLevel::User),
("global", ConfigLevel::Global),
];
for level_opt in levels.iter() {
check(
&[
"env",
"set",
"/test/config.json",
"--level",
&level_opt.0,
"--build-dir",
"/test/",
],
level_opt.1,
);
}
}
#[test]
fn test_get() {
fn check(args: &[&str], expected_key: &str, expected_build_dir: Option<PathBuf>) {
assert_eq!(
ConfigCommand::from_args(CMD_NAME, args),
Ok(ConfigCommand {
sub: SubCommand::Get(GetCommand {
process: MappingMode::Substitute,
select: SelectMode::First,
name: Some(expected_key.to_string()),
build_dir: expected_build_dir,
})
})
)
}
let key = "test-key";
let build_dir = "/test/";
check(&["get", key], key, None);
check(&["get", key, "--build-dir", build_dir], key, Some(build_dir.into()));
}
#[test]
fn test_set() {
fn check(
args: &[&str],
expected_level: ConfigLevel,
expected_key: &str,
expected_value: &serde_json::Value,
expected_build_dir: Option<PathBuf>,
) {
assert_eq!(
ConfigCommand::from_args(CMD_NAME, args),
Ok(ConfigCommand {
sub: SubCommand::Set(SetCommand {
level: expected_level,
name: expected_key.to_string(),
value: expected_value.clone(),
build_dir: expected_build_dir,
})
})
)
}
let key = "test-key";
let value = "test-value";
let value_json = serde_json::Value::String(value.to_string());
let build_dir = "/test/";
let levels = [
("build", ConfigLevel::Build),
("user", ConfigLevel::User),
("global", ConfigLevel::Global),
];
for level_opt in levels.iter() {
check(
&["set", key, value, "--level", level_opt.0],
level_opt.1,
key,
&value_json,
None,
);
check(
&["set", key, value, "--level", level_opt.0, "--build-dir", build_dir],
level_opt.1,
key,
&value_json,
Some(build_dir.into()),
);
}
}
#[test]
fn test_set_json() {
fn check(
args: &[&str],
expected_level: ConfigLevel,
expected_key: &str,
expected_value: &serde_json::Value,
expected_build_dir: Option<PathBuf>,
) {
assert_eq!(
ConfigCommand::from_args(CMD_NAME, args),
Ok(ConfigCommand {
sub: SubCommand::Set(SetCommand {
level: expected_level,
name: expected_key.to_string(),
value: expected_value.clone(),
build_dir: expected_build_dir,
})
})
)
}
let key = "test-key";
let value = "{\"test\": \"test-value\"}";
let value_json = serde_json::json!({"test": "test-value"});
let build_dir = "/test/";
let levels = [
("build", ConfigLevel::Build),
("user", ConfigLevel::User),
("global", ConfigLevel::Global),
];
for level_opt in levels.iter() {
check(
&["set", key, value, "--level", level_opt.0],
level_opt.1,
key,
&value_json,
None,
);
check(
&["set", key, value, "--level", level_opt.0, "--build-dir", build_dir],
level_opt.1,
key,
&value_json,
Some(build_dir.into()),
);
}
}
#[test]
fn test_remove() {
fn check(
args: &[&str],
expected_level: ConfigLevel,
expected_key: &str,
expected_build_dir: Option<PathBuf>,
) {
assert_eq!(
ConfigCommand::from_args(CMD_NAME, args),
Ok(ConfigCommand {
sub: SubCommand::Remove(RemoveCommand {
level: expected_level,
name: expected_key.to_string(),
build_dir: expected_build_dir,
})
})
)
}
let key = "test-key";
let build_dir = "/test/";
let levels = [
("build", ConfigLevel::Build),
("user", ConfigLevel::User),
("global", ConfigLevel::Global),
];
for level_opt in levels.iter() {
check(&["remove", key, "--level", level_opt.0], level_opt.1, key, None);
check(
&["remove", key, "--level", level_opt.0, "--build-dir", build_dir],
level_opt.1,
key,
Some(build_dir.into()),
);
}
}
}