layout | title |
---|---|
page |
Modeling a file system with JSON Schema |
- Introduction
- Creating the
fstab
schema - Starting the
entry
schema - Constraining an entry
- The
diskDevice
definition - The
diskUUID
definition - The
nfs
definition - The
tmpfs
definition - The full entry schema
- Referencing the
entry
schema in thefstab
schema
Not all constraints to an fstab file can be modeled using JSON Schema alone; however, it can represent a good number of them and the exercise is useful to demonstrate how constraints work. The examples provided are illustrative of the JSON Schema concepts rather than a real, working schema for an fstab file.
This example shows a possible JSON Schema representation of file system mount points as represented in an /etc/fstab
file.
An entry in an fstab file can have many different forms; Here is an example:
{
"/": {
"storage": {
"type": "disk",
"device": "/dev/sda1"
},
"fstype": "btrfs",
"readonly": true
},
"/var": {
"storage": {
"type": "disk",
"label": "8f3ba6f4-5c70-46ec-83af-0d5434953e5f"
},
"fstype": "ext4",
"options": [ "nosuid" ]
},
"/tmp": {
"storage": {
"type": "tmpfs",
"sizeInMB": 64
}
},
"/var/www": {
"storage": {
"type": "nfs",
"server": "my.nfs.server",
"remotePath": "/exports/mypath"
}
}
}
We will start with a base JSON Schema expressing the following constraints:
- the list of entries is a JSON object;
- the member names (or property names) of this object must all be valid, absolute paths;
- there must be an entry for the root filesystem (ie,
/
).
Building out our JSON Schema from top to bottom:
- The
$id
keyword. - The
$schema
keyword. - The
type
validation keyword. - The
required
validation keyword. - The
properties
validation keyword.- The
/
key is empty now; We will fill it out later.
- The
- The
patternProperties
validation keyword.- This matches other property names via a regular expression. Note: it does not match
/
. - The
^(/[^/]+)+$
key is empty now; We will fill it out later.
- This matches other property names via a regular expression. Note: it does not match
- The
additionalProperties
validation keyword.- The value here is
false
to constrain object properties to be either/
or to match the regular expression.
- The value here is
You will notice that the regular expression is explicitly anchored (with
^
and$
): in JSON Schema, regular expressions (inpatternProperties
and inpattern
) are not anchored by default.
{
"$id": "https://example.com/fstab",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [ "/" ],
"properties": {
"/": {}
},
"patternProperties": {
"^(/[^/]+)+$": {}
},
"additionalProperties": false,
}
We will start with an outline of the JSON schema which adds new concepts to what we've already demonstrated.
We saw these keywords in the prior exercise: $id
, $schema
, type
, required
and properties
.
To this we add:
- The
description
annotation keyword. - The
oneOf
keyword. - The
$ref
keyword.- In this case, all references used are local to the schema using a relative fragment URI (
#/...
).
- In this case, all references used are local to the schema using a relative fragment URI (
- The
$defs
keyword.- Including several key names which we will define later.
{
"$id": "https://example.com/entry-schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "JSON Schema for an fstab entry",
"type": "object",
"required": [ "storage" ],
"properties": {
"storage": {
"type": "object",
"oneOf": [
{ "$ref": "#/$defs/diskDevice" },
{ "$ref": "#/$defs/diskUUID" },
{ "$ref": "#/$defs/nfs" },
{ "$ref": "#/$defs/tmpfs" }
]
}
},
"$defs": {
"diskDevice": {},
"diskUUID": {},
"nfs": {},
"tmpfs": {}
}
}
Let's now extend this skeleton to add constraints to some of the properties.
- Our
fstype
key uses theenum
validation keyword. - Our
options
key uses the following:- The
type
validation keyword (see above). - The
minItems
validation keyword. - The
items
validation keyword. - The
uniqueItems
validation keyword. - Together these say:
options
must be an array, and the items therein must be strings, there must be at least one item, and all items should be unique.
- The
- We have a
readonly
key.
With these added constraints, the schema now looks like this:
{
"$id": "https://example.com/entry-schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "JSON Schema for an fstab entry",
"type": "object",
"required": [ "storage" ],
"properties": {
"storage": {
"type": "object",
"oneOf": [
{ "$ref": "#/$defs/diskDevice" },
{ "$ref": "#/$defs/diskUUID" },
{ "$ref": "#/$defs/nfs" },
{ "$ref": "#/$defs/tmpfs" }
]
},
"fstype": {
"enum": [ "ext3", "ext4", "btrfs" ]
},
"options": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
},
"uniqueItems": true
},
"readonly": {
"type": "boolean"
}
},
"$defs": {
"diskDevice": {},
"diskUUID": {},
"nfs": {},
"tmpfs": {}
}
}
One new keyword is introduced here:
- The
pattern
validation keyword notes thedevice
key must be an absolute path starting with /dev.
{
"diskDevice": {
"properties": {
"type": {
"enum": [ "disk" ]
},
"device": {
"type": "string",
"pattern": "^/dev/[^/]+(/[^/]+)*$"
}
},
"required": [ "type", "device" ],
"additionalProperties": false
}
}
No new keywords are introduced here.
We do have a new key: label
and the pattern
validation keyword states it must be a valid UUID.
{
"diskUUID": {
"properties": {
"type": {
"enum": [ "disk" ]
},
"label": {
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
}
},
"required": [ "type", "label" ],
"additionalProperties": false
}
}
We find another new keyword:
- The
format
annotation and assertion keyword.
{
"nfs": {
"properties": {
"type": { "enum": [ "nfs" ] },
"remotePath": {
"type": "string",
"pattern": "^(/[^/]+)+$"
},
"server": {
"type": "string",
"oneOf": [
{ "format": "hostname" },
{ "format": "ipv4" },
{ "format": "ipv6" }
]
}
},
"required": [ "type", "server", "remotePath" ],
"additionalProperties": false
}
}
Our last definition introduces two new keywords:
- The
minimum
validation keyword. - The
maximum
validation keyword. - Together these require the size be between 16 and 512, inclusive.
{
"tmpfs": {
"properties": {
"type": { "enum": [ "tmpfs" ] },
"sizeInMB": {
"type": "integer",
"minimum": 16,
"maximum": 512
}
},
"required": [ "type", "sizeInMB" ],
"additionalProperties": false
}
}
The resulting schema is quite large:
{
"$id": "https://example.com/entry-schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "JSON Schema for an fstab entry",
"type": "object",
"required": [ "storage" ],
"properties": {
"storage": {
"type": "object",
"oneOf": [
{ "$ref": "#/$defs/diskDevice" },
{ "$ref": "#/$defs/diskUUID" },
{ "$ref": "#/$defs/nfs" },
{ "$ref": "#/$defs/tmpfs" }
]
},
"fstype": {
"enum": [ "ext3", "ext4", "btrfs" ]
},
"options": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
},
"uniqueItems": true
},
"readonly": {
"type": "boolean"
}
},
"$defs": {
"diskDevice": {
"properties": {
"type": {
"enum": [ "disk" ]
},
"device": {
"type": "string",
"pattern": "^/dev/[^/]+(/[^/]+)*$"
}
},
"required": [ "type", "device" ],
"additionalProperties": false
},
"diskUUID": {
"properties": {
"type": {
"enum": [ "disk" ]
},
"label": {
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
}
},
"required": [ "type", "label" ],
"additionalProperties": false
},
"nfs": {
"properties": {
"type": { "enum": [ "nfs" ] },
"remotePath": {
"type": "string",
"pattern": "^(/[^/]+)+$"
},
"server": {
"type": "string",
"oneOf": [
{ "format": "hostname" },
{ "format": "ipv4" },
{ "format": "ipv6" }
]
}
},
"required": [ "type", "server", "remotePath" ],
"additionalProperties": false
},
"tmpfs": {
"properties": {
"type": { "enum": [ "tmpfs" ] },
"sizeInMB": {
"type": "integer",
"minimum": 16,
"maximum": 512
}
},
"required": [ "type", "sizeInMB" ],
"additionalProperties": false
}
}
}
Coming full circle we use the $ref
keyword to add our entry schema into the keys left empty at the start of the exercise:
- The
/
key. - The
^(/[^/]+)+$
key.
{
"$id": "https://example.com/fstab",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [ "/" ],
"properties": {
"/": { "$ref": "https://example.com/entry-schema" }
},
"patternProperties": {
"^(/[^/]+)+$": { "$ref": "https://example.com/entry-schema" }
},
"additionalProperties": false
}