Merge "Add ability to manually write to binary files." into main

This commit is contained in:
Marybeth Fair
2024-09-16 15:57:24 +00:00
committed by Gerrit Code Review
8 changed files with 125 additions and 25 deletions

View File

@@ -14,6 +14,7 @@ rust_defaults {
"libclap",
"libcxx",
"libaconfig_storage_protos",
"libserde",
],
}
@@ -36,7 +37,10 @@ rust_binary_host {
name: "aconfig-storage",
defaults: ["aconfig_storage_file.defaults"],
srcs: ["src/main.rs"],
rustlibs: ["libaconfig_storage_file"],
rustlibs: [
"libaconfig_storage_file",
"libserde_json",
],
}
rust_test_host {

View File

@@ -14,6 +14,8 @@ tempfile = "3.9.0"
thiserror = "1.0.56"
clap = { version = "4.1.8", features = ["derive"] }
cxx = "1.0"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
[[bin]]
name = "aconfig-storage"

View File

@@ -20,10 +20,11 @@
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Flag info header struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagInfoHeader {
pub version: u32,
pub container: String,
@@ -89,7 +90,7 @@ impl FlagInfoHeader {
}
/// bit field for flag info
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FlagInfoBit {
HasServerOverride = 1 << 0,
IsReadWrite = 1 << 1,
@@ -97,7 +98,7 @@ pub enum FlagInfoBit {
}
/// Flag info node struct
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Serialize, Deserialize)]
pub struct FlagInfoNode {
pub attributes: u8,
}
@@ -138,7 +139,7 @@ impl FlagInfoNode {
}
/// Flag info list struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagInfoList {
pub header: FlagInfoHeader,
pub nodes: Vec<FlagInfoNode>,

View File

@@ -23,10 +23,11 @@ use crate::{
};
use crate::{AconfigStorageError, StorageFileType, StoredFlagType};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Flag table header struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagTableHeader {
pub version: u32,
pub container: String,
@@ -95,7 +96,7 @@ impl FlagTableHeader {
}
/// Flag table node struct
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Serialize, Deserialize)]
pub struct FlagTableNode {
pub package_id: u32,
pub flag_name: String,
@@ -154,7 +155,7 @@ impl FlagTableNode {
}
}
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagTable {
pub header: FlagTableHeader,
pub buckets: Vec<Option<u32>>,

View File

@@ -20,10 +20,11 @@
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Flag value header struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagValueHeader {
pub version: u32,
pub container: String,
@@ -89,7 +90,7 @@ impl FlagValueHeader {
}
/// Flag value list struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagValueList {
pub header: FlagValueHeader,
pub booleans: Vec<bool>,

View File

@@ -41,6 +41,7 @@ pub mod sip_hasher13;
pub mod test_utils;
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fs::File;
use std::hash::Hasher;
@@ -107,7 +108,7 @@ impl TryFrom<u8> for StorageFileType {
/// Flag type enum as stored by storage file
/// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum StoredFlagType {
ReadWriteBoolean = 0,
ReadOnlyBoolean = 1,

View File

@@ -20,9 +20,29 @@ use aconfig_storage_file::{
list_flags, list_flags_with_info, read_file_to_bytes, AconfigStorageError, FlagInfoList,
FlagTable, FlagValueList, PackageTable, StorageFileType,
};
use clap::{builder::ArgAction, Arg, Command};
use serde::Serialize;
use serde_json;
use std::fmt;
use std::fs;
use std::fs::File;
use std::io::Write;
/**
* Usage Examples
*
* Print file:
* $ aconfig-storage print --file=path/to/flag.map --type=flag_map
*
* List flags:
* $ aconfig-storage list --flag-map=path/to/flag.map \
* --flag-val=path/to/flag.val --package-map=path/to/package.map
*
* Write binary file for testing:
* $ aconfig-storage print --file=path/to/flag.map --type=flag_map --format=json > flag_map.json
* $ vim flag_map.json // Manually make updates
* $ aconfig-storage write-bytes --input-file=flag_map.json --output-file=path/to/flag.map --type=flag_map
*/
fn cli() -> Command {
Command::new("aconfig-storage")
.subcommand_required(true)
@@ -34,7 +54,8 @@ fn cli() -> Command {
.long("type")
.required(true)
.value_parser(|s: &str| StorageFileType::try_from(s)),
),
)
.arg(Arg::new("format").long("format").required(false).action(ArgAction::Set)),
)
.subcommand(
Command::new("list")
@@ -50,41 +71,75 @@ fn cli() -> Command {
Arg::new("flag-info").long("flag-info").required(false).action(ArgAction::Set),
),
)
.subcommand(
Command::new("write-bytes")
// Where to write the output bytes. Suggest to use the StorageFileType names (e.g. flag.map).
.arg(
Arg::new("output-file")
.long("output-file")
.required(true)
.action(ArgAction::Set),
)
// Input file should be json.
.arg(
Arg::new("input-file").long("input-file").required(true).action(ArgAction::Set),
)
.arg(
Arg::new("type")
.long("type")
.required(true)
.value_parser(|s: &str| StorageFileType::try_from(s)),
),
)
}
fn print_storage_file(
file_path: &str,
file_type: &StorageFileType,
as_json: bool,
) -> Result<(), AconfigStorageError> {
let bytes = read_file_to_bytes(file_path)?;
match file_type {
StorageFileType::PackageMap => {
let package_table = PackageTable::from_bytes(&bytes)?;
println!("{:?}", package_table);
println!("{}", to_print_format(package_table, as_json));
}
StorageFileType::FlagMap => {
let flag_table = FlagTable::from_bytes(&bytes)?;
println!("{:?}", flag_table);
println!("{}", to_print_format(flag_table, as_json));
}
StorageFileType::FlagVal => {
let flag_value = FlagValueList::from_bytes(&bytes)?;
println!("{:?}", flag_value);
println!("{}", to_print_format(flag_value, as_json));
}
StorageFileType::FlagInfo => {
let flag_info = FlagInfoList::from_bytes(&bytes)?;
println!("{:?}", flag_info);
println!("{}", to_print_format(flag_info, as_json));
}
}
Ok(())
}
fn to_print_format<T>(file_contents: T, as_json: bool) -> String
where
T: Serialize + fmt::Debug,
{
if as_json {
serde_json::to_string(&file_contents).unwrap()
} else {
format!("{:?}", file_contents)
}
}
fn main() -> Result<(), AconfigStorageError> {
let matches = cli().get_matches();
match matches.subcommand() {
Some(("print", sub_matches)) => {
let file_path = sub_matches.get_one::<String>("file").unwrap();
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
print_storage_file(file_path, file_type)?
let format = sub_matches.get_one::<String>("format");
let as_json: bool = format == Some(&"json".to_string());
print_storage_file(file_path, file_type, as_json)?
}
Some(("list", sub_matches)) => {
let package_map = sub_matches.get_one::<String>("package-map").unwrap();
@@ -96,10 +151,10 @@ fn main() -> Result<(), AconfigStorageError> {
let flags = list_flags_with_info(package_map, flag_map, flag_val, info_file)?;
for flag in flags.iter() {
println!(
"{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
flag.is_readwrite, flag.has_server_override, flag.has_local_override,
);
"{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
flag.is_readwrite, flag.has_server_override, flag.has_local_override,
);
}
}
None => {
@@ -113,6 +168,40 @@ fn main() -> Result<(), AconfigStorageError> {
}
}
}
// Converts JSON of the file into raw bytes (as is used on-device).
// Intended to generate/easily update these files for testing.
Some(("write-bytes", sub_matches)) => {
let input_file_path = sub_matches.get_one::<String>("input-file").unwrap();
let input_json = fs::read_to_string(input_file_path).unwrap();
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
let output_bytes: Vec<u8>;
match file_type {
StorageFileType::FlagVal => {
let list: FlagValueList = serde_json::from_str(&input_json).unwrap();
output_bytes = list.into_bytes();
}
StorageFileType::FlagInfo => {
let list: FlagInfoList = serde_json::from_str(&input_json).unwrap();
output_bytes = list.into_bytes();
}
StorageFileType::FlagMap => {
let table: FlagTable = serde_json::from_str(&input_json).unwrap();
output_bytes = table.into_bytes();
}
StorageFileType::PackageMap => {
let table: PackageTable = serde_json::from_str(&input_json).unwrap();
output_bytes = table.into_bytes();
}
}
let output_file_path = sub_matches.get_one::<String>("output-file").unwrap();
let file = File::create(output_file_path);
if file.is_err() {
panic!("can't make file");
}
let _ = file.unwrap().write_all(&output_bytes);
}
_ => unreachable!(),
}
Ok(())

View File

@@ -20,10 +20,11 @@
use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Package table header struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct PackageTableHeader {
pub version: u32,
pub container: String,
@@ -92,7 +93,7 @@ impl PackageTableHeader {
}
/// Package table node struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct PackageTableNode {
pub package_name: String,
pub package_id: u32,
@@ -151,7 +152,7 @@ impl PackageTableNode {
}
/// Package table struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct PackageTable {
pub header: PackageTableHeader,
pub buckets: Vec<Option<u32>>,