mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-18 17:37:03 +00:00
Merge branch 'yannh:master' into master
This commit is contained in:
commit
fff7023b7f
24 changed files with 948 additions and 339 deletions
|
|
@ -2,6 +2,13 @@ FROM alpine:3.14 as certs
|
|||
RUN apk add ca-certificates
|
||||
|
||||
FROM scratch AS kubeconform
|
||||
LABEL org.opencontainers.image.authors="yann@mandragor.org" \
|
||||
org.opencontainers.image.source="https://github.com/yannh/kubeconform/" \
|
||||
org.opencontainers.image.description="A Kubernetes manifests validation tool" \
|
||||
org.opencontainers.image.documentation="https://github.com/yannh/kubeconform/" \
|
||||
org.opencontainers.image.licenses="Apache License 2.0" \
|
||||
org.opencontainers.image.title="kubeconform" \
|
||||
org.opencontainers.image.url="https://github.com/yannh/kubeconform/"
|
||||
MAINTAINER Yann HAMON <yann@mandragor.org>
|
||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY kubeconform /
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
FROM alpine:3.14 as certs
|
||||
LABEL org.opencontainers.image.authors="yann@mandragor.org" \
|
||||
org.opencontainers.image.source="https://github.com/yannh/kubeconform/" \
|
||||
org.opencontainers.image.description="A Kubernetes manifests validation tool" \
|
||||
org.opencontainers.image.documentation="https://github.com/yannh/kubeconform/" \
|
||||
org.opencontainers.image.licenses="Apache License 2.0" \
|
||||
org.opencontainers.image.title="kubeconform" \
|
||||
org.opencontainers.image.url="https://github.com/yannh/kubeconform/"
|
||||
MAINTAINER Yann HAMON <yann@mandragor.org>
|
||||
RUN apk add ca-certificates
|
||||
COPY kubeconform /
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
FROM bats/bats:v1.2.1
|
||||
RUN apk --no-cache add ca-certificates parallel
|
||||
COPY dist/kubeconform_linux_amd64/kubeconform /code/bin/
|
||||
RUN apk --no-cache add ca-certificates parallel libxml2-utils
|
||||
COPY dist/kubeconform_linux_amd64_v1/kubeconform /code/bin/
|
||||
COPY acceptance.bats acceptance-nonetwork.bats /code/
|
||||
COPY fixtures /code/fixtures
|
||||
|
|
|
|||
9
Makefile
9
Makefile
|
|
@ -31,12 +31,15 @@ docker-acceptance: build-bats
|
|||
docker run --network none -t bats -p acceptance-nonetwork.bats
|
||||
|
||||
goreleaser-build-static:
|
||||
docker run -t -e GOOS=linux -e GOARCH=amd64 -v $$PWD:/go/src/github.com/yannh/kubeconform -w /go/src/github.com/yannh/kubeconform goreleaser/goreleaser:v0.176.0 build --single-target --skip-post-hooks --rm-dist --snapshot
|
||||
cp dist/kubeconform_linux_amd64/kubeconform bin/
|
||||
docker run -t -e GOOS=linux -e GOARCH=amd64 -v $$PWD:/go/src/github.com/yannh/kubeconform -w /go/src/github.com/yannh/kubeconform goreleaser/goreleaser:v1.11.5 build --single-target --skip-post-hooks --rm-dist --snapshot
|
||||
cp dist/kubeconform_linux_amd64_v1/kubeconform bin/
|
||||
|
||||
release:
|
||||
docker run -e GITHUB_TOKEN -t -v /var/run/docker.sock:/var/run/docker.sock -v $$PWD:/go/src/github.com/yannh/kubeconform -w /go/src/github.com/yannh/kubeconform goreleaser/goreleaser:v0.176.0 release --rm-dist
|
||||
docker run -e GITHUB_TOKEN -t -v /var/run/docker.sock:/var/run/docker.sock -v $$PWD:/go/src/github.com/yannh/kubeconform -w /go/src/github.com/yannh/kubeconform goreleaser/goreleaser:v1.11.5 release --rm-dist
|
||||
|
||||
update-deps:
|
||||
go get -u ./...
|
||||
go mod tidy
|
||||
|
||||
update-junit-xsd:
|
||||
curl https://raw.githubusercontent.com/junit-team/junit5/main/platform-tests/src/test/resources/jenkins-junit.xsd > fixtures/junit.xsd
|
||||
|
|
|
|||
54
Readme.md
54
Readme.md
|
|
@ -16,7 +16,7 @@ It is inspired by, contains code from and is designed to stay close to
|
|||
* configurable list of **remote, or local schemas locations**, enabling validating Kubernetes
|
||||
custom resources (CRDs) and offline validation capabilities
|
||||
* uses by default a [self-updating fork](https://github.com/yannh/kubernetes-json-schema) of the schemas registry maintained
|
||||
by the [kubernetes-json-schema](https://github.com/instrumenta/kubernetes-json-schema) project - which guarantees
|
||||
by the kubernetes-json-schema project - which guarantees
|
||||
up-to-date **schemas for all recent versions of Kubernetes**.
|
||||
|
||||
### A small overview of Kubernetes manifest validation
|
||||
|
|
@ -57,6 +57,16 @@ $ brew install kubeconform
|
|||
|
||||
You can also download the latest version from the [release page](https://github.com/yannh/kubeconform/releases).
|
||||
|
||||
Another way of installation is via Golang's package manager:
|
||||
|
||||
```bash
|
||||
# With a specific version tag
|
||||
$ go install github.com/yannh/kubeconform/cmd/kubeconform@v0.4.13
|
||||
|
||||
# Latest version
|
||||
$ go install github.com/yannh/kubeconform/cmd/kubeconform@latest
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
|
|
@ -64,8 +74,8 @@ $ ./bin/kubeconform -h
|
|||
Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]...
|
||||
-cache string
|
||||
cache schemas downloaded via HTTP to this folder
|
||||
-cpu-prof string
|
||||
debug - log CPU profiling to file
|
||||
-debug
|
||||
print debug information
|
||||
-exit-on-error
|
||||
immediately stop execution when the first error is encountered
|
||||
-h show help information
|
||||
|
|
@ -82,16 +92,16 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]...
|
|||
-output string
|
||||
output format - json, junit, tap, text (default "text")
|
||||
-reject string
|
||||
comma-separated list of kinds to reject
|
||||
comma-separated list of kinds or GVKs to reject
|
||||
-schema-location value
|
||||
override schemas location search path (can be specified multiple times)
|
||||
-skip string
|
||||
comma-separated list of kinds to ignore
|
||||
comma-separated list of kinds or GVKs to ignore
|
||||
-strict
|
||||
disallow additional properties not in schema
|
||||
disallow additional properties not in schema or duplicated keys
|
||||
-summary
|
||||
print a summary at the end (ignored for junit output)
|
||||
-v show version information
|
||||
-v show version information
|
||||
-verbose
|
||||
print results for all resources (ignored for tap and junit output)
|
||||
```
|
||||
|
|
@ -135,6 +145,17 @@ cat fixtures/valid.yaml | ./bin/kubeconform -summary
|
|||
Summary: 1 resource found parsing stdin - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0
|
||||
```
|
||||
|
||||
* Validating a file, ignoring its resource using both Kind, and GVK (Group, Version, Kind) notations
|
||||
```
|
||||
# This will ignore ReplicationController for all apiVersions
|
||||
./bin/kubeconform -summary -skip ReplicationController fixtures/valid.yaml
|
||||
Summary: 1 resource found in 1 file - Valid: 0, Invalid: 0, Errors: 0, Skipped: 1
|
||||
|
||||
# This will ignore ReplicationController only for apiVersion v1
|
||||
$ ./bin/kubeconform -summary -skip v1/ReplicationController fixtures/valid.yaml
|
||||
Summary: 1 resource found in 1 file - Valid: 0, Invalid: 0, Errors: 0, Skipped: 1
|
||||
```
|
||||
|
||||
* Validating a folder, increasing the number of parallel workers
|
||||
```
|
||||
$ ./bin/kubeconform -summary -n 16 fixtures
|
||||
|
|
@ -184,6 +205,7 @@ Here are the variables you can use in -schema-location:
|
|||
* *StrictSuffix* - "-strict" or "" depending on whether validation is running in strict mode or not
|
||||
* *ResourceKind* - Kind of the Kubernetes Resource
|
||||
* *ResourceAPIVersion* - Version of API used for the resource - "v1" in "apiVersion: monitoring.coreos.com/v1"
|
||||
* *Group* - the group name as stated in this resource's definition - "monitoring.coreos.com" in "apiVersion: monitoring.coreos.com/v1"
|
||||
* *KindSuffix* - suffix computed from apiVersion - for compatibility with Kubeval schema registries
|
||||
|
||||
### Converting an OpenAPI file to a JSON Schema
|
||||
|
|
@ -209,7 +231,7 @@ Some CRD schemas do not have explicit validation for fields implicitly validated
|
|||
|
||||
### Usage as a Github Action
|
||||
|
||||
Kubeconform is publishes Docker Images to Github's new Container Registry, ghcr.io. These images
|
||||
Kubeconform publishes Docker Images to Github's new Container Registry, ghcr.io. These images
|
||||
can be used directly in a Github Action, once logged in using a [_Github Token_](https://github.blog/changelog/2021-03-24-packages-container-registry-now-supports-github_token/).
|
||||
|
||||
Example:
|
||||
|
|
@ -235,6 +257,22 @@ bandwidth costs might be applicable. Since bandwidth from Github Packages within
|
|||
Github Container Registry to also be usable for free within Github Actions in the future. If that were not to be the
|
||||
case, I might publish the Docker image to a different platform.
|
||||
|
||||
### Usage in Gitlab-CI
|
||||
|
||||
The Kubeconform Docker image can be used in Gitlab-CI. Here is an example of a Gitlab-CI job:
|
||||
|
||||
```
|
||||
lint-kubeconform:
|
||||
stage: validate
|
||||
image:
|
||||
name: ghcr.io/yannh/kubeconform:latest-alpine
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- kubeconform
|
||||
```
|
||||
|
||||
See [issue 106](https://github.com/yannh/kubeconform/issues/106) for more details.
|
||||
|
||||
### Proxy support
|
||||
|
||||
Kubeconform will respect the HTTPS_PROXY variable when downloading schema files.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ resetCacheFolder() {
|
|||
[ "${lines[0]}" == 'Usage: bin/kubeconform [OPTION]... [FILE OR FOLDER]...' ]
|
||||
}
|
||||
|
||||
@test "Fail and display help when using an incorrect flag" {
|
||||
run bin/kubeconform -xyz
|
||||
[ "$status" -eq 1 ]
|
||||
[ "${lines[0]}" == 'flag provided but not defined: -xyz' ]
|
||||
}
|
||||
|
||||
@test "Pass when parsing a valid Kubernetes config YAML file" {
|
||||
run bin/kubeconform -summary fixtures/valid.yaml
|
||||
[ "$status" -eq 0 ]
|
||||
|
|
@ -132,6 +138,16 @@ resetCacheFolder() {
|
|||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "Fail when parsing a config with duplicate properties and strict set" {
|
||||
run bin/kubeconform -strict -kubernetes-version 1.16.0 fixtures/duplicate_property.yaml
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "Pass when parsing a config with duplicate properties and strict NOT set" {
|
||||
run bin/kubeconform -kubernetes-version 1.16.0 fixtures/duplicate_property.yaml
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "Pass when using a valid, preset -schema-location" {
|
||||
run bin/kubeconform -schema-location default fixtures/valid.yaml
|
||||
[ "$status" -eq 0 ]
|
||||
|
|
@ -202,6 +218,18 @@ resetCacheFolder() {
|
|||
[ "$output" = "fixtures/valid.yaml - bob ReplicationController skipped" ]
|
||||
}
|
||||
|
||||
@test "Skip when parsing a resource with a GVK to skip" {
|
||||
run bin/kubeconform -verbose -skip v1/ReplicationController fixtures/valid.yaml
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "fixtures/valid.yaml - bob ReplicationController skipped" ]
|
||||
}
|
||||
|
||||
@test "Do not skip when parsing a resource with a GVK to skip, where the Kind matches but not the version" {
|
||||
run bin/kubeconform -verbose -skip v2/ReplicationController fixtures/valid.yaml
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "fixtures/valid.yaml - ReplicationController bob is valid" ]
|
||||
}
|
||||
|
||||
@test "Fail when parsing a resource from a kind to reject" {
|
||||
run bin/kubeconform -verbose -reject ReplicationController fixtures/valid.yaml
|
||||
[ "$status" -eq 1 ]
|
||||
|
|
@ -300,3 +328,10 @@ resetCacheFolder() {
|
|||
[ "$status" -eq 0 ]
|
||||
[ "$output" = 'Summary: 100000 resources found parsing stdin - Valid: 100000, Invalid: 0, Errors: 0, Skipped: 0' ]
|
||||
}
|
||||
|
||||
@test "JUnit output can be validated against the Junit schema definition" {
|
||||
run bash -c "bin/kubeconform -output junit -summary fixtures/valid.yaml > output.xml"
|
||||
[ "$status" -eq 0 ]
|
||||
run xmllint --noout --schema fixtures/junit.xsd output.xml
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,9 @@ func realMain() int {
|
|||
return 1
|
||||
}
|
||||
|
||||
if cfg.CPUProfileFile != "" {
|
||||
f, err := os.Create(cfg.CPUProfileFile)
|
||||
cpuProfileFile := os.Getenv("KUBECONFORM_CPUPROFILE_FILE")
|
||||
if cpuProfileFile != "" {
|
||||
f, err := os.Create(cpuProfileFile)
|
||||
if err != nil {
|
||||
log.Fatal("could not create CPU profile: ", err)
|
||||
}
|
||||
|
|
@ -97,9 +98,10 @@ func realMain() int {
|
|||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
|
||||
v, err := validator.New(cfg.SchemaLocations, validator.Opts{
|
||||
var v validator.Validator
|
||||
v, err = validator.New(cfg.SchemaLocations, validator.Opts{
|
||||
Cache: cfg.Cache,
|
||||
Debug: cfg.Debug,
|
||||
SkipTLS: cfg.SkipTLS,
|
||||
SkipKinds: cfg.SkipKinds,
|
||||
RejectKinds: cfg.RejectKinds,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
18
fixtures/duplicate_property.yaml
Normal file
18
fixtures/duplicate_property.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: nginx-ds
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: nginx-ds
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: envoy
|
||||
name: envoy
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
118
fixtures/junit.xsd
Normal file
118
fixtures/junit.xsd
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Source: https://svn.jenkins-ci.org/trunk/hudson/dtkit/dtkit-format/dtkit-junit-model/src/main/resources/com/thalesgroup/dtkit/junit/model/xsd/junit-4.xsd
|
||||
|
||||
This file available under the terms of the MIT License as follows:
|
||||
|
||||
*******************************************************************************
|
||||
* Copyright (c) 2010 Thales Corporate Services SAS *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy *
|
||||
* of this software and associated documentation files (the "Software"), to deal*
|
||||
* in the Software without restriction, including without limitation the rights *
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
|
||||
* copies of the Software, and to permit persons to whom the Software is *
|
||||
* furnished to do so, subject to the following conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in *
|
||||
* all copies or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,*
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN *
|
||||
* THE SOFTWARE. *
|
||||
********************************************************************************
|
||||
-->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:element name="failure">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute name="type" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="message" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="error">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute name="type" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="message" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="properties">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="property" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="property">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
<xs:attribute name="value" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="skipped" type="xs:string"/>
|
||||
<xs:element name="system-err" type="xs:string"/>
|
||||
<xs:element name="system-out" type="xs:string"/>
|
||||
|
||||
<xs:element name="testcase">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="skipped" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="error" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element ref="failure" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element ref="system-out" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element ref="system-err" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
<xs:attribute name="assertions" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="time" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="classname" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="status" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="testsuite">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="properties" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="testcase" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element ref="system-out" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="system-err" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
<xs:attribute name="tests" type="xs:string" use="required"/>
|
||||
<xs:attribute name="failures" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="errors" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="time" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="disabled" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="skipped" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="timestamp" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="hostname" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="id" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="package" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="testsuites">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="testsuite" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="time" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="tests" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="failures" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="disabled" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="errors" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
|
||||
</xs:schema>
|
||||
4
pkg/cache/ondisk.go
vendored
4
pkg/cache/ondisk.go
vendored
|
|
@ -1,7 +1,7 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
|
@ -23,7 +23,7 @@ func NewOnDiskCache(cache string) Cache {
|
|||
}
|
||||
|
||||
func cachePath(folder, resourceKind, resourceAPIVersion, k8sVersion string) string {
|
||||
hash := md5.Sum([]byte(fmt.Sprintf("%s-%s-%s", resourceKind, resourceAPIVersion, k8sVersion)))
|
||||
hash := sha256.Sum256([]byte(fmt.Sprintf("%s-%s-%s", resourceKind, resourceAPIVersion, k8sVersion)))
|
||||
return path.Join(folder, hex.EncodeToString(hash[:]))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
type Config struct {
|
||||
Cache string
|
||||
CPUProfileFile string
|
||||
Debug bool
|
||||
ExitOnError bool
|
||||
Files []string
|
||||
SchemaLocations []string
|
||||
|
|
@ -56,7 +56,7 @@ func splitCSV(csvStr string) map[string]struct{} {
|
|||
func FromFlags(progName string, args []string) (Config, string, error) {
|
||||
var schemaLocationsParam, ignoreFilenamePatterns arrayParam
|
||||
var skipKindsCSV, rejectKindsCSV string
|
||||
flags := flag.NewFlagSet(progName, flag.ExitOnError)
|
||||
flags := flag.NewFlagSet(progName, flag.ContinueOnError)
|
||||
var buf bytes.Buffer
|
||||
flags.SetOutput(&buf)
|
||||
|
||||
|
|
@ -65,19 +65,19 @@ func FromFlags(progName string, args []string) (Config, string, error) {
|
|||
|
||||
flags.StringVar(&c.KubernetesVersion, "kubernetes-version", "master", "version of Kubernetes to validate against, e.g.: 1.18.0")
|
||||
flags.Var(&schemaLocationsParam, "schema-location", "override schemas location search path (can be specified multiple times)")
|
||||
flags.StringVar(&skipKindsCSV, "skip", "", "comma-separated list of kinds to ignore")
|
||||
flags.StringVar(&rejectKindsCSV, "reject", "", "comma-separated list of kinds to reject")
|
||||
flags.StringVar(&skipKindsCSV, "skip", "", "comma-separated list of kinds or GVKs to ignore")
|
||||
flags.StringVar(&rejectKindsCSV, "reject", "", "comma-separated list of kinds or GVKs to reject")
|
||||
flags.BoolVar(&c.Debug, "debug", false, "print debug information")
|
||||
flags.BoolVar(&c.ExitOnError, "exit-on-error", false, "immediately stop execution when the first error is encountered")
|
||||
flags.BoolVar(&c.IgnoreMissingSchemas, "ignore-missing-schemas", false, "skip files with missing schemas instead of failing")
|
||||
flags.Var(&ignoreFilenamePatterns, "ignore-filename-pattern", "regular expression specifying paths to ignore (can be specified multiple times)")
|
||||
flags.BoolVar(&c.Summary, "summary", false, "print a summary at the end (ignored for junit output)")
|
||||
flags.IntVar(&c.NumberOfWorkers, "n", 4, "number of goroutines to run concurrently")
|
||||
flags.BoolVar(&c.Strict, "strict", false, "disallow additional properties not in schema")
|
||||
flags.BoolVar(&c.Strict, "strict", false, "disallow additional properties not in schema or duplicated keys")
|
||||
flags.StringVar(&c.OutputFormat, "output", "text", "output format - json, junit, tap, text")
|
||||
flags.BoolVar(&c.Verbose, "verbose", false, "print results for all resources (ignored for tap and junit output)")
|
||||
flags.BoolVar(&c.SkipTLS, "insecure-skip-tls-verify", false, "disable verification of the server's SSL certificate. This will make your HTTPS connections insecure")
|
||||
flags.StringVar(&c.Cache, "cache", "", "cache schemas downloaded via HTTP to this folder")
|
||||
flags.StringVar(&c.CPUProfileFile, "cpu-prof", "", "debug - log CPU profiling to file")
|
||||
flags.BoolVar(&c.Help, "h", false, "show help information")
|
||||
flags.BoolVar(&c.Version, "v", false, "show version information")
|
||||
flags.Usage = func() {
|
||||
|
|
|
|||
|
|
@ -112,9 +112,10 @@ func TestFromFlags(t *testing.T) {
|
|||
{
|
||||
[]string{"-cache", "cache", "-ignore-missing-schemas", "-kubernetes-version", "1.16.0", "-n", "2", "-output", "json",
|
||||
"-schema-location", "folder", "-schema-location", "anotherfolder", "-skip", "kinda,kindb", "-strict",
|
||||
"-reject", "kindc,kindd", "-summary", "-verbose", "file1", "file2"},
|
||||
"-reject", "kindc,kindd", "-summary", "-debug", "-verbose", "file1", "file2"},
|
||||
Config{
|
||||
Cache: "cache",
|
||||
Debug: true,
|
||||
Files: []string{"file1", "file2"},
|
||||
IgnoreMissingSchemas: true,
|
||||
KubernetesVersion: "1.16.0",
|
||||
|
|
|
|||
|
|
@ -33,22 +33,22 @@ type Property struct {
|
|||
}
|
||||
|
||||
type TestSuite struct {
|
||||
XMLName xml.Name `xml:"testsuite"`
|
||||
Properties []*Property `xml:"properties>property,omitempty"`
|
||||
Cases []TestCase `xml:"testcase"`
|
||||
Name string `xml:"name,attr"`
|
||||
Id int `xml:"id,attr"`
|
||||
Tests int `xml:"tests,attr"`
|
||||
Failures int `xml:"failures,attr"`
|
||||
Errors int `xml:"errors,attr"`
|
||||
Disabled int `xml:"disabled,attr"`
|
||||
Skipped int `xml:"skipped,attr"`
|
||||
XMLName xml.Name `xml:"testsuite"`
|
||||
Cases []TestCase `xml:"testcase"`
|
||||
Name string `xml:"name,attr"`
|
||||
Id int `xml:"id,attr"`
|
||||
Tests int `xml:"tests,attr"`
|
||||
Failures int `xml:"failures,attr"`
|
||||
Errors int `xml:"errors,attr"`
|
||||
Disabled int `xml:"disabled,attr"`
|
||||
Skipped int `xml:"skipped,attr"`
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
XMLName xml.Name `xml:"testcase"`
|
||||
Name string `xml:"name,attr"`
|
||||
ClassName string `xml:"classname,attr"`
|
||||
Time int `xml:"time,attr"` // Optional, but for Buildkite support https://github.com/yannh/kubeconform/issues/127
|
||||
Skipped *TestCaseSkipped `xml:"skipped,omitempty"`
|
||||
Error *TestCaseError `xml:"error,omitempty"`
|
||||
Failure []TestCaseError `xml:"failure,omitempty"`
|
||||
|
|
@ -100,8 +100,7 @@ func (o *junito) Write(result validator.Result) error {
|
|||
Name: result.Resource.Path,
|
||||
Id: o.id,
|
||||
Tests: 0, Failures: 0, Errors: 0, Disabled: 0, Skipped: 0,
|
||||
Cases: make([]TestCase, 0),
|
||||
Properties: make([]*Property, 0),
|
||||
Cases: make([]TestCase, 0),
|
||||
}
|
||||
o.suites[result.Resource.Path] = suite
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,7 @@ metadata:
|
|||
},
|
||||
"<testsuites name=\"kubeconform\" time=\"\" tests=\"1\" failures=\"0\" disabled=\"0\" errors=\"0\">\n" +
|
||||
" <testsuite name=\"deployment.yml\" id=\"1\" tests=\"1\" failures=\"0\" errors=\"0\" disabled=\"0\" skipped=\"0\">\n" +
|
||||
" <properties></properties>\n" +
|
||||
" <testcase name=\"my-app\" classname=\"Deployment@apps/v1\"></testcase>\n" +
|
||||
" <testcase name=\"my-app\" classname=\"Deployment@apps/v1\" time=\"\"></testcase>\n" +
|
||||
" </testsuite>\n" +
|
||||
"</testsuites>\n",
|
||||
},
|
||||
|
|
@ -82,8 +81,7 @@ metadata:
|
|||
},
|
||||
"<testsuites name=\"kubeconform\" time=\"\" tests=\"1\" failures=\"0\" disabled=\"0\" errors=\"0\">\n" +
|
||||
" <testsuite name=\"deployment.yml\" id=\"1\" tests=\"1\" failures=\"0\" errors=\"0\" disabled=\"0\" skipped=\"0\">\n" +
|
||||
" <properties></properties>\n" +
|
||||
" <testcase name=\"my-app\" classname=\"Deployment@apps/v1\"></testcase>\n" +
|
||||
" <testcase name=\"my-app\" classname=\"Deployment@apps/v1\" time=\"\"></testcase>\n" +
|
||||
" </testsuite>\n" +
|
||||
"</testsuites>\n",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package registry
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
|
@ -21,9 +23,10 @@ type SchemaRegistry struct {
|
|||
schemaPathTemplate string
|
||||
cache cache.Cache
|
||||
strict bool
|
||||
debug bool
|
||||
}
|
||||
|
||||
func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool, skipTLS bool) (*SchemaRegistry, error) {
|
||||
func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool, skipTLS bool, debug bool) (*SchemaRegistry, error) {
|
||||
reghttp := &http.Transport{
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 3 * time.Second,
|
||||
|
|
@ -53,6 +56,7 @@ func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool,
|
|||
schemaPathTemplate: schemaPathTemplate,
|
||||
cache: filecache,
|
||||
strict: strict,
|
||||
debug: debug,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -71,21 +75,41 @@ func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVers
|
|||
|
||||
resp, err := r.c.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed downloading schema at %s: %s", url, err)
|
||||
msg := fmt.Sprintf("failed downloading schema at %s: %s", url, err)
|
||||
if r.debug {
|
||||
log.Println(msg)
|
||||
}
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, newNotFoundError(fmt.Errorf("no schema found"))
|
||||
msg := fmt.Sprintf("could not find schema at %s", url)
|
||||
if r.debug {
|
||||
log.Print(msg)
|
||||
}
|
||||
return nil, newNotFoundError(errors.New(msg))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("error while downloading schema at %s - received HTTP status %d", url, resp.StatusCode)
|
||||
msg := fmt.Sprintf("error while downloading schema at %s - received HTTP status %d", url, resp.StatusCode)
|
||||
if r.debug {
|
||||
log.Print(msg)
|
||||
}
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed downloading schema at %s: %s", url, err)
|
||||
msg := fmt.Sprintf("failed parsing schema from %s: %s", url, err)
|
||||
if r.debug {
|
||||
log.Print(msg)
|
||||
}
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
|
||||
if r.debug {
|
||||
log.Printf("using schema found at %s", url)
|
||||
}
|
||||
|
||||
if r.cache != nil {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func TestDownloadSchema(t *testing.T) {
|
|||
"v1",
|
||||
"1.18.0",
|
||||
nil,
|
||||
fmt.Errorf("no schema found"),
|
||||
fmt.Errorf("could not find schema at http://kubernetesjson.dev"),
|
||||
},
|
||||
{
|
||||
"getting 503",
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type LocalRegistry struct {
|
||||
pathTemplate string
|
||||
strict bool
|
||||
debug bool
|
||||
}
|
||||
|
||||
// NewLocalSchemas creates a new "registry", that will serve schemas from files, given a list of schema filenames
|
||||
func newLocalRegistry(pathTemplate string, strict bool) (*LocalRegistry, error) {
|
||||
func newLocalRegistry(pathTemplate string, strict bool, debug bool) (*LocalRegistry, error) {
|
||||
return &LocalRegistry{
|
||||
pathTemplate,
|
||||
strict,
|
||||
debug,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -28,16 +32,32 @@ func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersi
|
|||
f, err := os.Open(schemaFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, newNotFoundError(fmt.Errorf("no schema found"))
|
||||
msg := fmt.Sprintf("could not open file %s", schemaFile)
|
||||
if r.debug {
|
||||
log.Print(msg)
|
||||
}
|
||||
return nil, newNotFoundError(errors.New(msg))
|
||||
}
|
||||
return nil, fmt.Errorf("failed to open schema %s", schemaFile)
|
||||
|
||||
msg := fmt.Sprintf("failed to open schema at %s: %s", schemaFile, err)
|
||||
if r.debug {
|
||||
log.Print(msg)
|
||||
}
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
content, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failed to read schema at %s: %s", schemaFile, err)
|
||||
if r.debug {
|
||||
log.Print(msg)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.debug {
|
||||
log.Printf("using schema found at %s", schemaFile)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,12 +61,14 @@ func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict
|
|||
StrictSuffix string
|
||||
ResourceKind string
|
||||
ResourceAPIVersion string
|
||||
Group string
|
||||
KindSuffix string
|
||||
}{
|
||||
normalisedVersion,
|
||||
strictSuffix,
|
||||
strings.ToLower(resourceKind),
|
||||
groupParts[len(groupParts)-1],
|
||||
groupParts[0],
|
||||
kindSuffix,
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +81,7 @@ func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict
|
|||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func New(schemaLocation string, cache string, strict bool, skipTLS bool) (Registry, error) {
|
||||
func New(schemaLocation string, cache string, strict bool, skipTLS bool, debug bool) (Registry, error) {
|
||||
if schemaLocation == "default" {
|
||||
schemaLocation = "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json"
|
||||
} else if !strings.HasSuffix(schemaLocation, "json") { // If we dont specify a full templated path, we assume the paths of our fork of kubernetes-json-schema
|
||||
|
|
@ -92,8 +94,8 @@ func New(schemaLocation string, cache string, strict bool, skipTLS bool) (Regist
|
|||
}
|
||||
|
||||
if strings.HasPrefix(schemaLocation, "http") {
|
||||
return newHTTPRegistry(schemaLocation, cache, strict, skipTLS)
|
||||
return newHTTPRegistry(schemaLocation, cache, strict, skipTLS, debug)
|
||||
}
|
||||
|
||||
return newLocalRegistry(schemaLocation, strict)
|
||||
return newLocalRegistry(schemaLocation, strict, debug)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,18 @@ type Signature struct {
|
|||
Kind, Version, Namespace, Name string
|
||||
}
|
||||
|
||||
// GroupVersionKind returns a string with the GVK encoding of a resource signature.
|
||||
// This encoding slightly differs from the Kubernetes upstream implementation
|
||||
// in order to be suitable for being used in the kubeconform command-line arguments.
|
||||
func (sig *Signature) GroupVersionKind() string {
|
||||
return fmt.Sprintf("%s/%s", sig.Version, sig.Kind)
|
||||
}
|
||||
|
||||
// QualifiedName returns a string for a signature in the format version/kind/namespace/name
|
||||
func (sig *Signature) QualifiedName() string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", sig.Version, sig.Kind, sig.Namespace, sig.Name)
|
||||
}
|
||||
|
||||
// Signature computes a signature for a resource, based on its Kind, Version, Namespace & Name
|
||||
func (res *Resource) Signature() (*Signature, error) {
|
||||
if res.sig != nil {
|
||||
|
|
@ -119,8 +131,3 @@ func (res *Resource) Resources() []Resource {
|
|||
|
||||
return []Resource{*res}
|
||||
}
|
||||
|
||||
// QualifiedName returns a string for a signature in the format version/kind/namespace/name
|
||||
func (sig *Signature) QualifiedName() string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", sig.Version, sig.Kind, sig.Namespace, sig.Name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ type Validator interface {
|
|||
// Opts contains a set of options for the validator.
|
||||
type Opts struct {
|
||||
Cache string // Cache schemas downloaded via HTTP to this folder
|
||||
Debug bool // Debug infos will be print here
|
||||
SkipTLS bool // skip TLS validation when downloading from an HTTP Schema Registry
|
||||
SkipKinds map[string]struct{} // List of resource Kinds to ignore
|
||||
RejectKinds map[string]struct{} // List of resource Kinds to reject
|
||||
|
|
@ -61,7 +62,7 @@ func New(schemaLocations []string, opts Opts) (Validator, error) {
|
|||
|
||||
registries := []registry.Registry{}
|
||||
for _, schemaLocation := range schemaLocations {
|
||||
reg, err := registry.New(schemaLocation, opts.Cache, opts.Strict, opts.SkipTLS)
|
||||
reg, err := registry.New(schemaLocation, opts.Cache, opts.Strict, opts.SkipTLS, opts.Debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -97,12 +98,23 @@ type v struct {
|
|||
// ValidateResource validates a single resource. This allows to validate
|
||||
// large resource streams using multiple Go Routines.
|
||||
func (val *v) ValidateResource(res resource.Resource) Result {
|
||||
// For backward compatibility reasons when determining whether
|
||||
// a resource should be skipped or rejected we use both
|
||||
// the GVK encoding of the resource signatures (the recommended method
|
||||
// for skipping/rejecting resources) and the raw Kind.
|
||||
|
||||
skip := func(signature resource.Signature) bool {
|
||||
if _, ok := val.opts.SkipKinds[signature.GroupVersionKind()]; ok {
|
||||
return ok
|
||||
}
|
||||
_, ok := val.opts.SkipKinds[signature.Kind]
|
||||
return ok
|
||||
}
|
||||
|
||||
reject := func(signature resource.Signature) bool {
|
||||
if _, ok := val.opts.RejectKinds[signature.GroupVersionKind()]; ok {
|
||||
return ok
|
||||
}
|
||||
_, ok := val.opts.RejectKinds[signature.Kind]
|
||||
return ok
|
||||
}
|
||||
|
|
@ -112,7 +124,12 @@ func (val *v) ValidateResource(res resource.Resource) Result {
|
|||
}
|
||||
|
||||
var r map[string]interface{}
|
||||
if err := yaml.Unmarshal(res.Bytes, &r); err != nil {
|
||||
unmarshaller := yaml.Unmarshal
|
||||
if val.opts.Strict {
|
||||
unmarshaller = yaml.UnmarshalStrict
|
||||
}
|
||||
|
||||
if err := unmarshaller(res.Bytes, &r); err != nil {
|
||||
return Result{Resource: res, Status: Error, Err: fmt.Errorf("error unmarshalling resource: %s", err)}
|
||||
}
|
||||
|
||||
|
|
@ -244,11 +261,3 @@ func downloadSchema(registries []registry.Registry, kind, version, k8sVersion st
|
|||
|
||||
return nil, nil // No schema found - we don't consider it an error, resource will be skipped
|
||||
}
|
||||
|
||||
// From kubeval - let's see if absolutely necessary
|
||||
// func init () {
|
||||
// gojsonschema.FormatCheckers.Add("int64", ValidFormat{})
|
||||
// gojsonschema.FormatCheckers.Add("byte", ValidFormat{})
|
||||
// gojsonschema.FormatCheckers.Add("int32", ValidFormat{})
|
||||
// gojsonschema.FormatCheckers.Add("int-or-string", ValidFormat{})
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"github.com/yannh/kubeconform/pkg/registry"
|
||||
"testing"
|
||||
|
||||
"github.com/yannh/kubeconform/pkg/registry"
|
||||
|
||||
"github.com/yannh/kubeconform/pkg/resource"
|
||||
)
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ func TestValidate(t *testing.T) {
|
|||
rawResource, schemaRegistry1 []byte
|
||||
schemaRegistry2 []byte
|
||||
ignoreMissingSchema bool
|
||||
strict bool
|
||||
expect Status
|
||||
}{
|
||||
{
|
||||
|
|
@ -60,6 +62,7 @@ lastName: bar
|
|||
}`),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
Valid,
|
||||
},
|
||||
{
|
||||
|
|
@ -93,6 +96,7 @@ lastName: bar
|
|||
}`),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
Invalid,
|
||||
},
|
||||
{
|
||||
|
|
@ -125,8 +129,61 @@ firstName: foo
|
|||
}`),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
Invalid,
|
||||
},
|
||||
{
|
||||
"key \"firstName\" already set in map",
|
||||
[]byte(`
|
||||
kind: name
|
||||
apiVersion: v1
|
||||
firstName: foo
|
||||
firstName: bar
|
||||
`),
|
||||
[]byte(`{
|
||||
"title": "Example Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["firstName"]
|
||||
}`),
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
Error,
|
||||
},
|
||||
{
|
||||
"key firstname already set in map in non-strict mode",
|
||||
[]byte(`
|
||||
kind: name
|
||||
apiVersion: v1
|
||||
firstName: foo
|
||||
firstName: bar
|
||||
`),
|
||||
[]byte(`{
|
||||
"title": "Example Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["firstName"]
|
||||
}`),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
Valid,
|
||||
},
|
||||
{
|
||||
"resource has invalid yaml",
|
||||
[]byte(`
|
||||
|
|
@ -161,6 +218,7 @@ lastName: bar
|
|||
}`),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
Error,
|
||||
},
|
||||
{
|
||||
|
|
@ -196,6 +254,7 @@ lastName: bar
|
|||
},
|
||||
"required": ["firstName", "lastName"]
|
||||
}`),
|
||||
false,
|
||||
false,
|
||||
Valid,
|
||||
},
|
||||
|
|
@ -232,6 +291,7 @@ lastName: bar
|
|||
},
|
||||
"required": ["firstName", "lastName"]
|
||||
}`),
|
||||
false,
|
||||
false,
|
||||
Valid,
|
||||
},
|
||||
|
|
@ -246,6 +306,7 @@ lastName: bar
|
|||
nil,
|
||||
nil,
|
||||
true,
|
||||
false,
|
||||
Skipped,
|
||||
},
|
||||
{
|
||||
|
|
@ -259,6 +320,7 @@ lastName: bar
|
|||
nil,
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
Error,
|
||||
},
|
||||
{
|
||||
|
|
@ -272,6 +334,7 @@ lastName: bar
|
|||
[]byte(`<html>error page</html>`),
|
||||
[]byte(`<html>error page</html>`),
|
||||
true,
|
||||
false,
|
||||
Skipped,
|
||||
},
|
||||
{
|
||||
|
|
@ -285,6 +348,7 @@ lastName: bar
|
|||
[]byte(`<html>error page</html>`),
|
||||
[]byte(`<html>error page</html>`),
|
||||
false,
|
||||
false,
|
||||
Error,
|
||||
},
|
||||
} {
|
||||
|
|
@ -293,6 +357,7 @@ lastName: bar
|
|||
SkipKinds: map[string]struct{}{},
|
||||
RejectKinds: map[string]struct{}{},
|
||||
IgnoreMissingSchemas: testCase.ignoreMissingSchema,
|
||||
Strict: testCase.strict,
|
||||
},
|
||||
schemaCache: nil,
|
||||
schemaDownload: downloadSchema,
|
||||
|
|
@ -306,7 +371,11 @@ lastName: bar
|
|||
},
|
||||
}
|
||||
if got := val.ValidateResource(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect {
|
||||
t.Errorf("%d - expected %d, got %d: %s", i, testCase.expect, got.Status, got.Err.Error())
|
||||
if got.Err != nil {
|
||||
t.Errorf("%d - expected %d, got %d: %s", i, testCase.expect, got.Status, got.Err.Error())
|
||||
} else {
|
||||
t.Errorf("%d - expected %d, got %d", i, testCase.expect, got.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]...
|
|||
-skip string
|
||||
comma-separated list of kinds to ignore
|
||||
-strict
|
||||
disallow additional properties not in schema
|
||||
disallow additional properties not in schema or duplicated keys
|
||||
-summary
|
||||
print a summary at the end (ignored for junit output)
|
||||
-v show version information
|
||||
|
|
@ -83,4 +83,4 @@ fixtures/crd_schema.yaml - CustomResourceDefinition trainingjobs.sagemaker.aws.a
|
|||
fixtures/invalid.yaml - ReplicationController bob is invalid: Invalid type. Expected: [integer,null], given: string
|
||||
[...]
|
||||
Summary: 65 resources found in 34 files - Valid: 55, Invalid: 2, Errors: 8 Skipped: 0
|
||||
{{< /prism >}}
|
||||
{{< /prism >}}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]...
|
|||
-skip string
|
||||
comma-separated list of kinds to ignore
|
||||
-strict
|
||||
disallow additional properties not in schema
|
||||
disallow additional properties not in schema or duplicated keys
|
||||
-summary
|
||||
print a summary at the end (ignored for junit output)
|
||||
-v show version information
|
||||
|
|
@ -117,4 +117,4 @@ Website powered by <a href=https://gohugo.io/>Hugo</a>
|
|||
</div>
|
||||
<script defer src=/js/prism.js></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in a new issue