automatically update to the latest revision

This commit is contained in:
naskya 2024-06-21 04:27:03 +09:00
parent 6e908aebdb
commit a502c30791
Signed by: naskya
GPG key ID: 712D413B3A9FED5C
6 changed files with 122 additions and 23 deletions

View file

@ -76,7 +76,7 @@ Please make sure to `cd` to the Firefish local repository before running these c
[The admin note](https://firefish.dev/firefish/firefish/-/blob/main/docs/notice-for-admins.md) may tell you that you need to update the config files. In such a case, please execute the following command. [The admin note](https://firefish.dev/firefish/firefish/-/blob/main/docs/notice-for-admins.md) may tell you that you need to update the config files. In such a case, please execute the following command.
```sh ```sh
fishctl config migrate <revision> fishctl config migrate
``` ```
### Validate the config files ### Validate the config files

View file

@ -9,7 +9,7 @@ use clap::Subcommand;
#[derive(Subcommand)] #[derive(Subcommand)]
pub(crate) enum Commands { pub(crate) enum Commands {
/// Convert an old config file into the new format /// Convert an old config file into the new format
Migrate { revision: Revision }, Migrate { revision: Option<Revision> },
/// Validate the config file /// Validate the config file
Validate, Validate,
} }

View file

@ -2,18 +2,99 @@
mod v20240701; mod v20240701;
use crate::config::Revision; use crate::config::{Revision, CLIENT_CONFIG_PATH, OLD_CONFIG_PATH, SERVER_CONFIG_PATH};
use serde::Deserialize;
use std::{fs, io::Read, path::Path};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub(crate) enum MigrationError { pub(crate) enum MigrationError {
#[error("failed to determine the current config revision ({0})")]
UnknownRevision(&'static str),
#[error(transparent)]
ReadFile(#[from] std::io::Error),
#[error(transparent)]
InvalidConfig(#[from] toml::de::Error),
#[error(transparent)] #[error(transparent)]
V20240701(#[from] v20240701::Error), V20240701(#[from] v20240701::Error),
} }
pub(super) async fn run(revision: Revision) -> Result<(), MigrationError> { fn next_revision() -> Result<Option<Revision>, MigrationError> {
let old_config_exists = Path::new(OLD_CONFIG_PATH).is_file();
let server_config_exists = Path::new(SERVER_CONFIG_PATH).is_file();
let client_config_exists = Path::new(CLIENT_CONFIG_PATH).is_file();
if server_config_exists && !client_config_exists {
return Err(MigrationError::UnknownRevision(
"client config file does not exist",
));
}
if !server_config_exists && client_config_exists {
return Err(MigrationError::UnknownRevision(
"server config file does not exist",
));
}
if !old_config_exists && !server_config_exists && !client_config_exists {
return Err(MigrationError::UnknownRevision(
"config file does not exist",
));
}
if old_config_exists && !server_config_exists && !client_config_exists {
return Ok(Some(Revision::V20240701));
}
#[derive(Deserialize)]
struct Config {
config_revision: Revision,
}
let mut buffer = String::new();
let mut server_toml = fs::File::open(SERVER_CONFIG_PATH)?;
server_toml.read_to_string(&mut buffer)?;
let server_config_revision = toml::from_str::<Config>(&buffer)?.config_revision;
let mut client_toml = fs::File::open(CLIENT_CONFIG_PATH)?;
client_toml.read_to_string(&mut buffer)?;
let client_config_revision = toml::from_str::<Config>(&buffer)?.config_revision;
if server_config_revision != client_config_revision {
return Err(MigrationError::UnknownRevision(
"server config revision and client config revision are different",
));
}
let next_revision = match server_config_revision {
Revision::V20240701 => None,
};
Ok(next_revision)
}
async fn update_to_latest() -> Result<(), MigrationError> {
if next_revision()?.is_none() {
println!("Your config files are already up-to-date! (as of this fishctl release)");
return Ok(());
}
while let Some(next_revision) = next_revision()? {
run_impl(next_revision).await?;
}
Ok(())
}
async fn run_impl(revision: Revision) -> Result<(), MigrationError> {
match revision { match revision {
Revision::V20240701 => v20240701::run().await?, Revision::V20240701 => v20240701::run().await?,
} }
Ok(()) Ok(())
} }
pub(super) async fn run(revision: Option<Revision>) -> Result<(), MigrationError> {
match revision {
Some(revision) => run_impl(revision).await,
None => update_to_latest().await,
}
}

View file

@ -1,7 +1,9 @@
//! `config migrate 20240701` subcommand //! `config migrate 20240701` subcommand
//! <https://firefish.dev/firefish/firefish/-/issues/10947> //! <https://firefish.dev/firefish/firefish/-/issues/10947>
use crate::config::{client, server, Revision}; use crate::config::{
client, server, Revision, CLIENT_CONFIG_PATH, OLD_CONFIG_PATH, SERVER_CONFIG_PATH,
};
use color_print::cprintln; use color_print::cprintln;
use sqlx::{postgres::PgConnectOptions, ConnectOptions}; use sqlx::{postgres::PgConnectOptions, ConnectOptions};
use std::{ use std::{
@ -15,7 +17,7 @@ use yaml_rust::{Yaml, YamlLoader};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub(crate) enum Error { pub(crate) enum Error {
#[error("failed to parse the old config file (.config/default.yml)")] #[error("failed to parse the old config file ({})", OLD_CONFIG_PATH)]
ReadYaml(#[from] ReadYamlConfigError), ReadYaml(#[from] ReadYamlConfigError),
#[error(transparent)] #[error(transparent)]
WriteToml(#[from] WriteTomlConfigError), WriteToml(#[from] WriteTomlConfigError),
@ -43,16 +45,16 @@ pub(crate) enum WriteTomlConfigError {
ServerSer(#[source] toml::ser::Error), ServerSer(#[source] toml::ser::Error),
#[error("failed to serialize the new client config into TOML format")] #[error("failed to serialize the new client config into TOML format")]
ClientSer(#[source] toml::ser::Error), ClientSer(#[source] toml::ser::Error),
#[error("failed to write to `config/server.toml`")] #[error("failed to create {}", SERVER_CONFIG_PATH)]
ServerWrite(#[source] io::Error), ServerWrite(#[source] io::Error),
#[error("failed to write to `config/client.toml`")] #[error("failed to create {}", CLIENT_CONFIG_PATH)]
ClientWrite(#[source] io::Error), ClientWrite(#[source] io::Error),
#[error("failed to create `config` directory")] #[error("failed to create the config directory")]
Mkdir(#[source] io::Error), Mkdir(#[source] io::Error),
} }
fn read_default_yml() -> Result<HashMap<String, Yaml>, ReadYamlConfigError> { fn read_default_yml() -> Result<HashMap<String, Yaml>, ReadYamlConfigError> {
let mut default_yml = fs::File::open(".config/default.yml")?; let mut default_yml = fs::File::open(OLD_CONFIG_PATH)?;
let mut buffer = String::new(); let mut buffer = String::new();
default_yml.read_to_string(&mut buffer)?; default_yml.read_to_string(&mut buffer)?;
@ -430,6 +432,8 @@ fn create_new_client_config(meta: Meta) -> Result<client::Config, Error> {
} }
pub(super) async fn run() -> Result<(), Error> { pub(super) async fn run() -> Result<(), Error> {
println!("Updating the config revision to 20240701...");
let (default_yml, meta) = read_old_config().await?; let (default_yml, meta) = read_old_config().await?;
let server_config = create_new_server_config(default_yml, &meta)?; let server_config = create_new_server_config(default_yml, &meta)?;
@ -440,7 +444,7 @@ pub(super) async fn run() -> Result<(), Error> {
} }
let mut server_toml = let mut server_toml =
fs::File::create("config/server.toml").map_err(WriteTomlConfigError::ServerWrite)?; fs::File::create(SERVER_CONFIG_PATH).map_err(WriteTomlConfigError::ServerWrite)?;
server_toml server_toml
.write( .write(
toml::to_string_pretty(&server_config) toml::to_string_pretty(&server_config)
@ -448,10 +452,10 @@ pub(super) async fn run() -> Result<(), Error> {
.as_bytes(), .as_bytes(),
) )
.map_err(WriteTomlConfigError::ServerWrite)?; .map_err(WriteTomlConfigError::ServerWrite)?;
cprintln!("<bold>config/server.toml</> has been created!"); cprintln!("<bold>{}</> has been created!", SERVER_CONFIG_PATH);
let mut client_toml = let mut client_toml =
fs::File::create("config/client.toml").map_err(WriteTomlConfigError::ClientWrite)?; fs::File::create(CLIENT_CONFIG_PATH).map_err(WriteTomlConfigError::ClientWrite)?;
client_toml client_toml
.write( .write(
toml::to_string_pretty(&client_config) toml::to_string_pretty(&client_config)
@ -459,7 +463,7 @@ pub(super) async fn run() -> Result<(), Error> {
.as_bytes(), .as_bytes(),
) )
.map_err(WriteTomlConfigError::ClientWrite)?; .map_err(WriteTomlConfigError::ClientWrite)?;
cprintln!("<bold>config/client.toml</> has been created!"); cprintln!("<bold>{}</> has been created!", CLIENT_CONFIG_PATH);
Ok(()) Ok(())
} }

View file

@ -1,6 +1,6 @@
//! `config validate` subcommand //! `config validate` subcommand
use crate::config::{client, server}; use crate::config::{client, server, CLIENT_CONFIG_PATH, SERVER_CONFIG_PATH};
use color_print::cprintln; use color_print::cprintln;
use std::{fs, io::Read}; use std::{fs, io::Read};
use validator::Validate; use validator::Validate;
@ -36,18 +36,30 @@ pub(super) fn run() -> Result<(), ValidationError> {
let client_validation_result = match read_client_toml() { let client_validation_result = match read_client_toml() {
Ok(config) => config.validate().map_err(|err| { Ok(config) => config.validate().map_err(|err| {
cprintln!("<r!><bold>config/client.toml is invalid.</></>\n{}", err); cprintln!(
"<r!><bold>{} is invalid.</></>\n{}",
CLIENT_CONFIG_PATH,
err
);
ValidationError::InvalidConfig ValidationError::InvalidConfig
}), }),
Err(ReadError::InvalidFormat(err)) => { Err(ReadError::InvalidFormat(err)) => {
cprintln!("<r!><bold>config/client.toml is invalid.</></>\n{}", err); cprintln!(
"<r!><bold>{} is invalid.</></>\n{}",
CLIENT_CONFIG_PATH,
err
);
Err(ValidationError::InvalidConfig) Err(ValidationError::InvalidConfig)
} }
Err(ReadError::ReadFile(err)) => Err(ValidationError::ReadFile(err)), Err(ReadError::ReadFile(err)) => Err(ValidationError::ReadFile(err)),
}; };
if server_validation_result.is_ok() && client_validation_result.is_ok() { if server_validation_result.is_ok() && client_validation_result.is_ok() {
cprintln!("<g!><bold>config/server.toml and config/client.toml are valid!</></>"); cprintln!(
"<g!><bold>{} and {} are valid!</></>",
SERVER_CONFIG_PATH,
CLIENT_CONFIG_PATH
);
cprintln!("<bold>Note:</> This is only a formal validation, so this does not guarantee that the settings are appropriate."); cprintln!("<bold>Note:</> This is only a formal validation, so this does not guarantee that the settings are appropriate.");
} }
@ -55,7 +67,7 @@ pub(super) fn run() -> Result<(), ValidationError> {
} }
fn read_server_toml() -> Result<server::Config, ReadError> { fn read_server_toml() -> Result<server::Config, ReadError> {
let mut file = fs::File::open("config/server.toml")?; let mut file = fs::File::open(SERVER_CONFIG_PATH)?;
let mut buffer = String::new(); let mut buffer = String::new();
file.read_to_string(&mut buffer)?; file.read_to_string(&mut buffer)?;
@ -64,7 +76,7 @@ fn read_server_toml() -> Result<server::Config, ReadError> {
} }
fn read_client_toml() -> Result<client::Config, ReadError> { fn read_client_toml() -> Result<client::Config, ReadError> {
let mut file = fs::File::open("config/client.toml")?; let mut file = fs::File::open(CLIENT_CONFIG_PATH)?;
let mut buffer = String::new(); let mut buffer = String::new();
file.read_to_string(&mut buffer)?; file.read_to_string(&mut buffer)?;

View file

@ -4,11 +4,13 @@ pub mod server;
use clap::ValueEnum; use clap::ValueEnum;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Clone, ValueEnum, Debug)] #[derive(Deserialize, Serialize, PartialEq, PartialOrd, Clone, ValueEnum, Debug)]
pub enum Revision { pub enum Revision {
#[clap(name = "20240701")] #[clap(name = "20240701")]
#[serde(rename = "20240701")] #[serde(rename = "20240701")]
V20240701, V20240701,
} }
pub const LATEST_REVISION: Revision = Revision::V20240701; pub const SERVER_CONFIG_PATH: &str = "config/server.toml";
pub const CLIENT_CONFIG_PATH: &str = "config/client.toml";
pub const OLD_CONFIG_PATH: &str = ".config/default.yml";