Skip to content

Commit b6d0f7a

Browse files
A nicer way to export definitions to stdout and import them from stdin
instead of a brittle and shell/escaping-dependent special value of '-', which is still supported for backwards compatibility, even in single quotes, the user is now asked to pass --stdout and --stdin, respectively. '
1 parent 2e61c7b commit b6d0f7a

File tree

3 files changed

+201
-40
lines changed

3 files changed

+201
-40
lines changed

CHANGELOG.md

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,50 @@
11
# rabbitmqadmin-ng Change Log
22

3-
## v0.29.0 (in development)
3+
## v0.30.0 (in development)
44

5-
In (documented) changes yet.
5+
No (documented) changes yet.
6+
7+
8+
## v0.29.0 (Mar 23, 2025)
9+
10+
### Breaking Changes
11+
12+
* `definitions export`'s special `--file` value of `-` for "standard input" is deprecated. Use `--stdout` instead:
13+
14+
```shell
15+
rabbitmqadmin definitions export --stdout > definitions.json
16+
```
17+
18+
```shell
19+
# exports 3.x-era definitions that might contain classic queue mirroring keys, transforms
20+
# them to not use any CMQ policies, injects an explicit queue type into the matched queues,
21+
# and drops all the policies that had nothing beyond the CMQ keys,
22+
# then passes the result to the standard input of
23+
# 'rabbitmqadmin definitions import --stdin'
24+
rabbitmqadmin --node "source.node" definitions export --transformations strip_cmq_keys_from_policies,drop_empty_policies --stdout | rabbitmqadmin --node "destination.node" definitions import --stdin
25+
```
26+
27+
### Enhancements
28+
29+
* 'definitions import` now supports reading definitions from the standard input instead of a file.
30+
For that, pass `--stdin` instead of `--file "/path/to/definitions.json"`.
31+
32+
```shell
33+
rabbitmqadmin definitions import --stdin < definitions.json
34+
```
35+
36+
```shell
37+
cat definitions.json | rabbitmqadmin definitions import --stdin
38+
```
39+
40+
```shell
41+
# exports 3.x-era definitions that might contain classic queue mirroring keys, transforms
42+
# them to not use any CMQ policies, injects an explicit queue type into the matched queues,
43+
# and drops all the policies that had nothing beyond the CMQ keys,
44+
# then passes the result to the standard input of
45+
# 'rabbitmqadmin definitions import --stdin'
46+
rabbitmqadmin --node "source.node" definitions export --transformations strip_cmq_keys_from_policies,drop_empty_policies --stdout | rabbitmqadmin --node "destination.node" definitions import --stdin
47+
```
648

749

850
## v0.28.0 (Mar 23, 2025)

src/cli.rs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,10 +1203,22 @@ fn definitions_subcommands() -> [Command; 4] {
12031203
))
12041204
.arg(
12051205
Arg::new("file")
1206+
.group("output")
12061207
.long("file")
1207-
.help("output file path or '-' for standard output")
1208+
.help("output file path")
12081209
.required(false)
1209-
.default_value("-"),
1210+
.default_value("-")
1211+
.conflicts_with("stdout"),
1212+
)
1213+
.arg(
1214+
Arg::new("stdout")
1215+
.group("output")
1216+
.long("stdout")
1217+
.help("print result to the standard output stream")
1218+
.required(false)
1219+
.num_args(0)
1220+
.action(ArgAction::SetTrue)
1221+
.conflicts_with("file"),
12101222
)
12111223
.arg(
12121224
Arg::new("transformations")
@@ -1250,10 +1262,22 @@ Examples:
12501262
))
12511263
.arg(
12521264
Arg::new("file")
1265+
.group("output")
12531266
.long("file")
1254-
.help("output file path or '-' for standard output")
1267+
.help("output file path")
12551268
.required(false)
1256-
.default_value("-"),
1269+
.default_value("-")
1270+
.conflicts_with("stdout"),
1271+
)
1272+
.arg(
1273+
Arg::new("stdout")
1274+
.group("output")
1275+
.long("stdout")
1276+
.help("print result to the standard output stream")
1277+
.required(false)
1278+
.num_args(0)
1279+
.action(ArgAction::SetTrue)
1280+
.conflicts_with("file"),
12571281
);
12581282

12591283
let import_cmd = Command::new("import")
@@ -1264,9 +1288,21 @@ Examples:
12641288
))
12651289
.arg(
12661290
Arg::new("file")
1291+
.group("input")
12671292
.long("file")
1268-
.help("JSON file with cluster-wide definitions")
1269-
.required(true),
1293+
.help("cluster-wide definitions JSON file path; mutually exclusive with --stdin")
1294+
.required(true)
1295+
.conflicts_with("stdin"),
1296+
)
1297+
.arg(
1298+
Arg::new("stdin")
1299+
.group("input")
1300+
.long("stdin")
1301+
.help("read input JSON from the standard input stream, mutually exclusive with --file")
1302+
.required(false)
1303+
.num_args(0)
1304+
.action(ArgAction::SetTrue)
1305+
.conflicts_with("file"),
12701306
);
12711307

12721308
let import_into_vhost_cmd = Command::new("import_into_vhost")
@@ -1277,9 +1313,21 @@ Examples:
12771313
))
12781314
.arg(
12791315
Arg::new("file")
1316+
.group("input")
12801317
.long("file")
1281-
.help("JSON file with virtual host-specific definitions")
1282-
.required(true),
1318+
.help("cluster-wide definitions JSON file path; mutually exclusive with --stdin")
1319+
.required(true)
1320+
.conflicts_with("stdin"),
1321+
)
1322+
.arg(
1323+
Arg::new("stdin")
1324+
.group("input")
1325+
.long("stdin")
1326+
.help("read input JSON from the standard input stream, mutually exclusive with --file")
1327+
.required(false)
1328+
.num_args(0)
1329+
.action(ArgAction::SetTrue)
1330+
.conflicts_with("file"),
12831331
);
12841332

12851333
[

src/commands.rs

Lines changed: 101 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,16 +1215,31 @@ fn export_cluster_wide_definitions_without_transformations(
12151215
) -> ClientResult<()> {
12161216
match client.export_cluster_wide_definitions() {
12171217
Ok(definitions) => {
1218-
let path = command_args.get_one::<String>("file").unwrap();
1219-
match path.as_str() {
1220-
"-" => {
1218+
let path = command_args.get_one::<String>("file").cloned();
1219+
let use_stdout = command_args.get_one::<bool>("stdout").copied();
1220+
match (path, use_stdout) {
1221+
(Some(_val), Some(true)) => {
12211222
println!("{}", &definitions);
12221223
Ok(())
12231224
}
1224-
file => {
1225-
_ = fs::write(file, &definitions);
1225+
(Some(val), Some(false)) => match val.as_str() {
1226+
"-" => {
1227+
println!("{}", &definitions);
1228+
Ok(())
1229+
}
1230+
_ => {
1231+
_ = fs::write(val, &definitions);
1232+
Ok(())
1233+
}
1234+
},
1235+
(_, Some(true)) => {
1236+
println!("{}", &definitions);
12261237
Ok(())
12271238
}
1239+
_ => {
1240+
eprintln!("either --file or --stdout must be provided");
1241+
process::exit(1)
1242+
}
12281243
}
12291244
}
12301245
Err(err) => Err(err),
@@ -1257,19 +1272,39 @@ pub fn export_vhost_definitions(
12571272
pub fn import_definitions(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> {
12581273
let path = command_args
12591274
.get_one::<String>("file")
1260-
.map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"'))
1261-
.unwrap();
1262-
let definitions = read_definitions(path);
1275+
.map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"'));
1276+
let use_stdin = command_args.get_one::<bool>("stdin").copied();
1277+
let definitions = read_definitions(path, use_stdin);
12631278
match definitions {
12641279
Ok(defs) => {
12651280
let defs_json = serde_json::from_str(defs.as_str()).unwrap_or_else(|err| {
1266-
eprintln!("`{}` is not a valid JSON file: {}", path, err);
1281+
match path {
1282+
None => {
1283+
eprintln!("could not parse the value read from the standard input: {}", err);
1284+
}
1285+
Some(val) => {
1286+
eprintln!("`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", val, err);
1287+
}
1288+
}
12671289
process::exit(1)
12681290
});
12691291
client.import_definitions(defs_json)
12701292
}
12711293
Err(err) => {
1272-
eprintln!("`{}` could not be read: {}", path, err);
1294+
match path {
1295+
None => {
1296+
eprintln!(
1297+
"could not parse the value read from the standard input: {}",
1298+
err
1299+
);
1300+
}
1301+
Some(val) => {
1302+
eprintln!(
1303+
"`{}` does not exist, is not a readable file, or is not a valid JSON file: {}",
1304+
val, err
1305+
);
1306+
}
1307+
}
12731308
process::exit(1)
12741309
}
12751310
}
@@ -1282,43 +1317,79 @@ pub fn import_vhost_definitions(
12821317
) -> ClientResult<()> {
12831318
let path = command_args
12841319
.get_one::<String>("file")
1285-
.map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"'))
1286-
.unwrap();
1287-
let definitions = read_definitions(path);
1320+
.map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"'));
1321+
let use_stdin = command_args.get_one::<bool>("stdin").copied();
1322+
let definitions = read_definitions(path, use_stdin);
12881323
match definitions {
12891324
Ok(defs) => {
12901325
let defs_json = serde_json::from_str(defs.as_str()).unwrap_or_else(|err| {
1291-
eprintln!("`{}` is not a valid JSON file: {}", path, err);
1326+
match path {
1327+
None => {
1328+
eprintln!("could not parse the value read from the standard input: {}", err);
1329+
}
1330+
Some(val) => {
1331+
eprintln!("`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", val, err);
1332+
}
1333+
}
12921334
process::exit(1)
12931335
});
12941336
client.import_vhost_definitions(vhost, defs_json)
12951337
}
12961338
Err(err) => {
1297-
eprintln!("`{}` could not be read: {}", path, err);
1339+
match path {
1340+
None => {
1341+
eprintln!(
1342+
"could not parse the value read from the standard input: {}",
1343+
err
1344+
);
1345+
}
1346+
Some(val) => {
1347+
eprintln!(
1348+
"`{}` does not exist, is not a readable file, or is not a valid JSON file: {}",
1349+
val, err
1350+
);
1351+
}
1352+
}
12981353
process::exit(1)
12991354
}
13001355
}
13011356
}
13021357

1303-
fn read_definitions(path: &str) -> io::Result<String> {
1304-
match path {
1305-
"-" => {
1358+
fn read_definitions(path: Option<&str>, use_stdin: Option<bool>) -> io::Result<String> {
1359+
match (path, use_stdin) {
1360+
(_, Some(true)) => {
13061361
let mut buffer = String::new();
1307-
let stdin = io::stdin();
1308-
let lines = stdin.lines();
1309-
for ln in lines {
1310-
match ln {
1311-
Ok(line) => buffer.push_str(&line),
1312-
Err(err) => {
1313-
eprintln!("Error reading from standard input: {}", err);
1314-
process::exit(1);
1315-
}
1316-
}
1317-
}
1362+
read_stdin_lines(&mut buffer);
13181363

13191364
Ok(buffer)
13201365
}
1321-
_ => fs::read_to_string(path),
1366+
(Some(val), _) => match val {
1367+
"-" => {
1368+
let mut buffer = String::new();
1369+
read_stdin_lines(&mut buffer);
1370+
1371+
Ok(buffer)
1372+
}
1373+
_ => fs::read_to_string(val),
1374+
},
1375+
_ => Err(io::Error::new(
1376+
io::ErrorKind::InvalidInput,
1377+
"either an input file path or --stdin must be specified",
1378+
)),
1379+
}
1380+
}
1381+
1382+
fn read_stdin_lines(buffer: &mut String) {
1383+
let stdin = io::stdin();
1384+
let lines = stdin.lines();
1385+
for ln in lines {
1386+
match ln {
1387+
Ok(line) => buffer.push_str(&line),
1388+
Err(err) => {
1389+
eprintln!("Error reading from standard input: {}", err);
1390+
process::exit(1);
1391+
}
1392+
}
13221393
}
13231394
}
13241395

0 commit comments

Comments
 (0)