Introduction¶
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:
data
1
{2
"/": {3
"storage": {4
"type": "disk",5
"device": "/dev/sda1"6
},7
"fstype": "btrfs",8
"readonly": true9
},10
"/var": {11
"storage": {12
"type": "disk",13
"label": "8f3ba6f4-5c70-46ec-83af-0d5434953e5f"14
},15
"fstype": "ext4",16
"options": [ "nosuid" ]17
},18
"/tmp": {19
"storage": {20
"type": "tmpfs",21
"sizeInMB": 6422
}23
},24
"/var/www": {25
"storage": {26
"type": "nfs",27
"server": "my.nfs.server",28
"remotePath": "/exports/mypath"29
}30
}31
}Creating the fstab
schema¶
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
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.
- The
additionalProperties
validation keyword.
- The value here is
false
to constrain object properties to be either /
or to match the regular expression.
You will notice that the regular expression is explicitly anchored (with ^
and $
): in JSON Schema, regular expressions (in patternProperties
and in pattern
) are not anchored by default.
schema
1
{2
"$id": "https://example.com/fstab",3
"$schema": "https://json-schema.org/draft/2020-12/schema",4
"type": "object",5
"required": [ "/" ],6
"properties": {7
"/": {}8
},9
"patternProperties": {10
"^(/[^/]+)+$": {}11
},12
"additionalProperties": false13
}Starting the entry
schema¶
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 (
#/...
).
- The
$defs
keyword.
- Including several key names which we will define later.
schema
1
{2
"$id": "https://example.com/entry-schema",3
"$schema": "https://json-schema.org/draft/2020-12/schema",4
"description": "JSON Schema for an fstab entry",5
"type": "object",6
"required": [ "storage" ],7
"properties": {8
"storage": {9
"type": "object",10
"oneOf": [11
{ "$ref": "#/$defs/diskDevice" },12
{ "$ref": "#/$defs/diskUUID" },13
{ "$ref": "#/$defs/nfs" },14
{ "$ref": "#/$defs/tmpfs" }15
]16
}17
},18
"$defs": {19
"diskDevice": {},20
"diskUUID": {},21
"nfs": {},22
"tmpfs": {}23
}24
}Constraining an entry¶
Let's now extend this skeleton to add constraints to some of the properties.
- Our
fstype
key uses the enum
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.
- We have a
readonly
key.
With these added constraints, the schema now looks like this:
schema
1
{2
"$id": "https://example.com/entry-schema",3
"$schema": "https://json-schema.org/draft/2020-12/schema",4
"description": "JSON Schema for an fstab entry",5
"type": "object",6
"required": [ "storage" ],7
"properties": {8
"storage": {9
"type": "object",10
"oneOf": [11
{ "$ref": "#/$defs/diskDevice" },12
{ "$ref": "#/$defs/diskUUID" },13
{ "$ref": "#/$defs/nfs" },14
{ "$ref": "#/$defs/tmpfs" }15
]16
},17
"fstype": {18
"enum": [ "ext3", "ext4", "btrfs" ]19
},20
"options": {21
"type": "array",22
"minItems": 1,23
"items": {24
"type": "string"25
},26
"uniqueItems": true27
},28
"readonly": {29
"type": "boolean"30
}31
},32
"$defs": {33
"diskDevice": {},34
"diskUUID": {},35
"nfs": {},36
"tmpfs": {}37
}38
}The diskDevice
definition¶
One new keyword is introduced here:
- The
pattern
validation keyword notes the device
key must be an absolute path starting with /dev.
data
1
{2
"diskDevice": {3
"properties": {4
"type": {5
"enum": [ "disk" ]6
},7
"device": {8
"type": "string",9
"pattern": "^/dev/[^/]+(/[^/]+)*$"10
}11
},12
"required": [ "type", "device" ],13
"additionalProperties": false14
}15
}The diskUUID
definition¶
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.
data
1
{2
"diskUUID": {3
"properties": {4
"type": {5
"enum": [ "disk" ]6
},7
"label": {8
"type": "string",9
"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}$"10
}11
},12
"required": [ "type", "label" ],13
"additionalProperties": false14
}15
}The nfs
definition¶
We find another new keyword:
- The
format
annotation and assertion keyword.
data
1
{2
"nfs": {3
"properties": {4
"type": { "enum": [ "nfs" ] },5
"remotePath": {6
"type": "string",7
"pattern": "^(/[^/]+)+$"8
},9
"server": {10
"type": "string",11
"oneOf": [12
{ "format": "hostname" },13
{ "format": "ipv4" },14
{ "format": "ipv6" }15
]16
}17
},18
"required": [ "type", "server", "remotePath" ],19
"additionalProperties": false20
}21
}The tmpfs
definition¶
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.
data
1
{2
"tmpfs": {3
"properties": {4
"type": { "enum": [ "tmpfs" ] },5
"sizeInMB": {6
"type": "integer",7
"minimum": 16,8
"maximum": 5129
}10
},11
"required": [ "type", "sizeInMB" ],12
"additionalProperties": false13
}14
}The full entry schema¶
The resulting schema is quite large:
schema
1
{2
"$id": "https://example.com/entry-schema",3
"$schema": "https://json-schema.org/draft/2020-12/schema",4
"description": "JSON Schema for an fstab entry",5
"type": "object",6
"required": [ "storage" ],7
"properties": {8
"storage": {9
"type": "object",10
"oneOf": [11
{ "$ref": "#/$defs/diskDevice" },12
{ "$ref": "#/$defs/diskUUID" },13
{ "$ref": "#/$defs/nfs" },14
{ "$ref": "#/$defs/tmpfs" }15
]16
},17
"fstype": {18
"enum": [ "ext3", "ext4", "btrfs" ]19
},20
"options": {21
"type": "array",22
"minItems": 1,23
"items": {24
"type": "string"25
},26
"uniqueItems": true27
},28
"readonly": {29
"type": "boolean"30
}31
},32
"$defs": {33
"diskDevice": {34
"properties": {35
"type": {36
"enum": [ "disk" ]37
},38
"device": {39
"type": "string",40
"pattern": "^/dev/[^/]+(/[^/]+)*$"41
}42
},43
"required": [ "type", "device" ],44
"additionalProperties": false45
},46
"diskUUID": {47
"properties": {48
"type": {49
"enum": [ "disk" ]50
},51
"label": {52
"type": "string",53
"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}$"54
}55
},56
"required": [ "type", "label" ],57
"additionalProperties": false58
},59
"nfs": {60
"properties": {61
"type": { "enum": [ "nfs" ] },62
"remotePath": {63
"type": "string",64
"pattern": "^(/[^/]+)+$"65
},66
"server": {67
"type": "string",68
"oneOf": [69
{ "format": "hostname" },70
{ "format": "ipv4" },71
{ "format": "ipv6" }72
]73
}74
},75
"required": [ "type", "server", "remotePath" ],76
"additionalProperties": false77
},78
"tmpfs": {79
"properties": {80
"type": { "enum": [ "tmpfs" ] },81
"sizeInMB": {82
"type": "integer",83
"minimum": 16,84
"maximum": 51285
}86
},87
"required": [ "type", "sizeInMB" ],88
"additionalProperties": false89
}90
}91
}Referencing the entry
schema in the fstab
schema¶
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.
schema
1
{2
"$id": "https://example.com/fstab",3
"$schema": "https://json-schema.org/draft/2020-12/schema",4
"type": "object",5
"required": [ "/" ],6
"properties": {7
"/": { "$ref": "https://example.com/entry-schema" }8
},9
"patternProperties": {10
"^(/[^/]+)+$": { "$ref": "https://example.com/entry-schema" }11
},12
"additionalProperties": false13
}