mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
Add a host analyzer to check if a subnet contains an IP address (#1735)
* Add a host collector / analyzer to check if a subnet contains an IP address
This commit is contained in:
@@ -2746,6 +2746,61 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
subnetContainsIP:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
cidr:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
ip:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- cidr
|
||||
- ip
|
||||
- outcomes
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
@@ -895,6 +895,61 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
subnetContainsIP:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
cidr:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
ip:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- cidr
|
||||
- ip
|
||||
- outcomes
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
@@ -895,6 +895,61 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
subnetContainsIP:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
cidr:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
ip:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- cidr
|
||||
- ip
|
||||
- outcomes
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
@@ -19837,6 +19837,61 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
subnetContainsIP:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
cidr:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
ip:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- cidr
|
||||
- ip
|
||||
- outcomes
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
16
examples/preflight/host/subnet-contains-ip.yaml
Normal file
16
examples/preflight/host/subnet-contains-ip.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: subnet-contains-ip
|
||||
spec:
|
||||
analyzers:
|
||||
- subnetContainsIP:
|
||||
cidr: "10.0.0.0/8"
|
||||
ip: "10.0.0.5"
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "false"
|
||||
message: The IP address is not within the subnet range
|
||||
- pass:
|
||||
when: "true"
|
||||
message: The IP address is within the subnet range
|
||||
@@ -45,6 +45,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b
|
||||
return &AnalyzeHostIPV4Interfaces{analyzer.IPV4Interfaces}, true
|
||||
case analyzer.SubnetAvailable != nil:
|
||||
return &AnalyzeHostSubnetAvailable{analyzer.SubnetAvailable}, true
|
||||
case analyzer.SubnetContainsIP != nil:
|
||||
return &AnalyzeHostSubnetContainsIP{analyzer.SubnetContainsIP}, true
|
||||
case analyzer.FilesystemPerformance != nil:
|
||||
return &AnalyzeHostFilesystemPerformance{analyzer.FilesystemPerformance}, true
|
||||
case analyzer.Certificate != nil:
|
||||
|
||||
55
pkg/analyze/host_subnetcontainsip.go
Normal file
55
pkg/analyze/host_subnetcontainsip.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type AnalyzeHostSubnetContainsIP struct {
|
||||
hostAnalyzer *troubleshootv1beta2.SubnetContainsIPAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostSubnetContainsIP) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Subnet Contains IP")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostSubnetContainsIP) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostSubnetContainsIP) Analyze(
|
||||
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
|
||||
) ([]*AnalyzeResult, error) {
|
||||
_, ipNet, err := net.ParseCIDR(a.hostAnalyzer.CIDR)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse CIDR %s", a.hostAnalyzer.CIDR)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(a.hostAnalyzer.IP)
|
||||
if ip == nil {
|
||||
return nil, errors.Errorf("failed to parse IP address %s", a.hostAnalyzer.IP)
|
||||
}
|
||||
|
||||
contains := fmt.Sprintf("%t", ipNet.Contains(ip))
|
||||
|
||||
results, err := analyzeHostCollectorResults([]collectedContent{{Data: []byte(contains)}}, a.hostAnalyzer.Outcomes, a.CheckCondition, a.Title())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to analyze Subnet Contains IP")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostSubnetContainsIP) CheckCondition(when string, data []byte) (bool, error) {
|
||||
switch when {
|
||||
case "true":
|
||||
return string(data) == "true", nil
|
||||
case "false":
|
||||
return string(data) == "false", nil
|
||||
}
|
||||
|
||||
return false, errors.Errorf("unknown condition: %q", when)
|
||||
}
|
||||
122
pkg/analyze/host_subnetcontainsip_test.go
Normal file
122
pkg/analyze/host_subnetcontainsip_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeSubnetContainsIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hostAnalyzer *troubleshootv1beta2.SubnetContainsIPAnalyze
|
||||
result []*AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "ip is in subnet",
|
||||
hostAnalyzer: &troubleshootv1beta2.SubnetContainsIPAnalyze{
|
||||
CIDR: "10.0.0.0/8",
|
||||
IP: "10.0.0.5",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "true",
|
||||
Message: "IP address is in subnet",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "false",
|
||||
Message: "IP address is not in subnet",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Subnet Contains IP",
|
||||
IsPass: true,
|
||||
Message: "IP address is in subnet",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ip is not in subnet",
|
||||
hostAnalyzer: &troubleshootv1beta2.SubnetContainsIPAnalyze{
|
||||
CIDR: "10.0.0.0/8",
|
||||
IP: "192.168.1.1",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "true",
|
||||
Message: "IP address is in subnet",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "false",
|
||||
Message: "IP address is not in subnet",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Subnet Contains IP",
|
||||
IsFail: true,
|
||||
Message: "IP address is not in subnet",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid CIDR",
|
||||
hostAnalyzer: &troubleshootv1beta2.SubnetContainsIPAnalyze{
|
||||
CIDR: "invalid",
|
||||
IP: "10.0.0.5",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "true",
|
||||
Message: "IP address is in subnet",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid IP",
|
||||
hostAnalyzer: &troubleshootv1beta2.SubnetContainsIPAnalyze{
|
||||
CIDR: "10.0.0.0/8",
|
||||
IP: "invalid",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "true",
|
||||
Message: "IP address is in subnet",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
result, err := (&AnalyzeHostSubnetContainsIP{test.hostAnalyzer}).Analyze(nil, nil)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
return
|
||||
}
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,14 @@ type SubnetAvailableAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type SubnetContainsIPAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
CIDR string `json:"cidr" yaml:"cidr"`
|
||||
IP string `json:"ip" yaml:"ip"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type FilesystemPerformanceAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
@@ -157,6 +165,7 @@ type HostAnalyze struct {
|
||||
TCPConnect *TCPConnectAnalyze `json:"tcpConnect,omitempty" yaml:"tcpConnect,omitempty"`
|
||||
IPV4Interfaces *IPV4InterfacesAnalyze `json:"ipv4Interfaces,omitempty" yaml:"ipv4Interfaces,omitempty"`
|
||||
SubnetAvailable *SubnetAvailableAnalyze `json:"subnetAvailable,omitempty" yaml:"subnetAvailable,omitempty"`
|
||||
SubnetContainsIP *SubnetContainsIPAnalyze `json:"subnetContainsIP,omitempty" yaml:"subnetContainsIP,omitempty"`
|
||||
FilesystemPerformance *FilesystemPerformanceAnalyze `json:"filesystemPerformance,omitempty" yaml:"filesystemPerformance,omitempty"`
|
||||
Certificate *CertificateAnalyze `json:"certificate,omitempty" yaml:"certificate,omitempty"`
|
||||
CertificatesCollection *HostCertificatesCollectionAnalyze `json:"certificatesCollection,omitempty" yaml:"certificatesCollection,omitempty"`
|
||||
|
||||
@@ -1918,6 +1918,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
|
||||
*out = new(SubnetAvailableAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.SubnetContainsIP != nil {
|
||||
in, out := &in.SubnetContainsIP, &out.SubnetContainsIP
|
||||
*out = new(SubnetContainsIPAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.FilesystemPerformance != nil {
|
||||
in, out := &in.FilesystemPerformance, &out.FilesystemPerformance
|
||||
*out = new(FilesystemPerformanceAnalyze)
|
||||
@@ -4814,6 +4819,33 @@ func (in *SubnetAvailableAnalyze) DeepCopy() *SubnetAvailableAnalyze {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SubnetContainsIPAnalyze) DeepCopyInto(out *SubnetContainsIPAnalyze) {
|
||||
*out = *in
|
||||
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
|
||||
if in.Outcomes != nil {
|
||||
in, out := &in.Outcomes, &out.Outcomes
|
||||
*out = make([]*Outcome, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(Outcome)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetContainsIPAnalyze.
|
||||
func (in *SubnetContainsIPAnalyze) DeepCopy() *SubnetContainsIPAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SubnetContainsIPAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SupportBundle) DeepCopyInto(out *SupportBundle) {
|
||||
*out = *in
|
||||
|
||||
@@ -279,6 +279,8 @@ func (c *RemoteCollector) toHostCollector() (*troubleshootv1beta2.HostCollect, e
|
||||
CollectorName: c.Collect.IPV4Interfaces.CollectorName,
|
||||
Exclude: c.Collect.IPV4Interfaces.Exclude,
|
||||
},
|
||||
CIDRRangeAlloc: c.Collect.SubnetAvailable.CIDRRangeAlloc,
|
||||
DesiredCIDR: c.Collect.SubnetAvailable.DesiredCIDR,
|
||||
}
|
||||
case c.Collect.FilesystemPerformance != nil:
|
||||
hostCollect.FilesystemPerformance = &troubleshootv1beta2.FilesystemPerformance{
|
||||
|
||||
@@ -4174,6 +4174,90 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"subnetContainsIP": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cidr",
|
||||
"ip",
|
||||
"outcomes"
|
||||
],
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"checkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"cidr": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclude": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
},
|
||||
"ip": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pass": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"warn": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strict": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sysctl": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -18765,6 +18765,90 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"subnetContainsIP": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cidr",
|
||||
"ip",
|
||||
"outcomes"
|
||||
],
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"checkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"cidr": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclude": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
},
|
||||
"ip": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pass": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"warn": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strict": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sysctl": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
Reference in New Issue
Block a user