Skip to content

Commit

Permalink
feat(cli): add a "config edit" command (#1360)
Browse files Browse the repository at this point in the history
This PR adds a new feature to load and save the current configuration
using an external editor. It is still a work in progress but it already
works.

<details>
<summary>Edit the user and select a product</summary>

[Grabación de pantalla desde 2024-06-20
07-22-33.webm](https://github.com/openSUSE/agama/assets/15836/bf661bf1-464f-4b0d-bbcd-1ddebadb33e1)
</details>

## Additional changes

* Dropped the `--format` option as it is not expected to work with a
format different from JSON.
* Improve JSON formatting (by using `serde_json::to_string_pretty()`.
  • Loading branch information
imobachgs committed Jun 20, 2024
2 parents 7b4db54 + a94bd50 commit be7223d
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 102 deletions.
97 changes: 76 additions & 21 deletions rust/agama-cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,47 @@
use std::io::{self, Read};

use crate::{
error::CliError,
printers::{print, Format},
use std::{
io::{self, Read},
path::PathBuf,
process::Command,
};

use crate::show_progress;
use agama_lib::{
auth::AuthToken, connection, install_settings::InstallSettings, Store as SettingsStore,
};
use anyhow::anyhow;
use clap::Subcommand;
use std::io::Write;
use tempfile::Builder;

const DEFAULT_EDITOR: &str = "/usr/bin/vi";

#[derive(Subcommand, Debug)]
pub enum ConfigCommands {
/// Generates an installation profile with the current settings.
/// Generate an installation profile with the current settings.
///
/// It is possible that many configuration settings do not have a value. Those settings
/// are not included in the output.
///
/// The output of command can be used as input for the "agama config load".
Show,

/// Reads and loads a profile from the standard input.
/// Read and load a profile from the standard input.
Load,
}

pub enum ConfigAction {
Show,
Load,
/// Edit and update installation option using an external editor.
///
/// The changes are not applied if the editor exits with an error code.
///
/// If an editor is not specified, it honors the EDITOR environment variable. It falls back to
/// `/usr/bin/vi` as a last resort.
Edit {
/// Editor command (including additional arguments if needed)
#[arg(short, long)]
editor: Option<String>,
},
}

pub async fn run(subcommand: ConfigCommands, format: Format) -> anyhow::Result<()> {
pub async fn run(subcommand: ConfigCommands) -> anyhow::Result<()> {
let Some(token) = AuthToken::find() else {
println!("You need to login for generating a valid token");
return Ok(());
Expand All @@ -37,26 +50,68 @@ pub async fn run(subcommand: ConfigCommands, format: Format) -> anyhow::Result<(
let client = agama_lib::http_client(token.as_str())?;
let store = SettingsStore::new(connection().await?, client).await?;

let command = parse_config_command(subcommand)?;
match command {
ConfigAction::Show => {
match subcommand {
ConfigCommands::Show => {
let model = store.load().await?;
print(model, std::io::stdout(), format)?;
let json = serde_json::to_string_pretty(&model)?;
println!("{}", json);
Ok(())
}
ConfigAction::Load => {
ConfigCommands::Load => {
let mut stdin = io::stdin();
let mut contents = String::new();
stdin.read_to_string(&mut contents)?;
let result: InstallSettings = serde_json::from_str(&contents)?;
Ok(store.store(&result).await?)
}
ConfigCommands::Edit { editor } => {
let model = store.load().await?;
let editor = editor
.or_else(|| std::env::var("EDITOR").ok())
.unwrap_or(DEFAULT_EDITOR.to_string());
let result = edit(&model, &editor)?;
tokio::spawn(async move {
show_progress().await.unwrap();
});
store.store(&result).await?;
Ok(())
}
}
}

fn parse_config_command(subcommand: ConfigCommands) -> Result<ConfigAction, CliError> {
match subcommand {
ConfigCommands::Show => Ok(ConfigAction::Show),
ConfigCommands::Load => Ok(ConfigAction::Load),
/// Edit the installation settings using an external editor.
///
/// If the editor does not return a successful error code, it returns an error.
///
/// * `model`: current installation settings.
/// * `editor`: editor command.
fn edit(model: &InstallSettings, editor: &str) -> anyhow::Result<InstallSettings> {
let content = serde_json::to_string_pretty(model)?;
let mut file = Builder::new().suffix(".json").tempfile()?;
let path = PathBuf::from(file.path());
write!(file, "{}", content)?;

let mut command = editor_command(&editor);
let status = command.arg(path.as_os_str()).status()?;
if status.success() {
return Ok(InstallSettings::from_file(path)?);
}

Err(anyhow!(
"Ignoring the changes becase the editor was closed with an error code."
))
}

/// Return the Command to run the editor.
///
/// Separate the program and the arguments and build a Command struct.
///
/// * `command`: command to run as editor.
fn editor_command(command: &str) -> Command {
let mut parts = command.split_whitespace();
let program = parts.next().unwrap_or(DEFAULT_EDITOR);

let mut command = Command::new(program);
command.args(parts.collect::<Vec<&str>>());
command
}
8 changes: 1 addition & 7 deletions rust/agama-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ mod commands;
mod config;
mod error;
mod logs;
mod printers;
mod profile;
mod progress;
mod questions;
Expand All @@ -18,7 +17,6 @@ use auth::run as run_auth_cmd;
use commands::Commands;
use config::run as run_config_cmd;
use logs::run as run_logs_cmd;
use printers::Format;
use profile::run as run_profile_cmd;
use progress::InstallerProgress;
use questions::run as run_questions_cmd;
Expand All @@ -39,10 +37,6 @@ use std::{
struct Cli {
#[command(subcommand)]
pub command: Commands,

/// Format output
#[arg(value_enum, short, long, default_value_t = Format::Json)]
pub format: Format,
}

async fn probe() -> anyhow::Result<()> {
Expand Down Expand Up @@ -129,7 +123,7 @@ async fn run_command(cli: Cli) -> anyhow::Result<()> {
Commands::Config(subcommand) => {
let manager = build_manager().await?;
wait_for_services(&manager).await?;
run_config_cmd(subcommand, cli.format).await
run_config_cmd(subcommand).await
}
Commands::Probe => {
let manager = build_manager().await?;
Expand Down
74 changes: 0 additions & 74 deletions rust/agama-cli/src/printers.rs

This file was deleted.

7 changes: 7 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Thu Jun 20 12:58:32 UTC 2024 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>

- Add a new "config edit" command allows editing installation
settings using an external editor (gh#openSUSE/agama#1360).
- Remove the "--format" option (gh#openSUSE/agama#1360).

-------------------------------------------------------------------
Thu Jun 20 05:32:42 UTC 2024 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>

Expand Down

0 comments on commit be7223d

Please sign in to comment.