JSON output support for zfs list
This commit adds support for JSON output for zfs list using '-j' option. Information is collected in JSON format which is later printed in jSON format. Existing options for zfs list also work with '-j'. man pages are updated with relevant information. Reviewed-by: Tony Hutter <hutter2@llnl.gov> Reviewed-by: Ameer Hamza <ahamza@ixsystems.com> Signed-off-by: Umer Saleem <usaleem@ixsystems.com> Closes #16217
This commit is contained in:
parent
aa15b60e58
commit
443abfc71d
|
@ -310,8 +310,9 @@ get_usage(zfs_help_t idx)
|
|||
return (gettext("\tupgrade [-v]\n"
|
||||
"\tupgrade [-r] [-V version] <-a | filesystem ...>\n"));
|
||||
case HELP_LIST:
|
||||
return (gettext("\tlist [-Hp] [-r|-d max] [-o property[,...]] "
|
||||
"[-s property]...\n\t [-S property]... [-t type[,...]] "
|
||||
return (gettext("\tlist [-Hp] [-j [--json-int]] [-r|-d max] "
|
||||
"[-o property[,...]] [-s property]...\n\t "
|
||||
"[-S property]... [-t type[,...]] "
|
||||
"[filesystem|volume|snapshot] ...\n"));
|
||||
case HELP_MOUNT:
|
||||
return (gettext("\tmount\n"
|
||||
|
@ -3609,6 +3610,9 @@ typedef struct list_cbdata {
|
|||
boolean_t cb_literal;
|
||||
boolean_t cb_scripted;
|
||||
zprop_list_t *cb_proplist;
|
||||
boolean_t cb_json;
|
||||
nvlist_t *cb_jsobj;
|
||||
boolean_t cb_json_as_int;
|
||||
} list_cbdata_t;
|
||||
|
||||
/*
|
||||
|
@ -3679,10 +3683,11 @@ zfs_list_avail_color(zfs_handle_t *zhp)
|
|||
|
||||
/*
|
||||
* Given a dataset and a list of fields, print out all the properties according
|
||||
* to the described layout.
|
||||
* to the described layout, or return an nvlist containing all the fields, later
|
||||
* to be printed out as JSON object.
|
||||
*/
|
||||
static void
|
||||
print_dataset(zfs_handle_t *zhp, list_cbdata_t *cb)
|
||||
collect_dataset(zfs_handle_t *zhp, list_cbdata_t *cb)
|
||||
{
|
||||
zprop_list_t *pl = cb->cb_proplist;
|
||||
boolean_t first = B_TRUE;
|
||||
|
@ -3691,9 +3696,23 @@ print_dataset(zfs_handle_t *zhp, list_cbdata_t *cb)
|
|||
nvlist_t *propval;
|
||||
const char *propstr;
|
||||
boolean_t right_justify;
|
||||
nvlist_t *item, *d, *props;
|
||||
item = d = props = NULL;
|
||||
zprop_source_t sourcetype = ZPROP_SRC_NONE;
|
||||
char source[ZFS_MAX_DATASET_NAME_LEN];
|
||||
if (cb->cb_json) {
|
||||
d = fnvlist_lookup_nvlist(cb->cb_jsobj, "datasets");
|
||||
if (d == NULL) {
|
||||
fprintf(stderr, "datasets obj not found.\n");
|
||||
exit(1);
|
||||
}
|
||||
item = fnvlist_alloc();
|
||||
props = fnvlist_alloc();
|
||||
fill_dataset_info(item, zhp, cb->cb_json_as_int);
|
||||
}
|
||||
|
||||
for (; pl != NULL; pl = pl->pl_next) {
|
||||
if (!first) {
|
||||
if (!cb->cb_json && !first) {
|
||||
if (cb->cb_scripted)
|
||||
(void) putchar('\t');
|
||||
else
|
||||
|
@ -3709,69 +3728,112 @@ print_dataset(zfs_handle_t *zhp, list_cbdata_t *cb)
|
|||
right_justify = zfs_prop_align_right(pl->pl_prop);
|
||||
} else if (pl->pl_prop != ZPROP_USERPROP) {
|
||||
if (zfs_prop_get(zhp, pl->pl_prop, property,
|
||||
sizeof (property), NULL, NULL, 0,
|
||||
cb->cb_literal) != 0)
|
||||
sizeof (property), &sourcetype, source,
|
||||
sizeof (source), cb->cb_literal) != 0)
|
||||
propstr = "-";
|
||||
else
|
||||
propstr = property;
|
||||
right_justify = zfs_prop_align_right(pl->pl_prop);
|
||||
} else if (zfs_prop_userquota(pl->pl_user_prop)) {
|
||||
sourcetype = ZPROP_SRC_LOCAL;
|
||||
if (zfs_prop_get_userquota(zhp, pl->pl_user_prop,
|
||||
property, sizeof (property), cb->cb_literal) != 0)
|
||||
property, sizeof (property), cb->cb_literal) != 0) {
|
||||
sourcetype = ZPROP_SRC_NONE;
|
||||
propstr = "-";
|
||||
else
|
||||
} else {
|
||||
propstr = property;
|
||||
}
|
||||
right_justify = B_TRUE;
|
||||
} else if (zfs_prop_written(pl->pl_user_prop)) {
|
||||
sourcetype = ZPROP_SRC_LOCAL;
|
||||
if (zfs_prop_get_written(zhp, pl->pl_user_prop,
|
||||
property, sizeof (property), cb->cb_literal) != 0)
|
||||
property, sizeof (property), cb->cb_literal) != 0) {
|
||||
sourcetype = ZPROP_SRC_NONE;
|
||||
propstr = "-";
|
||||
else
|
||||
} else {
|
||||
propstr = property;
|
||||
}
|
||||
right_justify = B_TRUE;
|
||||
} else {
|
||||
if (nvlist_lookup_nvlist(userprops,
|
||||
pl->pl_user_prop, &propval) != 0)
|
||||
pl->pl_user_prop, &propval) != 0) {
|
||||
propstr = "-";
|
||||
else
|
||||
} else {
|
||||
propstr = fnvlist_lookup_string(propval,
|
||||
ZPROP_VALUE);
|
||||
strlcpy(source,
|
||||
fnvlist_lookup_string(propval,
|
||||
ZPROP_SOURCE), ZFS_MAX_DATASET_NAME_LEN);
|
||||
if (strcmp(source,
|
||||
zfs_get_name(zhp)) == 0) {
|
||||
sourcetype = ZPROP_SRC_LOCAL;
|
||||
} else if (strcmp(source,
|
||||
ZPROP_SOURCE_VAL_RECVD) == 0) {
|
||||
sourcetype = ZPROP_SRC_RECEIVED;
|
||||
} else {
|
||||
sourcetype = ZPROP_SRC_INHERITED;
|
||||
}
|
||||
}
|
||||
right_justify = B_FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* zfs_list_avail_color() needs ZFS_PROP_AVAILABLE + USED
|
||||
* - so we need another for() search for the USED part
|
||||
* - when no colors wanted, we can skip the whole thing
|
||||
*/
|
||||
if (use_color() && pl->pl_prop == ZFS_PROP_AVAILABLE) {
|
||||
zprop_list_t *pl2 = cb->cb_proplist;
|
||||
for (; pl2 != NULL; pl2 = pl2->pl_next) {
|
||||
if (pl2->pl_prop == ZFS_PROP_USED) {
|
||||
color_start(zfs_list_avail_color(zhp));
|
||||
/* found it, no need for more loops */
|
||||
break;
|
||||
if (cb->cb_json) {
|
||||
if (pl->pl_prop == ZFS_PROP_NAME)
|
||||
continue;
|
||||
if (zprop_nvlist_one_property(
|
||||
zfs_prop_to_name(pl->pl_prop), propstr,
|
||||
sourcetype, source, NULL, props,
|
||||
cb->cb_json_as_int) != 0)
|
||||
nomem();
|
||||
} else {
|
||||
/*
|
||||
* zfs_list_avail_color() needs
|
||||
* ZFS_PROP_AVAILABLE + USED, so we need another
|
||||
* for() search for the USED part when no colors
|
||||
* wanted, we can skip the whole thing
|
||||
*/
|
||||
if (use_color() && pl->pl_prop == ZFS_PROP_AVAILABLE) {
|
||||
zprop_list_t *pl2 = cb->cb_proplist;
|
||||
for (; pl2 != NULL; pl2 = pl2->pl_next) {
|
||||
if (pl2->pl_prop == ZFS_PROP_USED) {
|
||||
color_start(
|
||||
zfs_list_avail_color(zhp));
|
||||
/*
|
||||
* found it, no need for more
|
||||
* loops
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is being called in scripted mode, or if
|
||||
* this is the last column and it is left-justified,
|
||||
* don't include a width format specifier.
|
||||
*/
|
||||
if (cb->cb_scripted || (pl->pl_next == NULL &&
|
||||
!right_justify))
|
||||
(void) fputs(propstr, stdout);
|
||||
else if (right_justify) {
|
||||
(void) printf("%*s", (int)pl->pl_width,
|
||||
propstr);
|
||||
} else {
|
||||
(void) printf("%-*s", (int)pl->pl_width,
|
||||
propstr);
|
||||
}
|
||||
|
||||
if (pl->pl_prop == ZFS_PROP_AVAILABLE)
|
||||
color_end();
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is being called in scripted mode, or if this is the
|
||||
* last column and it is left-justified, don't include a width
|
||||
* format specifier.
|
||||
*/
|
||||
if (cb->cb_scripted || (pl->pl_next == NULL && !right_justify))
|
||||
(void) fputs(propstr, stdout);
|
||||
else if (right_justify)
|
||||
(void) printf("%*s", (int)pl->pl_width, propstr);
|
||||
else
|
||||
(void) printf("%-*s", (int)pl->pl_width, propstr);
|
||||
|
||||
if (pl->pl_prop == ZFS_PROP_AVAILABLE)
|
||||
color_end();
|
||||
}
|
||||
|
||||
(void) putchar('\n');
|
||||
if (cb->cb_json) {
|
||||
fnvlist_add_nvlist(item, "properties", props);
|
||||
fnvlist_add_nvlist(d, zfs_get_name(zhp), item);
|
||||
fnvlist_free(props);
|
||||
fnvlist_free(item);
|
||||
} else
|
||||
(void) putchar('\n');
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -3783,12 +3845,12 @@ list_callback(zfs_handle_t *zhp, void *data)
|
|||
list_cbdata_t *cbp = data;
|
||||
|
||||
if (cbp->cb_first) {
|
||||
if (!cbp->cb_scripted)
|
||||
if (!cbp->cb_scripted && !cbp->cb_json)
|
||||
print_header(cbp);
|
||||
cbp->cb_first = B_FALSE;
|
||||
}
|
||||
|
||||
print_dataset(zhp, cbp);
|
||||
collect_dataset(zhp, cbp);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
@ -3807,9 +3869,16 @@ zfs_do_list(int argc, char **argv)
|
|||
int ret = 0;
|
||||
zfs_sort_column_t *sortcol = NULL;
|
||||
int flags = ZFS_ITER_PROP_LISTSNAPS | ZFS_ITER_ARGS_CAN_BE_PATHS;
|
||||
nvlist_t *data = NULL;
|
||||
|
||||
struct option long_options[] = {
|
||||
{"json-int", no_argument, NULL, ZFS_OPTION_JSON_NUMS_AS_INT},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
/* check options */
|
||||
while ((c = getopt(argc, argv, "HS:d:o:prs:t:")) != -1) {
|
||||
while ((c = getopt_long(argc, argv, "jHS:d:o:prs:t:", long_options,
|
||||
NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'o':
|
||||
fields = optarg;
|
||||
|
@ -3824,6 +3893,17 @@ zfs_do_list(int argc, char **argv)
|
|||
case 'r':
|
||||
flags |= ZFS_ITER_RECURSE;
|
||||
break;
|
||||
case 'j':
|
||||
cb.cb_json = B_TRUE;
|
||||
cb.cb_jsobj = zfs_json_schema(0, 1);
|
||||
data = fnvlist_alloc();
|
||||
fnvlist_add_nvlist(cb.cb_jsobj, "datasets", data);
|
||||
fnvlist_free(data);
|
||||
break;
|
||||
case ZFS_OPTION_JSON_NUMS_AS_INT:
|
||||
cb.cb_json_as_int = B_TRUE;
|
||||
cb.cb_literal = B_TRUE;
|
||||
break;
|
||||
case 'H':
|
||||
cb.cb_scripted = B_TRUE;
|
||||
break;
|
||||
|
@ -3897,6 +3977,12 @@ found3:;
|
|||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (!cb.cb_json && cb.cb_json_as_int) {
|
||||
(void) fprintf(stderr, gettext("'--json-int' only works with"
|
||||
" '-j' option\n"));
|
||||
usage(B_FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* If "-o space" and no types were specified, don't display snapshots.
|
||||
*/
|
||||
|
@ -3936,6 +4022,11 @@ found3:;
|
|||
ret = zfs_for_each(argc, argv, flags, types, sortcol, &cb.cb_proplist,
|
||||
limit, list_callback, &cb);
|
||||
|
||||
if (ret == 0 && cb.cb_json)
|
||||
zcmd_print_json(cb.cb_jsobj);
|
||||
else if (ret != 0 && cb.cb_json)
|
||||
nvlist_free(cb.cb_jsobj);
|
||||
|
||||
zprop_free_list(cb.cb_proplist);
|
||||
zfs_free_sort_columns(sortcol);
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
.Cm list
|
||||
.Op Fl r Ns | Ns Fl d Ar depth
|
||||
.Op Fl Hp
|
||||
.Op Fl j Op Ar --json-int
|
||||
.Oo Fl o Ar property Ns Oo , Ns Ar property Oc Ns … Oc
|
||||
.Oo Fl s Ar property Oc Ns …
|
||||
.Oo Fl S Ar property Oc Ns …
|
||||
|
@ -70,6 +71,11 @@ The following fields are displayed:
|
|||
Used for scripting mode.
|
||||
Do not print headers and separate fields by a single tab instead of arbitrary
|
||||
white space.
|
||||
.It Fl j Op Ar --json-int
|
||||
Print the output in JSON format.
|
||||
Specify
|
||||
.Sy --json-int
|
||||
to print the numbers in integer format instead of strings in JSON output.
|
||||
.It Fl d Ar depth
|
||||
Recursively display any children of the dataset, limiting the recursion to
|
||||
.Ar depth .
|
||||
|
@ -186,6 +192,161 @@ pool/home 315K 457G 21K /export/home
|
|||
pool/home/anne 18K 457G 18K /export/home/anne
|
||||
pool/home/bob 276K 457G 276K /export/home/bob
|
||||
.Ed
|
||||
.Ss Example 2 : No Listing ZFS filesystems and snapshots in JSON format
|
||||
.Bd -literal -compact -offset Ds
|
||||
.No # Nm zfs Cm list Fl j Fl t Ar filesystem,snapshot | Cm jq
|
||||
{
|
||||
"output_version": {
|
||||
"command": "zfs list",
|
||||
"vers_major": 0,
|
||||
"vers_minor": 1
|
||||
},
|
||||
"datasets": {
|
||||
"pool": {
|
||||
"name": "pool",
|
||||
"type": "FILESYSTEM",
|
||||
"pool": "pool",
|
||||
"properties": {
|
||||
"used": {
|
||||
"value": "290K",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"available": {
|
||||
"value": "30.5G",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"referenced": {
|
||||
"value": "24K",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"mountpoint": {
|
||||
"value": "/pool",
|
||||
"source": {
|
||||
"type": "DEFAULT",
|
||||
"data": "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pool/home": {
|
||||
"name": "pool/home",
|
||||
"type": "FILESYSTEM",
|
||||
"pool": "pool",
|
||||
"properties": {
|
||||
"used": {
|
||||
"value": "48K",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"available": {
|
||||
"value": "30.5G",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"referenced": {
|
||||
"value": "24K",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"mountpoint": {
|
||||
"value": "/mnt/home",
|
||||
"source": {
|
||||
"type": "LOCAL",
|
||||
"data": "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pool/home/bob": {
|
||||
"name": "pool/home/bob",
|
||||
"type": "FILESYSTEM",
|
||||
"pool": "pool",
|
||||
"properties": {
|
||||
"used": {
|
||||
"value": "24K",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"available": {
|
||||
"value": "30.5G",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"referenced": {
|
||||
"value": "24K",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"mountpoint": {
|
||||
"value": "/mnt/home/bob",
|
||||
"source": {
|
||||
"type": "INHERITED",
|
||||
"data": "pool/home"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pool/home/bob@v1": {
|
||||
"name": "pool/home/bob@v1",
|
||||
"type": "SNAPSHOT",
|
||||
"pool": "pool",
|
||||
"dataset": "pool/home/bob",
|
||||
"snapshot_name": "v1",
|
||||
"properties": {
|
||||
"used": {
|
||||
"value": "0B",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"available": {
|
||||
"value": "-",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"referenced": {
|
||||
"value": "24K",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
},
|
||||
"mountpoint": {
|
||||
"value": "-",
|
||||
"source": {
|
||||
"type": "NONE",
|
||||
"data": "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.Ed
|
||||
.
|
||||
.Sh SEE ALSO
|
||||
.Xr zfsprops 7 ,
|
||||
|
|
Loading…
Reference in New Issue