Skip to content

Commit

Permalink
Add json schema generation tool (#59)
Browse files Browse the repository at this point in the history
Add a cli that generates image customizer API json schema. 
The generated schema file can be leveraged by other tools for API
dependency and config validation.

Here's an example using it in VSCode for syntax validation:

![image](https://github.com/user-attachments/assets/9d6e2457-c934-446a-98c0-9c487e84c154)


### **Checklist**
- [x] Tests added/updated
- [ ] Documentation updated (if needed)
- [x] Code conforms to style guidelines

---------

Signed-off-by: Roaa Sakr <[email protected]>
  • Loading branch information
romoh authored Jan 8, 2025
1 parent 0bc50e3 commit e625439
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 2 deletions.
5 changes: 5 additions & 0 deletions toolkit/tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/fatih/color v1.16.0
github.com/gdamore/tcell v1.4.0
github.com/google/uuid v1.6.0
github.com/invopop/jsonschema v0.13.0
github.com/klauspost/pgzip v1.2.5
github.com/moby/sys/mountinfo v0.6.2
github.com/muesli/crunchy v0.4.0
Expand All @@ -33,18 +34,22 @@ require (
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/klauspost/compress v1.10.5 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.7 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.1.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xrash/smetrics v0.0.0-20170218160415-a3153f7040e9 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
Expand Down
11 changes: 11 additions & 0 deletions toolkit/tools/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2c
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bendahl/uinput v1.4.0 h1:aVJhayM1wEv7yXXLvC/fbXMmA1uB+jAspKhXQaV+76U=
github.com/bendahl/uinput v1.4.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -35,6 +39,9 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc=
github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
Expand All @@ -48,6 +55,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand Down Expand Up @@ -79,6 +88,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xrash/smetrics v0.0.0-20170218160415-a3153f7040e9 h1:w8V9v0qVympSF6GjdjIyeqR7+EVhAF9CBQmkmW7Zw0w=
github.com/xrash/smetrics v0.0.0-20170218160415-a3153f7040e9/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
Expand Down
8 changes: 8 additions & 0 deletions toolkit/tools/imagecustomizerapi/disksize.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"regexp"
"strconv"

"github.com/invopop/jsonschema"
"github.com/microsoft/azurelinux/toolkit/tools/imagegen/diskutils"
"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -51,6 +52,13 @@ func (s *DiskSize) UnmarshalJSON(data []byte) error {
return parseAndSetDiskSize(stringValue, s)
}

func (DiskSize) JSONSchema() *jsonschema.Schema {
return &jsonschema.Schema{
Type: "string",
Pattern: `^\d+[KMGT]$`,
}
}

func parseAndSetDiskSize(stringValue string, s *DiskSize) error {
diskSize, err := parseDiskSize(stringValue)
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions toolkit/tools/imagecustomizerapi/filepermissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"strconv"

"github.com/invopop/jsonschema"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -46,3 +47,10 @@ func (p *FilePermissions) UnmarshalYAML(value *yaml.Node) error {
*p = (FilePermissions)(fileModeUint)
return nil
}

func (FilePermissions) JSONSchema() *jsonschema.Schema {
return &jsonschema.Schema{
Type: "string",
Pattern: "^[0-7]{3,4}$",
}
}
8 changes: 8 additions & 0 deletions toolkit/tools/imagecustomizerapi/partitionsize.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"

"github.com/invopop/jsonschema"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -66,6 +67,13 @@ func (s *PartitionSize) UnmarshalJSON(data []byte) error {
return parseAndSetPartitionSize(stringValue, s)
}

func (PartitionSize) JSONSchema() *jsonschema.Schema {
return &jsonschema.Schema{
Type: "string",
Pattern: `^(\d+[KMGT]$|grow)$`,
}
}

func parseAndSetPartitionSize(stringValue string, s *PartitionSize) error {
switch stringValue {
case PartitionSizeGrow:
Expand Down
2 changes: 1 addition & 1 deletion toolkit/tools/imagecustomizerapi/uki.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

type Uki struct {
Kernels UkiKernels `yaml:"kernels"`
Kernels UkiKernels `yaml:"kernels" json:"kernels"`
}

func (u *Uki) IsValid() error {
Expand Down
21 changes: 20 additions & 1 deletion toolkit/tools/imagecustomizerapi/ukikernels.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package imagecustomizerapi

import (
"fmt"
"gopkg.in/yaml.v3"
"regexp"

"github.com/invopop/jsonschema"
"gopkg.in/yaml.v3"
)

type UkiKernels struct {
Expand Down Expand Up @@ -42,6 +44,23 @@ func (u *UkiKernels) UnmarshalYAML(value *yaml.Node) error {
}
}

func (UkiKernels) JSONSchema() *jsonschema.Schema {
return &jsonschema.Schema{
OneOf: []*jsonschema.Schema{
{
Type: "string",
Enum: []interface{}{"auto"},
},
{
Type: "array",
Items: &jsonschema.Schema{
Type: "string",
},
},
},
}
}

func (u *UkiKernels) IsValid() error {
if u.Auto && len(u.Kernels) > 0 {
return fmt.Errorf("'auto' cannot coexist with a list of kernel names")
Expand Down
13 changes: 13 additions & 0 deletions toolkit/tools/imagecustomizerschemacli/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

BINARY_NAME := bin/imagecustomizerschemacli
SCHEMA_FILE := schema.json

.PHONY: all
all: run

.PHONY: run
run:
@echo "Generating $(SCHEMA_FILE)"
go run . -o $(SCHEMA_FILE)
47 changes: 47 additions & 0 deletions toolkit/tools/imagecustomizerschemacli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package main

import (
"encoding/json"
"fmt"
"log"
"os"

"github.com/invopop/jsonschema"
"github.com/microsoft/azurelinux/toolkit/tools/imagecustomizerapi"
"gopkg.in/alecthomas/kingpin.v2"
)

func main() {
app := kingpin.New("jsonschemacli", "A CLI tool to generate JSON schema for the image customizer API.")
outputFile := app.Flag("output", "Path to the output JSON schema file").Short('o').Required().String()

kingpin.MustParse(app.Parse(os.Args[1:]))

if err := generateJSONSchema(*outputFile); err != nil {
log.Fatalf("Error: %v", err)
}

fmt.Printf("JSON schema has been written to %s\n", *outputFile)
}

func generateJSONSchema(outputFile string) error {
reflector := jsonschema.Reflector{
RequiredFromJSONSchemaTags: true,
}

schema := reflector.Reflect(&imagecustomizerapi.Config{})
schemaJSON, err := json.MarshalIndent(schema, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal schema: %w", err)
}

// Write schema to file
if err := os.WriteFile(outputFile, schemaJSON, 0o644); err != nil {
return fmt.Errorf("failed to write schema to file: %w", err)
}

return nil
}

0 comments on commit e625439

Please sign in to comment.