Skip to content

Commit

Permalink
✨ Move schema validation to sdk to be used by different repos (#21)
Browse files Browse the repository at this point in the history
* Move schema validation to sdk to be used by different repos

Signed-off-by: Mauro Morales <mauro.morales@spectrocloud.com>

* remove unused

Signed-off-by: Mauro Morales <mauro.morales@spectrocloud.com>

* fix recommendations

Signed-off-by: Mauro Morales <mauro.morales@spectrocloud.com>

---------

Signed-off-by: Mauro Morales <mauro.morales@spectrocloud.com>
  • Loading branch information
mauromorales authored May 26, 2023
1 parent 3524a69 commit c90740d
Show file tree
Hide file tree
Showing 13 changed files with 992 additions and 50 deletions.
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ require (
github.com/itchyny/gojq v0.12.12
github.com/jaypipes/ghw v0.10.0
github.com/joho/godotenv v1.5.1
github.com/kairos-io/kairos/v2 v2.0.3
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5
github.com/mudler/yip v1.1.0
github.com/onsi/ginkgo/v2 v2.9.5
github.com/onsi/gomega v1.27.6
github.com/onsi/gomega v1.27.7
github.com/pterm/pterm v0.12.61
github.com/qeesung/image2ascii v1.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0
github.com/swaggest/jsonschema-go v0.3.51
github.com/twpayne/go-vfs/v4 v4.2.0
github.com/zcalusic/sysinfo v0.9.5
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
Expand Down Expand Up @@ -52,7 +55,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
github.com/gookit/color v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
Expand All @@ -64,12 +67,12 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/swaggest/refl v1.1.0 // indirect
github.com/twpayne/go-vfs v1.7.2 // indirect
github.com/vbatts/tar-split v0.11.3 // indirect
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
Expand Down
69 changes: 22 additions & 47 deletions go.sum

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions schema/install_schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package schema

import (
jsonschemago "github.com/swaggest/jsonschema-go"
)

// InstallSchema represents the install block in the Kairos configuration. It is used to drive automatic installations without user interaction.
type InstallSchema struct {
_ struct{} `title:"Kairos Schema: Install block" description:"The install block is to drive automatic installations without user interaction."`
Auto bool `json:"auto,omitempty" description:"Set to true when installing without Pairing"`
BindMounts []string `json:"bind_mounts,omitempty"`
Bundles []BundleSchema `json:"bundles,omitempty" description:"Add bundles in runtime"`
Device string `json:"device,omitempty" pattern:"^(auto|/|(/[a-zA-Z0-9_-]+)+)$" description:"Device for automated installs" examples:"[\"auto\",\"/dev/sda\"]"`
EphemeralMounts []string `json:"ephemeral_mounts,omitempty"`
EncryptedPartitions []string `json:"encrypted_partitions,omitempty"`
Env []interface{} `json:"env,omitempty"`
GrubOptionsSchema `json:"grub_options,omitempty"`
Image string `json:"image,omitempty" description:"Use a different container image for the installation"`
PowerManagement
SkipEncryptCopyPlugins bool `json:"skip_copy_kcrypt_plugin,omitempty"`
}

// BundleSchema represents the bundle block which can be used in different places of the Kairos configuration. It is used to reference a bundle and its confguration.
type BundleSchema struct {
DB string `json:"db_path,omitempty"`
LocalFile bool `json:"local_file,omitempty"`
Repository string `json:"repository,omitempty"`
Rootfs string `json:"rootfs_path,omitempty"`
Targets []string `json:"targets,omitempty"`
}

// GrubOptionsSchema represents the grub options block which can be used in different places of the Kairos configuration. It is used to configure grub.
type GrubOptionsSchema struct {
DefaultFallback string `json:"default_fallback,omitempty" description:"Sets default fallback logic"`
DefaultMenuEntry string `json:"default_menu_entry,omitempty" description:"Change GRUB menu entry"`
ExtraActiveCmdline string `json:"extra_active_cmdline,omitempty" description:"Additional Kernel option cmdline to apply just for active"`
ExtraCmdline string `json:"extra_cmdline,omitempty" description:"Additional Kernel option cmdline to apply"`
ExtraPassiveCmdline string `json:"extra_passive_cmdline,omitempty" description:"Additional Kernel option cmdline to apply just for passive"`
ExtraRecoveryCmdline string `json:"extra_recovery_cmdline,omitempty" description:"Set additional boot commands when booting into recovery"`
NextEntry string `json:"next_entry,omitempty" description:"Set the next reboot entry."`
SavedEntry string `json:"saved_entry,omitempty" description:"Set the default boot entry."`
}

// PowerManagement is a meta structure to hold the different rules for managing power, which are not compatible between each other.
type PowerManagement struct {
}

// NoPowerManagement is a meta structure used when the user does not define any power management options or when the user does not want to reboot or poweroff the machine.
type NoPowerManagement struct {
Reboot bool `json:"reboot,omitempty" const:"false" default:"false" description:"Reboot after installation"`
Poweroff bool `json:"poweroff,omitempty" const:"false" default:"false" description:"Power off after installation"`
}

// RebootOnly is a meta structure used to enforce that when the reboot option is set, the poweroff option is not set.
type RebootOnly struct {
Reboot bool `json:"reboot,omitempty" const:"true" default:"false" required:"true" description:"Reboot after installation"`
Poweroff bool `json:"poweroff,omitempty" const:"false" default:"false" description:"Power off after installation"`
}

// PowerOffOnly is a meta structure used to enforce that when the poweroff option is set, the reboot option is not set.
type PowerOffOnly struct {
Reboot bool `json:"reboot,omitempty" const:"false" default:"false" description:"Reboot after installation"`
Poweroff bool `json:"poweroff,omitempty" const:"true" default:"false" required:"true" description:"Power off after installation"`
}

var _ jsonschemago.OneOfExposer = PowerManagement{}

// The OneOfModel interface is only needed for the tests that check the new schema contain all needed fields
// it can be removed once the new schema is the single source of truth.
type OneOfModel interface {
JSONSchemaOneOf() []interface{}
}

// JSONSchemaOneOf defines that different which are the different valid power management rules and states that one and only one of them needs to be validated for the entire schema to be valid.
func (PowerManagement) JSONSchemaOneOf() []interface{} {
return []interface{}{
NoPowerManagement{}, RebootOnly{}, PowerOffOnly{},
}
}
134 changes: 134 additions & 0 deletions schema/install_schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package schema_test

import (
"strings"

. "github.com/kairos-io/kairos-sdk/schema"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Install Schema", func() {
var config *KConfig
var err error
var yaml string

JustBeforeEach(func() {
config, err = NewConfigFromYAML(yaml, InstallSchema{})
Expect(err).ToNot(HaveOccurred())
})

Context("when device is auto", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: auto`
})

It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})

Context("when device is a path", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda`
})

It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})

Context("when device is other than a path or auto", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: foobar`
})

It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(
strings.Contains(config.ValidationError.Error(),
"does not match pattern '^(auto|/|(/[a-zA-Z0-9_-]+)+)$'",
),
).To(BeTrue())
})
})

Context("when reboot and poweroff are true", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda
reboot: true
poweroff: true`
})

It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(config.ValidationError.Error()).To(MatchRegexp("value must be false"))
})
})

Context("when reboot is true and poweroff is false", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda
reboot: true
poweroff: false`
})

It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})

Context("when reboot is false and poweroff is true", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda
reboot: false
poweroff: true`
})

It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})

Context("with no power management set", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda`
})

It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})

Context("with all possible options", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: "/dev/sda"
reboot: true
auto: true
image: "docker:.."
bundles:
- rootfs_path: /usr/local/lib/extensions/<name>
targets:
- container://<image>
grub_options:
extra_cmdline: "config_url=http://"
extra_active_cmdline: "config_url=http://"
extra_passive_cmdline: "config_url=http://"
default_menu_entry: "foobar"
env:
- foo=barevice: /dev/sda`
})

It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
})
68 changes: 68 additions & 0 deletions schema/p2p_schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package schema

import (
jsonschemago "github.com/swaggest/jsonschema-go"
)

// P2PSchema represents the P2P block in the Kairos configuration. It is used to enables and configure the p2p full-mesh functionalities.
type P2PSchema struct {
_ struct{} `title:"Kairos Schema: P2P block" description:"The p2p block enables the p2p full-mesh functionalities."`
Role string `json:"role,omitempty" default:"none" enum:"[\"master\",\"worker\",\"none\"]"`
NetworkID string `json:"network_id,omitempty" description:"User defined network-id. Can be used to have multiple clusters in the same network"`
DNS bool `json:"dns,omitempty" description:"Enable embedded DNS See also: https://mudler.github.io/edgevpn/docs/concepts/overview/dns/"`
DisableDHT bool `json:"disable_dht,omitempty" default:"true" description:"Disabling DHT makes co-ordination to discover nodes only in the local network"`
P2PNetworkExtended
VPN `json:"vpn,omitempty"`
}

// KubeVIPSchema represents the kubevip block in the Kairos configuration. It sets the Elastic IP used in KubeVIP. Only valid with p2p.
type KubeVIPSchema struct {
_ struct{} `title:"Kairos Schema: KubeVIP block" description:"Sets the Elastic IP used in KubeVIP. Only valid with p2p"`
EIP string `json:"eip,omitempty" example:"192.168.1.110"`
ManifestURL string `json:"manifest_url,omitempty" description:"Specify a manifest URL for KubeVIP." default:""`
Enable bool `json:"enable,omitempty" description:"Enables KubeVIP"`
Interface bool `json:"interface,omitempty" description:"Specifies a KubeVIP Interface" example:"ens18"`
}

// P2PNetworkExtended is a meta structure to hold the different rules for managing the P2P network, which are not compatible between each other.
type P2PNetworkExtended struct {
}

// P2PAutoDisabled is used to validate that when p2p.auto is disabled, then neither p2p.auto.ha not p2p.network_token can be set.
type P2PAutoDisabled struct {
NetworkToken string `json:"network_token,omitempty" const:"" required:"true"`
Auto struct {
Enable bool `json:"enable" const:"false" required:"true"`
Ha struct {
Enable bool `json:"enable" const:"false"`
} `json:"ha"`
} `json:"auto"`
}

// P2PAutoEnabled is used to validate that when p2p.auto is set, p2p.network_token has to be set.
type P2PAutoEnabled struct {
NetworkToken string `json:"network_token" required:"true" minLength:"1" description:"network_token is the shared secret used by the nodes to co-ordinate with p2p"`
Auto struct {
Enable bool `json:"enable,omitempty" const:"true"`
Ha struct {
Enable bool `json:"enable" const:"true"`
MasterNodes int `json:"master_nodes,omitempty" minimum:"1" description:"Number of HA additional master nodes. A master node is always required for creating the cluster and is implied."`
} `json:"ha"`
} `json:"auto,omitempty"`
}

var _ jsonschemago.OneOfExposer = P2PNetworkExtended{}

// JSONSchemaOneOf defines that different which are the different valid p2p network rules and states that one and only one of them needs to be validated for the entire schema to be valid.
func (P2PNetworkExtended) JSONSchemaOneOf() []interface{} {
return []interface{}{
P2PAutoEnabled{}, P2PAutoDisabled{},
}
}

// VPN represents the vpn block in the Kairos configuration.
type VPN struct {
Create bool `json:"vpn,omitempty" default:"true"`
Use bool `json:"use,omitempty" default:"true"`
Envs []interface{} `json:"env,omitempty"`
}
Loading

0 comments on commit c90740d

Please sign in to comment.