Skip to content

Commit

Permalink
Schema: add global schema (auto version selection)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkellerer committed Apr 17, 2023
1 parent 6aed2ad commit 91638dd
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 37 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ RESTIC_GEN=$(BUILD)restic-generator
RESTIC_DIR=$(BUILD)restic-
RESTIC_CMD=$(BUILD)restic-commands.json

CONTRIB_DIR=contrib
JSONSCHEMA_DIR=docs/static/jsonschema
CONFIG_REFERENCE_DIR=docs/content/configuration/reference

Expand Down Expand Up @@ -193,6 +194,10 @@ generate-jsonschema: build

mkdir -p $(JSONSCHEMA_DIR) || echo "$(JSONSCHEMA_DIR) exists"

SCHEMA_BASE_URL="/resticprofile/jsonschema" \
SCHEMA_REF_BASE_URL="." \
$(abspath $(BINARY)) generate --config-reference $(CONTRIB_DIR)/templates/config-schema.gojson > $(JSONSCHEMA_DIR)/config.json

$(abspath $(BINARY)) generate --json-schema v1 > $(JSONSCHEMA_DIR)/config-1.json
$(abspath $(BINARY)) generate --json-schema v2 > $(JSONSCHEMA_DIR)/config-2.json
$(abspath $(BINARY)) generate --json-schema --version 0.9 v1 > $(JSONSCHEMA_DIR)/config-1-restic-0-9.json
Expand Down
7 changes: 6 additions & 1 deletion commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"path/filepath"
"regexp"
"sort"
"strconv"
Expand Down Expand Up @@ -235,8 +236,12 @@ func generateConfigReference(output io.Writer, args []string) (err error) {
}

data := config.NewTemplateInfoData(resticVersion)
tpl := templates.New("config-reference", data.GetFuncs())
name := "config-reference"
if len(args) > 0 {
name = filepath.Base(args[0])
}

tpl := templates.New(name, data.GetFuncs())
if len(args) > 0 {
tpl, err = tpl.ParseFiles(args...)
} else {
Expand Down
15 changes: 15 additions & 0 deletions commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -295,6 +296,20 @@ func TestGenerateCommand(t *testing.T) {
assert.Contains(t, ref, "| **continue-on-error** |")
})

t.Run("--config-reference config-schema.gojson", func(t *testing.T) {
buffer.Reset()
assert.NoError(t, generateCommand(buffer, commandRequest{args: []string{"--config-reference", "contrib/templates/config-schema.gojson"}}))
ref := buffer.String()
assert.Contains(t, ref, `"$schema"`)
assert.Contains(t, ref, "/jsonschema/config-1.json")
assert.Contains(t, ref, "/jsonschema/config-2.json")

decoder := json.NewDecoder(strings.NewReader(ref))
content := make(map[string]any)
assert.NoError(t, decoder.Decode(&content))
assert.Contains(t, content, `$schema`)
})

t.Run("--json-schema", func(t *testing.T) {
buffer.Reset()
assert.NoError(t, generateCommand(buffer, commandRequest{args: []string{"--json-schema"}}))
Expand Down
2 changes: 1 addition & 1 deletion config/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Global struct {
DefaultCommand string `mapstructure:"default-command" default:"snapshots" description:"The restic or resticprofile command to use when no command was specified"`
Initialize bool `mapstructure:"initialize" default:"false" description:"Initialize a repository if missing"`
ResticBinary string `mapstructure:"restic-binary" description:"Full path of the restic executable (detected if not set)"`
ResticVersion string // not configurable at the moment. To be set after ResticBinary is known.
ResticVersion string `mapstructure:"restic-version" description:"Sets the restic version (detected if not set)"`
FilterResticFlags bool `mapstructure:"restic-arguments-filter" default:"true" description:"Remove unknown flags instead of passing all configured flags to restic"`
ResticLockRetryAfter time.Duration `mapstructure:"restic-lock-retry-after" default:"1m" description:"Time to wait before trying to get a lock on a restic repositoey - see https://creativeprojects.github.io/resticprofile/usage/locks/"`
ResticStaleLockAge time.Duration `mapstructure:"restic-stale-lock-age" default:"2h" description:"The age an unused lock on a restic repository must have at least before resiticprofile attempts to unlock - see https://creativeprojects.github.io/resticprofile/usage/locks/"`
Expand Down
10 changes: 7 additions & 3 deletions contrib/templates/config-reference.gomd
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ date: {{ .Now.Format "2006-01-02T15:04:05Z07:00" }}
{{- end }}

{{- $webBaseUrl := "https://creativeprojects.github.io/resticprofile" -}}
{{- if .Env.WEB_BASE_URL -}}
{{- $webBaseUrl = .Env.WEB_BASE_URL -}}
{{- end -}}
{{- $configWebUrl := "{base}/configuration" | replace "{base}" $webBaseUrl -}}

{{ define "printCell" -}}
Expand Down Expand Up @@ -344,7 +347,8 @@ JSON schema URLs for a specific *restic* version:
* `.../config-2-restic-{MAJOR}-{MINOR}.json`

Available URLs:
{{ range $v := .KnownResticVersions }}
* {{ $webBaseUrl }}/jsonschema/config-2-restic-{{ $v | replace "." "-" }}.json
* {{ $webBaseUrl }}/jsonschema/config-1-restic-{{ $v | replace "." "-" }}.json
{{ range $version := .KnownResticVersions }}
{{- $version = slice ($version | split ".") 0 2 | join "." -}}
* {{ $webBaseUrl }}/jsonschema/config-2-restic-{{ $version | replace "." "-" }}.json
* {{ $webBaseUrl }}/jsonschema/config-1-restic-{{ $version | replace "." "-" }}.json
{{- end }}
157 changes: 157 additions & 0 deletions contrib/templates/config-schema.gojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
{{- /* ------------------------------------------------------------------------------

Template that generates a global json schema that redirects to versioned URLs
depending on "version" and "restic-version" properties

Usage: resticprofile generate \
--config-reference contrib/templates/config-schema.gojson

------------------------------------------------------------------------------ */ -}}
{{- /*gotype: github.com/creativeprojects/resticprofile/config.TemplateInfoData*/ -}}
{{- $baseUrl := "https://creativeprojects.github.io/resticprofile/jsonschema" -}}
{{- if .Env.SCHEMA_BASE_URL -}}
{{- $baseUrl = .Env.SCHEMA_BASE_URL -}}
{{- end -}}
{{- $refBaseUrl := $baseUrl -}}
{{- if .Env.SCHEMA_REF_BASE_URL -}}
{{- $refBaseUrl = .Env.SCHEMA_REF_BASE_URL -}}
{{- end -}}
{
"$id": "{{ $baseUrl | js }}/config.json",
"$schema": "http://json-schema.org/draft-07/schema",
"$defs": {
"config-1": {
"oneOf": [
{
"properties": {
"version": {
"const": "1"
}
},
"required": [
"version"
]
},
{
"properties": {
"version": {
"type": "string",
"maxLength": 0
}
},
"required": [
"version"
]
},
{
"not": {
"properties": {
"version": {
}
},
"required": [
"version"
]
}
}
]
},
"config-2": {
"properties": {
"version": {
"const": "2"
}
},
"required": [
"version"
]
}
},
"oneOf": [
{{ define "schemaWithResticVersion" -}}
{{- $baseUrl := index . 0 -}}
{{- $config := index . 1 -}}
{{- $version := index . 2 -}}
{
"allOf": [
{
"$ref": "#/$defs/config-{{ $config | js }}"
},
{
"properties": {
"global": {
"type": "object",
"properties": {
"restic-version": {
"type": "string",
"pattern": "{{ $version | replace "." "\\." | js }}.*"
}
},
"required": [
"restic-version"
]
}
},
"required": [
"global"
]
},
{
"$ref": "{{ $baseUrl | js }}/config-{{ $config | js }}-restic-{{ $version | replace "." "-" | js }}.json"
}
]
},
{{ end -}}
{{- /* restic major version ( assuming it is "0.$major", may need to change when restic reaches v1 ) */ -}}
{{ range $version := .KnownResticVersions -}}
{{- $version = slice ($version | split ".") 0 2 | join "." -}}
{{- template "schemaWithResticVersion" (list $refBaseUrl "2" $version) -}}
{{- template "schemaWithResticVersion" (list $refBaseUrl "1" $version) -}}
{{- end }}
{{- /* no restic version */ -}}
{
"allOf": [
{
"$ref": "#/$defs/config-2"
},
{{- block "noResticVersion" . }}
{
"not": {
"properties": {
"global": {
"type": "object",
"properties": {
"restic-version": {
}
},
"required": [
"restic-version"
]
}
},
"required": [
"global"
]
}
},
{{ end }}
{
"$ref": "{{ $refBaseUrl | js }}/config-2.json"
}
]
},
{
"allOf": [
{
"$ref": "#/$defs/config-1"
},
{{- template "noResticVersion" -}}
{
"$ref": "{{ $refBaseUrl | js }}/config-1.json"
}
]
}
],
"title": "resticprofile configuration",
"type": "object"
}
64 changes: 37 additions & 27 deletions docs/content/configuration/jsonschema/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,28 @@ weight: 55
JSON, YAML and TOML configuration files can benefit from a JSON schema that describes the
config structure depending on the selected *restic* and configuration file version.

## Schema URLs
## Schema URL

JSON schema URLs for **any** *restic* version:

* Config V1: {{< absolute "jsonschema/config-1.json" >}}
* Config V2: {{< absolute "jsonschema/config-2.json" >}}

These universal schemas contain all flags and commands of all known *restic* versions and
may allow to set flags that are not supported by a particular *restic* version. Descriptions
and deprecation markers indicate what is supported and what should no longer be used.

JSON schema URLs for a **specific** *restic* version (list of [available URLs]({{< ref "reference/#json-schema" >}})):
**{{< absolute "jsonschema/config.json" >}}**

* `.../config-1-restic-{MAJOR}-{MINOR}.json`
* `.../config-2-restic-{MAJOR}-{MINOR}.json`

These schemas contain only flags and commands of a specific *restic* version. The schema will
validate a config only when flags are used that the selected *restic* version supports
according to its manual pages.

{{% notice style="hint" %}}
As an alternative to URLs, schemas can be generated locally with:
`resticprofile generate --json-schema [--version RESTIC_VERSION] [v2|v1]`
{{% /notice %}}
This schema detects config and restic version and redirects to the corresponding [versioned URL](#versioned-urls).

## Usage (vscode)

To use a schema with **vscode**, begin your config files with:
To use a schema with **vscode**, begin your config files with:

{{< tabs groupId="config-with-json" >}}
{{% tab name="toml" %}}
``````toml
#:schema {{< absolute "jsonschema/config-2.json" nohtml >}}
#:schema {{< absolute "jsonschema/config.json" nohtml >}}

version = "2"

``````
{{% /tab %}}
{{% tab name="yaml" %}}
``````yaml
# yaml-language-server: $schema={{< absolute "jsonschema/config-2.json" nohtml >}}
# yaml-language-server: $schema={{< absolute "jsonschema/config.json" nohtml >}}

version: "2"

Expand All @@ -57,7 +38,7 @@ version: "2"
{{% tab name="json" %}}
``````json
{
"$schema": "{{< absolute "jsonschema/config-2.json" nohtml >}}",
"$schema": "{{< absolute "jsonschema/config.json" nohtml >}}",
"version": "2"
}
``````
Expand All @@ -72,4 +53,33 @@ YAML & TOML validation with JSON schema is not supported out of the box. Additio

{{< figure src="/recordings/jsonschema-vsc.gif" >}}

**Extension**: `redhat.vscode-yaml`
**Extension**: `redhat.vscode-yaml`

## Versioned URLs

The following versioned schemas are referenced from `config.json`. Which URL is selected depends
on config and restic version. You may use the URLs directly if you need a self-contained schema
file or to enforce a certain version of configuration format and/or restic flags

JSON schema URLs for **any** *restic* version:

* Config V1: {{< absolute "jsonschema/config-1.json" >}}
* Config V2: {{< absolute "jsonschema/config-2.json" >}}

These universal schemas contain all flags and commands of all known *restic* versions and
may allow to set flags that are not supported by a particular *restic* version. Descriptions
and deprecation markers indicate what is supported and what should no longer be used.

JSON schema URLs for a **specific** *restic* version (list of [available URLs]({{< ref "reference/#json-schema" >}})):

* `.../config-1-restic-{MAJOR}-{MINOR}.json`
* `.../config-2-restic-{MAJOR}-{MINOR}.json`

These schemas contain only flags and commands of a specific *restic* version. The schema will
validate a config only when flags are used that the selected *restic* version supports
according to its manual pages.

{{% notice style="hint" %}}
As an alternative to URLs, schemas can be generated locally with:
`resticprofile generate --json-schema [--version RESTIC_VERSION] [v2|v1]`
{{% /notice %}}
16 changes: 11 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,19 @@ func main() {
}

// detect restic version
if len(global.ResticVersion) == 0 {
if global.ResticVersion, err = restic.GetVersion(resticBinary); err != nil {
clog.Warningf("assuming restic is at latest known version ; %s", err.Error())
global.ResticVersion = restic.AnyVersion
{
detected := ""
if len(global.ResticVersion) == 0 {
if global.ResticVersion, err = restic.GetVersion(resticBinary); err == nil {
detected = " (detected)"
} else {
clog.Warningf("assuming restic is at latest known version ; %s", err.Error())
global.ResticVersion = restic.AnyVersion
detected = "unknown version (any)"
}
}
clog.Debugf("restic %s%s", global.ResticVersion, detected)
}
clog.Debugf("restic %s", global.ResticVersion)

if c.HasProfile(flags.name) {
// if running as a systemd timer
Expand Down

0 comments on commit 91638dd

Please sign in to comment.