From 87d977d198ef55c4292012cef8440475f200fd6a Mon Sep 17 00:00:00 2001 From: nishant Date: Fri, 19 Feb 2021 11:17:02 +0530 Subject: [PATCH 01/26] deployment template WIP --- .../deployment-template.md | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/docs/user-guide/creating-application/deployment-template.md b/docs/user-guide/creating-application/deployment-template.md index bfa7b9faf5..698bbd557e 100644 --- a/docs/user-guide/creating-application/deployment-template.md +++ b/docs/user-guide/creating-application/deployment-template.md @@ -32,16 +32,47 @@ This defines ports on which application services will be exposed to other servic ```yaml ContainerPort: - name: app + - name: app port: 8080 servicePort: 80 + envoyPort: 8799 + useHTTP2: true + supportStreaming: true + idleTimeout: 1800s + servicemonitor: + enabled: true + path: /metrics + scheme: 'http' + interval: 30s + scrapeTimeout: 20s + metricRelabelings: + - sourceLabels: [namespace] + regex: '(.*)' + replacement: myapp + targetLabel: target_namespace ``` -| Key | Description | +| Key | Description |optional| | :--- | :--- | -| `name` | name of the container | -| `port` | port for the container | -| `servicePort` | service port for the container | +| `name` | name of the container port| +| `port` | port no in container | +| `servicePort` | port In service | +| `envoyPort` | envoy proxy port | +| `useHTTP2`| envoy should use http2 as proxy | +| `supportStreaming` | streaming should be enabled | +| `idleTimeout` | ideal timeout for envoy proxy | +| `servicemonitor.` | this configuration is directly passed to service monitor | +| `servicemonitor.enabled`| serviceMonitor enabled | +| `servicemonitor.path`| path for serviceMonitor target| +| `servicemonitor.scheme`| scheme for serviceMonitor target | +| `servicemonitor.interval`| polling interval for serviceMonitor| +| `servicemonitor.scrapeTimeout`| timeout for serviceMonitor target | +| `servicemonitor.metricRelabelings`| metrics relabling for serviceMonitor | https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#relabelconfig +| `servicemonitor.metricRelabelings.sourceLabels`| | +| `servicemonitor.metricRelabelings.regex`| | +| `servicemonitor.metricRelabelings.replacement`| | +| `servicemonitor.metricRelabelings.targetLabel`| | + ### Liveness Probe @@ -51,6 +82,12 @@ If this check fails, kubernetes restarts the pod. This should return error code LivenessProbe: Path: "" port: 8080 + scheme: "" + httpHeader: + name: "" + value: "" + tcp: false + command: [] initialDelaySeconds: 20 periodSeconds: 10 successThreshold: 1 @@ -58,14 +95,20 @@ LivenessProbe: failureThreshold: 3 ``` -| Key | Description | + +| Key | Description | optional | :--- | :--- | -| `Path` | It define the path where the liveness needs to be checked. | +| `Path` | It define the path where the Liveness needs to be checked. | +| `port` | port for Liveness probe | +| `scheme` | scheme for Liveness probe | optional +| `httpHeader` | headers for Liveness probe request in key val pair (http/https) | optional | `failureThreshold` | It defines the maximum number of failures that are acceptable before a given container is not considered as live. | | `initialDelaySeconds` | It defines the time to wait before a given container is checked for liveliness. | | `periodSeconds` | It defines the time to check a given container for liveness. | | `successThreshold` | It defines the number of successes required before a given container is said to fulfil the liveness probe. | | `timeoutSeconds` | It defines the time for checking timeout. | +| `tcp` | | +| `command`| ### Readiness Probe From f60ec43989d3179110fa73c29ab1639202e4fe80 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Tue, 5 Oct 2021 11:38:30 +0530 Subject: [PATCH 02/26] json schema --- .../gojsonpointer/LICENSE-APACHE-2.0.txt | 202 +++ .../xeipuuv/gojsonpointer/README.md | 41 + .../xeipuuv/gojsonpointer/pointer.go | 211 ++++ .../gojsonreference/LICENSE-APACHE-2.0.txt | 202 +++ .../xeipuuv/gojsonreference/README.md | 10 + .../xeipuuv/gojsonreference/reference.go | 147 +++ .../xeipuuv/gojsonschema/.gitignore | 3 + .../xeipuuv/gojsonschema/.travis.yml | 9 + .../gojsonschema/LICENSE-APACHE-2.0.txt | 202 +++ .../github.com/xeipuuv/gojsonschema/README.md | 466 +++++++ .../github.com/xeipuuv/gojsonschema/draft.go | 125 ++ .../github.com/xeipuuv/gojsonschema/errors.go | 364 ++++++ .../xeipuuv/gojsonschema/format_checkers.go | 393 ++++++ .../xeipuuv/gojsonschema/glide.yaml | 13 + vendor/github.com/xeipuuv/gojsonschema/go.mod | 7 + vendor/github.com/xeipuuv/gojsonschema/go.sum | 11 + .../xeipuuv/gojsonschema/internalLog.go | 37 + .../xeipuuv/gojsonschema/jsonContext.go | 73 ++ .../xeipuuv/gojsonschema/jsonLoader.go | 386 ++++++ .../xeipuuv/gojsonschema/locales.go | 472 +++++++ .../github.com/xeipuuv/gojsonschema/result.go | 220 ++++ .../github.com/xeipuuv/gojsonschema/schema.go | 1087 +++++++++++++++++ .../xeipuuv/gojsonschema/schemaLoader.go | 206 ++++ .../xeipuuv/gojsonschema/schemaPool.go | 215 ++++ .../gojsonschema/schemaReferencePool.go | 68 ++ .../xeipuuv/gojsonschema/schemaType.go | 83 ++ .../xeipuuv/gojsonschema/subSchema.go | 149 +++ .../github.com/xeipuuv/gojsonschema/types.go | 62 + .../github.com/xeipuuv/gojsonschema/utils.go | 197 +++ .../xeipuuv/gojsonschema/validation.go | 858 +++++++++++++ 30 files changed, 6519 insertions(+) create mode 100644 vendor/github.com/xeipuuv/gojsonpointer/LICENSE-APACHE-2.0.txt create mode 100644 vendor/github.com/xeipuuv/gojsonpointer/README.md create mode 100644 vendor/github.com/xeipuuv/gojsonpointer/pointer.go create mode 100644 vendor/github.com/xeipuuv/gojsonreference/LICENSE-APACHE-2.0.txt create mode 100644 vendor/github.com/xeipuuv/gojsonreference/README.md create mode 100644 vendor/github.com/xeipuuv/gojsonreference/reference.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/.gitignore create mode 100644 vendor/github.com/xeipuuv/gojsonschema/.travis.yml create mode 100644 vendor/github.com/xeipuuv/gojsonschema/LICENSE-APACHE-2.0.txt create mode 100644 vendor/github.com/xeipuuv/gojsonschema/README.md create mode 100644 vendor/github.com/xeipuuv/gojsonschema/draft.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/errors.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/format_checkers.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/glide.yaml create mode 100644 vendor/github.com/xeipuuv/gojsonschema/go.mod create mode 100644 vendor/github.com/xeipuuv/gojsonschema/go.sum create mode 100644 vendor/github.com/xeipuuv/gojsonschema/internalLog.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/jsonContext.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/locales.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/result.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schema.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schemaLoader.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schemaPool.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schemaType.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/subSchema.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/types.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/utils.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/validation.go diff --git a/vendor/github.com/xeipuuv/gojsonpointer/LICENSE-APACHE-2.0.txt b/vendor/github.com/xeipuuv/gojsonpointer/LICENSE-APACHE-2.0.txt new file mode 100644 index 0000000000..55ede8a42c --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonpointer/LICENSE-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 xeipuuv + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/xeipuuv/gojsonpointer/README.md b/vendor/github.com/xeipuuv/gojsonpointer/README.md new file mode 100644 index 0000000000..00059242ca --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonpointer/README.md @@ -0,0 +1,41 @@ +# gojsonpointer +An implementation of JSON Pointer - Go language + +## Usage + jsonText := `{ + "name": "Bobby B", + "occupation": { + "title" : "King", + "years" : 15, + "heir" : "Joffrey B" + } + }` + + var jsonDocument map[string]interface{} + json.Unmarshal([]byte(jsonText), &jsonDocument) + + //create a JSON pointer + pointerString := "/occupation/title" + pointer, _ := NewJsonPointer(pointerString) + + //SET a new value for the "title" in the document + pointer.Set(jsonDocument, "Supreme Leader of Westeros") + + //GET the new "title" from the document + title, _, _ := pointer.Get(jsonDocument) + fmt.Println(title) //outputs "Supreme Leader of Westeros" + + //DELETE the "heir" from the document + deletePointer := NewJsonPointer("/occupation/heir") + deletePointer.Delete(jsonDocument) + + b, _ := json.Marshal(jsonDocument) + fmt.Println(string(b)) + //outputs `{"name":"Bobby B","occupation":{"title":"Supreme Leader of Westeros","years":15}}` + + +## References +http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07 + +### Note +The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented. diff --git a/vendor/github.com/xeipuuv/gojsonpointer/pointer.go b/vendor/github.com/xeipuuv/gojsonpointer/pointer.go new file mode 100644 index 0000000000..7faf5d7f94 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonpointer/pointer.go @@ -0,0 +1,211 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonpointer +// repository-desc An implementation of JSON Pointer - Go language +// +// description Main and unique file. +// +// created 25-02-2013 + +package gojsonpointer + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +const ( + const_empty_pointer = `` + const_pointer_separator = `/` + + const_invalid_start = `JSON pointer must be empty or start with a "` + const_pointer_separator + `"` +) + +type implStruct struct { + mode string // "SET" or "GET" + + inDocument interface{} + + setInValue interface{} + + getOutNode interface{} + getOutKind reflect.Kind + outError error +} + +type JsonPointer struct { + referenceTokens []string +} + +// NewJsonPointer parses the given string JSON pointer and returns an object +func NewJsonPointer(jsonPointerString string) (p JsonPointer, err error) { + + // Pointer to the root of the document + if len(jsonPointerString) == 0 { + // Keep referenceTokens nil + return + } + if jsonPointerString[0] != '/' { + return p, errors.New(const_invalid_start) + } + + p.referenceTokens = strings.Split(jsonPointerString[1:], const_pointer_separator) + return +} + +// Uses the pointer to retrieve a value from a JSON document +func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) { + + is := &implStruct{mode: "GET", inDocument: document} + p.implementation(is) + return is.getOutNode, is.getOutKind, is.outError + +} + +// Uses the pointer to update a value from a JSON document +func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) { + + is := &implStruct{mode: "SET", inDocument: document, setInValue: value} + p.implementation(is) + return document, is.outError + +} + +// Uses the pointer to delete a value from a JSON document +func (p *JsonPointer) Delete(document interface{}) (interface{}, error) { + is := &implStruct{mode: "DEL", inDocument: document} + p.implementation(is) + return document, is.outError +} + +// Both Get and Set functions use the same implementation to avoid code duplication +func (p *JsonPointer) implementation(i *implStruct) { + + kind := reflect.Invalid + + // Full document when empty + if len(p.referenceTokens) == 0 { + i.getOutNode = i.inDocument + i.outError = nil + i.getOutKind = kind + i.outError = nil + return + } + + node := i.inDocument + + previousNodes := make([]interface{}, len(p.referenceTokens)) + previousTokens := make([]string, len(p.referenceTokens)) + + for ti, token := range p.referenceTokens { + + isLastToken := ti == len(p.referenceTokens)-1 + previousNodes[ti] = node + previousTokens[ti] = token + + switch v := node.(type) { + + case map[string]interface{}: + decodedToken := decodeReferenceToken(token) + if _, ok := v[decodedToken]; ok { + node = v[decodedToken] + if isLastToken && i.mode == "SET" { + v[decodedToken] = i.setInValue + } else if isLastToken && i.mode =="DEL" { + delete(v,decodedToken) + } + } else if (isLastToken && i.mode == "SET") { + v[decodedToken] = i.setInValue + } else { + i.outError = fmt.Errorf("Object has no key '%s'", decodedToken) + i.getOutKind = reflect.Map + i.getOutNode = nil + return + } + + case []interface{}: + tokenIndex, err := strconv.Atoi(token) + if err != nil { + i.outError = fmt.Errorf("Invalid array index '%s'", token) + i.getOutKind = reflect.Slice + i.getOutNode = nil + return + } + if tokenIndex < 0 || tokenIndex >= len(v) { + i.outError = fmt.Errorf("Out of bound array[0,%d] index '%d'", len(v), tokenIndex) + i.getOutKind = reflect.Slice + i.getOutNode = nil + return + } + + node = v[tokenIndex] + if isLastToken && i.mode == "SET" { + v[tokenIndex] = i.setInValue + } else if isLastToken && i.mode =="DEL" { + v[tokenIndex] = v[len(v)-1] + v[len(v)-1] = nil + v = v[:len(v)-1] + previousNodes[ti-1].(map[string]interface{})[previousTokens[ti-1]] = v + } + + default: + i.outError = fmt.Errorf("Invalid token reference '%s'", token) + i.getOutKind = reflect.ValueOf(node).Kind() + i.getOutNode = nil + return + } + + } + + i.getOutNode = node + i.getOutKind = reflect.ValueOf(node).Kind() + i.outError = nil +} + +// Pointer to string representation function +func (p *JsonPointer) String() string { + + if len(p.referenceTokens) == 0 { + return const_empty_pointer + } + + pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator) + + return pointerString +} + +// Specific JSON pointer encoding here +// ~0 => ~ +// ~1 => / +// ... and vice versa + +func decodeReferenceToken(token string) string { + step1 := strings.Replace(token, `~1`, `/`, -1) + step2 := strings.Replace(step1, `~0`, `~`, -1) + return step2 +} + +func encodeReferenceToken(token string) string { + step1 := strings.Replace(token, `~`, `~0`, -1) + step2 := strings.Replace(step1, `/`, `~1`, -1) + return step2 +} diff --git a/vendor/github.com/xeipuuv/gojsonreference/LICENSE-APACHE-2.0.txt b/vendor/github.com/xeipuuv/gojsonreference/LICENSE-APACHE-2.0.txt new file mode 100644 index 0000000000..55ede8a42c --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonreference/LICENSE-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 xeipuuv + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/xeipuuv/gojsonreference/README.md b/vendor/github.com/xeipuuv/gojsonreference/README.md new file mode 100644 index 0000000000..9ab6e1eb13 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonreference/README.md @@ -0,0 +1,10 @@ +# gojsonreference +An implementation of JSON Reference - Go language + +## Dependencies +https://github.com/xeipuuv/gojsonpointer + +## References +http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07 + +http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03 diff --git a/vendor/github.com/xeipuuv/gojsonreference/reference.go b/vendor/github.com/xeipuuv/gojsonreference/reference.go new file mode 100644 index 0000000000..6457291301 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonreference/reference.go @@ -0,0 +1,147 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonreference +// repository-desc An implementation of JSON Reference - Go language +// +// description Main and unique file. +// +// created 26-02-2013 + +package gojsonreference + +import ( + "errors" + "net/url" + "path/filepath" + "runtime" + "strings" + + "github.com/xeipuuv/gojsonpointer" +) + +const ( + const_fragment_char = `#` +) + +func NewJsonReference(jsonReferenceString string) (JsonReference, error) { + + var r JsonReference + err := r.parse(jsonReferenceString) + return r, err + +} + +type JsonReference struct { + referenceUrl *url.URL + referencePointer gojsonpointer.JsonPointer + + HasFullUrl bool + HasUrlPathOnly bool + HasFragmentOnly bool + HasFileScheme bool + HasFullFilePath bool +} + +func (r *JsonReference) GetUrl() *url.URL { + return r.referenceUrl +} + +func (r *JsonReference) GetPointer() *gojsonpointer.JsonPointer { + return &r.referencePointer +} + +func (r *JsonReference) String() string { + + if r.referenceUrl != nil { + return r.referenceUrl.String() + } + + if r.HasFragmentOnly { + return const_fragment_char + r.referencePointer.String() + } + + return r.referencePointer.String() +} + +func (r *JsonReference) IsCanonical() bool { + return (r.HasFileScheme && r.HasFullFilePath) || (!r.HasFileScheme && r.HasFullUrl) +} + +// "Constructor", parses the given string JSON reference +func (r *JsonReference) parse(jsonReferenceString string) (err error) { + + r.referenceUrl, err = url.Parse(jsonReferenceString) + if err != nil { + return + } + refUrl := r.referenceUrl + + if refUrl.Scheme != "" && refUrl.Host != "" { + r.HasFullUrl = true + } else { + if refUrl.Path != "" { + r.HasUrlPathOnly = true + } else if refUrl.RawQuery == "" && refUrl.Fragment != "" { + r.HasFragmentOnly = true + } + } + + r.HasFileScheme = refUrl.Scheme == "file" + if runtime.GOOS == "windows" { + // on Windows, a file URL may have an extra leading slash, and if it + // doesn't then its first component will be treated as the host by the + // Go runtime + if refUrl.Host == "" && strings.HasPrefix(refUrl.Path, "/") { + r.HasFullFilePath = filepath.IsAbs(refUrl.Path[1:]) + } else { + r.HasFullFilePath = filepath.IsAbs(refUrl.Host + refUrl.Path) + } + } else { + r.HasFullFilePath = filepath.IsAbs(refUrl.Path) + } + + // invalid json-pointer error means url has no json-pointer fragment. simply ignore error + r.referencePointer, _ = gojsonpointer.NewJsonPointer(refUrl.Fragment) + + return +} + +// Creates a new reference from a parent and a child +// If the child cannot inherit from the parent, an error is returned +func (r *JsonReference) Inherits(child JsonReference) (*JsonReference, error) { + if child.GetUrl() == nil { + return nil, errors.New("childUrl is nil!") + } + + if r.GetUrl() == nil { + return nil, errors.New("parentUrl is nil!") + } + + // Get a copy of the parent url to make sure we do not modify the original. + // URL reference resolving fails if the fragment of the child is empty, but the parent's is not. + // The fragment of the child must be used, so the fragment of the parent is manually removed. + parentUrl := *r.GetUrl() + parentUrl.Fragment = "" + + ref, err := NewJsonReference(parentUrl.ResolveReference(child.GetUrl()).String()) + if err != nil { + return nil, err + } + return &ref, err +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/.gitignore b/vendor/github.com/xeipuuv/gojsonschema/.gitignore new file mode 100644 index 0000000000..68e993ce3e --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/.gitignore @@ -0,0 +1,3 @@ +*.sw[nop] +*.iml +.vscode/ diff --git a/vendor/github.com/xeipuuv/gojsonschema/.travis.yml b/vendor/github.com/xeipuuv/gojsonschema/.travis.yml new file mode 100644 index 0000000000..3289001cd1 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - "1.11" + - "1.12" + - "1.13" +before_install: + - go get github.com/xeipuuv/gojsonreference + - go get github.com/xeipuuv/gojsonpointer + - go get github.com/stretchr/testify/assert diff --git a/vendor/github.com/xeipuuv/gojsonschema/LICENSE-APACHE-2.0.txt b/vendor/github.com/xeipuuv/gojsonschema/LICENSE-APACHE-2.0.txt new file mode 100644 index 0000000000..55ede8a42c --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/LICENSE-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 xeipuuv + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/xeipuuv/gojsonschema/README.md b/vendor/github.com/xeipuuv/gojsonschema/README.md new file mode 100644 index 0000000000..758f26df0f --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/README.md @@ -0,0 +1,466 @@ +[![GoDoc](https://godoc.org/github.com/xeipuuv/gojsonschema?status.svg)](https://godoc.org/github.com/xeipuuv/gojsonschema) +[![Build Status](https://travis-ci.org/xeipuuv/gojsonschema.svg)](https://travis-ci.org/xeipuuv/gojsonschema) +[![Go Report Card](https://goreportcard.com/badge/github.com/xeipuuv/gojsonschema)](https://goreportcard.com/report/github.com/xeipuuv/gojsonschema) + +# gojsonschema + +## Description + +An implementation of JSON Schema for the Go programming language. Supports draft-04, draft-06 and draft-07. + +References : + +* http://json-schema.org +* http://json-schema.org/latest/json-schema-core.html +* http://json-schema.org/latest/json-schema-validation.html + +## Installation + +``` +go get github.com/xeipuuv/gojsonschema +``` + +Dependencies : +* [github.com/xeipuuv/gojsonpointer](https://github.com/xeipuuv/gojsonpointer) +* [github.com/xeipuuv/gojsonreference](https://github.com/xeipuuv/gojsonreference) +* [github.com/stretchr/testify/assert](https://github.com/stretchr/testify#assert-package) + +## Usage + +### Example + +```go + +package main + +import ( + "fmt" + "github.com/xeipuuv/gojsonschema" +) + +func main() { + + schemaLoader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json") + documentLoader := gojsonschema.NewReferenceLoader("file:///home/me/document.json") + + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + panic(err.Error()) + } + + if result.Valid() { + fmt.Printf("The document is valid\n") + } else { + fmt.Printf("The document is not valid. see errors :\n") + for _, desc := range result.Errors() { + fmt.Printf("- %s\n", desc) + } + } +} + + +``` + +#### Loaders + +There are various ways to load your JSON data. +In order to load your schemas and documents, +first declare an appropriate loader : + +* Web / HTTP, using a reference : + +```go +loader := gojsonschema.NewReferenceLoader("http://www.some_host.com/schema.json") +``` + +* Local file, using a reference : + +```go +loader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json") +``` + +References use the URI scheme, the prefix (file://) and a full path to the file are required. + +* JSON strings : + +```go +loader := gojsonschema.NewStringLoader(`{"type": "string"}`) +``` + +* Custom Go types : + +```go +m := map[string]interface{}{"type": "string"} +loader := gojsonschema.NewGoLoader(m) +``` + +And + +```go +type Root struct { + Users []User `json:"users"` +} + +type User struct { + Name string `json:"name"` +} + +... + +data := Root{} +data.Users = append(data.Users, User{"John"}) +data.Users = append(data.Users, User{"Sophia"}) +data.Users = append(data.Users, User{"Bill"}) + +loader := gojsonschema.NewGoLoader(data) +``` + +#### Validation + +Once the loaders are set, validation is easy : + +```go +result, err := gojsonschema.Validate(schemaLoader, documentLoader) +``` + +Alternatively, you might want to load a schema only once and process to multiple validations : + +```go +schema, err := gojsonschema.NewSchema(schemaLoader) +... +result1, err := schema.Validate(documentLoader1) +... +result2, err := schema.Validate(documentLoader2) +... +// etc ... +``` + +To check the result : + +```go + if result.Valid() { + fmt.Printf("The document is valid\n") + } else { + fmt.Printf("The document is not valid. see errors :\n") + for _, err := range result.Errors() { + // Err implements the ResultError interface + fmt.Printf("- %s\n", err) + } + } +``` + + +## Loading local schemas + +By default `file` and `http(s)` references to external schemas are loaded automatically via the file system or via http(s). An external schema can also be loaded using a `SchemaLoader`. + +```go + sl := gojsonschema.NewSchemaLoader() + loader1 := gojsonschema.NewStringLoader(`{ "type" : "string" }`) + err := sl.AddSchema("http://some_host.com/string.json", loader1) +``` + +Alternatively if your schema already has an `$id` you can use the `AddSchemas` function +```go + loader2 := gojsonschema.NewStringLoader(`{ + "$id" : "http://some_host.com/maxlength.json", + "maxLength" : 5 + }`) + err = sl.AddSchemas(loader2) +``` + +The main schema should be passed to the `Compile` function. This main schema can then directly reference the added schemas without needing to download them. +```go + loader3 := gojsonschema.NewStringLoader(`{ + "$id" : "http://some_host.com/main.json", + "allOf" : [ + { "$ref" : "http://some_host.com/string.json" }, + { "$ref" : "http://some_host.com/maxlength.json" } + ] + }`) + + schema, err := sl.Compile(loader3) + + documentLoader := gojsonschema.NewStringLoader(`"hello world"`) + + result, err := schema.Validate(documentLoader) +``` + +It's also possible to pass a `ReferenceLoader` to the `Compile` function that references a loaded schema. + +```go +err = sl.AddSchemas(loader3) +schema, err := sl.Compile(gojsonschema.NewReferenceLoader("http://some_host.com/main.json")) +``` + +Schemas added by `AddSchema` and `AddSchemas` are only validated when the entire schema is compiled, unless meta-schema validation is used. + +## Using a specific draft +By default `gojsonschema` will try to detect the draft of a schema by using the `$schema` keyword and parse it in a strict draft-04, draft-06 or draft-07 mode. If `$schema` is missing, or the draft version is not explicitely set, a hybrid mode is used which merges together functionality of all drafts into one mode. + +Autodectection can be turned off with the `AutoDetect` property. Specific draft versions can be specified with the `Draft` property. + +```go +sl := gojsonschema.NewSchemaLoader() +sl.Draft = gojsonschema.Draft7 +sl.AutoDetect = false +``` + +If autodetection is on (default), a draft-07 schema can savely reference draft-04 schemas and vice-versa, as long as `$schema` is specified in all schemas. + +## Meta-schema validation +Schemas that are added using the `AddSchema`, `AddSchemas` and `Compile` can be validated against their meta-schema by setting the `Validate` property. + +The following example will produce an error as `multipleOf` must be a number. If `Validate` is off (default), this error is only returned at the `Compile` step. + +```go +sl := gojsonschema.NewSchemaLoader() +sl.Validate = true +err := sl.AddSchemas(gojsonschema.NewStringLoader(`{ + $id" : "http://some_host.com/invalid.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "multipleOf" : true +}`)) + ``` +``` + ``` + +Errors returned by meta-schema validation are more readable and contain more information, which helps significantly if you are developing a schema. + +Meta-schema validation also works with a custom `$schema`. In case `$schema` is missing, or `AutoDetect` is set to `false`, the meta-schema of the used draft is used. + + +## Working with Errors + +The library handles string error codes which you can customize by creating your own gojsonschema.locale and setting it +```go +gojsonschema.Locale = YourCustomLocale{} +``` + +However, each error contains additional contextual information. + +Newer versions of `gojsonschema` may have new additional errors, so code that uses a custom locale will need to be updated when this happens. + +**err.Type()**: *string* Returns the "type" of error that occurred. Note you can also type check. See below + +Note: An error of RequiredType has an err.Type() return value of "required" + + "required": RequiredError + "invalid_type": InvalidTypeError + "number_any_of": NumberAnyOfError + "number_one_of": NumberOneOfError + "number_all_of": NumberAllOfError + "number_not": NumberNotError + "missing_dependency": MissingDependencyError + "internal": InternalError + "const": ConstEror + "enum": EnumError + "array_no_additional_items": ArrayNoAdditionalItemsError + "array_min_items": ArrayMinItemsError + "array_max_items": ArrayMaxItemsError + "unique": ItemsMustBeUniqueError + "contains" : ArrayContainsError + "array_min_properties": ArrayMinPropertiesError + "array_max_properties": ArrayMaxPropertiesError + "additional_property_not_allowed": AdditionalPropertyNotAllowedError + "invalid_property_pattern": InvalidPropertyPatternError + "invalid_property_name": InvalidPropertyNameError + "string_gte": StringLengthGTEError + "string_lte": StringLengthLTEError + "pattern": DoesNotMatchPatternError + "multiple_of": MultipleOfError + "number_gte": NumberGTEError + "number_gt": NumberGTError + "number_lte": NumberLTEError + "number_lt": NumberLTError + "condition_then" : ConditionThenError + "condition_else" : ConditionElseError + +**err.Value()**: *interface{}* Returns the value given + +**err.Context()**: *gojsonschema.JsonContext* Returns the context. This has a String() method that will print something like this: (root).firstName + +**err.Field()**: *string* Returns the fieldname in the format firstName, or for embedded properties, person.firstName. This returns the same as the String() method on *err.Context()* but removes the (root). prefix. + +**err.Description()**: *string* The error description. This is based on the locale you are using. See the beginning of this section for overwriting the locale with a custom implementation. + +**err.DescriptionFormat()**: *string* The error description format. This is relevant if you are adding custom validation errors afterwards to the result. + +**err.Details()**: *gojsonschema.ErrorDetails* Returns a map[string]interface{} of additional error details specific to the error. For example, GTE errors will have a "min" value, LTE will have a "max" value. See errors.go for a full description of all the error details. Every error always contains a "field" key that holds the value of *err.Field()* + +Note in most cases, the err.Details() will be used to generate replacement strings in your locales, and not used directly. These strings follow the text/template format i.e. +``` +{{.field}} must be greater than or equal to {{.min}} +``` + +The library allows you to specify custom template functions, should you require more complex error message handling. +```go +gojsonschema.ErrorTemplateFuncs = map[string]interface{}{ + "allcaps": func(s string) string { + return strings.ToUpper(s) + }, +} +``` + +Given the above definition, you can use the custom function `"allcaps"` in your localization templates: +``` +{{allcaps .field}} must be greater than or equal to {{.min}} +``` + +The above error message would then be rendered with the `field` value in capital letters. For example: +``` +"PASSWORD must be greater than or equal to 8" +``` + +Learn more about what types of template functions you can use in `ErrorTemplateFuncs` by referring to Go's [text/template FuncMap](https://golang.org/pkg/text/template/#FuncMap) type. + +## Formats +JSON Schema allows for optional "format" property to validate instances against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this: + +````json +{"type": "string", "format": "email"} +```` + +Not all formats defined in draft-07 are available. Implemented formats are: + +* `date` +* `time` +* `date-time` +* `hostname`. Subdomains that start with a number are also supported, but this means that it doesn't strictly follow [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5) and has the implication that ipv4 addresses are also recognized as valid hostnames. +* `email`. Go's email parser deviates slightly from [RFC5322](https://tools.ietf.org/html/rfc5322). Includes unicode support. +* `idn-email`. Same caveat as `email`. +* `ipv4` +* `ipv6` +* `uri`. Includes unicode support. +* `uri-reference`. Includes unicode support. +* `iri` +* `iri-reference` +* `uri-template` +* `uuid` +* `regex`. Go uses the [RE2](https://github.com/google/re2/wiki/Syntax) engine and is not [ECMA262](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) compatible. +* `json-pointer` +* `relative-json-pointer` + +`email`, `uri` and `uri-reference` use the same validation code as their unicode counterparts `idn-email`, `iri` and `iri-reference`. If you rely on unicode support you should use the specific +unicode enabled formats for the sake of interoperability as other implementations might not support unicode in the regular formats. + +The validation code for `uri`, `idn-email` and their relatives use mostly standard library code. + +For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this: + +```go +// Define the format checker +type RoleFormatChecker struct {} + +// Ensure it meets the gojsonschema.FormatChecker interface +func (f RoleFormatChecker) IsFormat(input interface{}) bool { + + asString, ok := input.(string) + if ok == false { + return false + } + + return strings.HasPrefix("ROLE_", asString) +} + +// Add it to the library +gojsonschema.FormatCheckers.Add("role", RoleFormatChecker{}) +```` + +Now to use in your json schema: +````json +{"type": "string", "format": "role"} +```` + +Another example would be to check if the provided integer matches an id on database: + +JSON schema: +```json +{"type": "integer", "format": "ValidUserId"} +``` + +```go +// Define the format checker +type ValidUserIdFormatChecker struct {} + +// Ensure it meets the gojsonschema.FormatChecker interface +func (f ValidUserIdFormatChecker) IsFormat(input interface{}) bool { + + asFloat64, ok := input.(float64) // Numbers are always float64 here + if ok == false { + return false + } + + // XXX + // do the magic on the database looking for the int(asFloat64) + + return true +} + +// Add it to the library +gojsonschema.FormatCheckers.Add("ValidUserId", ValidUserIdFormatChecker{}) +```` + +Formats can also be removed, for example if you want to override one of the formats that is defined by default. + +```go +gojsonschema.FormatCheckers.Remove("hostname") +``` + + +## Additional custom validation +After the validation has run and you have the results, you may add additional +errors using `Result.AddError`. This is useful to maintain the same format within the resultset instead +of having to add special exceptions for your own errors. Below is an example. + +```go +type AnswerInvalidError struct { + gojsonschema.ResultErrorFields +} + +func newAnswerInvalidError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *AnswerInvalidError { + err := AnswerInvalidError{} + err.SetContext(context) + err.SetType("custom_invalid_error") + // it is important to use SetDescriptionFormat() as this is used to call SetDescription() after it has been parsed + // using the description of err will be overridden by this. + err.SetDescriptionFormat("Answer to the Ultimate Question of Life, the Universe, and Everything is {{.answer}}") + err.SetValue(value) + err.SetDetails(details) + + return &err +} + +func main() { + // ... + schema, err := gojsonschema.NewSchema(schemaLoader) + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + + if true { // some validation + jsonContext := gojsonschema.NewJsonContext("question", nil) + errDetail := gojsonschema.ErrorDetails{ + "answer": 42, + } + result.AddError( + newAnswerInvalidError( + gojsonschema.NewJsonContext("answer", jsonContext), + 52, + errDetail, + ), + errDetail, + ) + } + + return result, err + +} +``` + +This is especially useful if you want to add validation beyond what the +json schema drafts can provide such business specific logic. + +## Uses + +gojsonschema uses the following test suite : + +https://github.com/json-schema/JSON-Schema-Test-Suite diff --git a/vendor/github.com/xeipuuv/gojsonschema/draft.go b/vendor/github.com/xeipuuv/gojsonschema/draft.go new file mode 100644 index 0000000000..61298e7aa0 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/draft.go @@ -0,0 +1,125 @@ +// Copyright 2018 johandorland ( https://github.com/johandorland ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gojsonschema + +import ( + "errors" + "math" + "reflect" + + "github.com/xeipuuv/gojsonreference" +) + +// Draft is a JSON-schema draft version +type Draft int + +// Supported Draft versions +const ( + Draft4 Draft = 4 + Draft6 Draft = 6 + Draft7 Draft = 7 + Hybrid Draft = math.MaxInt32 +) + +type draftConfig struct { + Version Draft + MetaSchemaURL string + MetaSchema string +} +type draftConfigs []draftConfig + +var drafts draftConfigs + +func init() { + drafts = []draftConfig{ + { + Version: Draft4, + MetaSchemaURL: "http://json-schema.org/draft-04/schema", + MetaSchema: `{"id":"http://json-schema.org/draft-04/schema#","$schema":"http://json-schema.org/draft-04/schema#","description":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"positiveInteger":{"type":"integer","minimum":0},"positiveIntegerDefault0":{"allOf":[{"$ref":"#/definitions/positiveInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true}},"type":"object","properties":{"id":{"type":"string"},"$schema":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"multipleOf":{"type":"number","minimum":0,"exclusiveMinimum":true},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"boolean","default":false},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"boolean","default":false},"maxLength":{"$ref":"#/definitions/positiveInteger"},"minLength":{"$ref":"#/definitions/positiveIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/positiveInteger"},"minItems":{"$ref":"#/definitions/positiveIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxProperties":{"$ref":"#/definitions/positiveInteger"},"minProperties":{"$ref":"#/definitions/positiveIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"dependencies":{"exclusiveMaximum":["maximum"],"exclusiveMinimum":["minimum"]},"default":{}}`, + }, + { + Version: Draft6, + MetaSchemaURL: "http://json-schema.org/draft-06/schema", + MetaSchema: `{"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}`, + }, + { + Version: Draft7, + MetaSchemaURL: "http://json-schema.org/draft-07/schema", + MetaSchema: `{"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}`, + }, + } +} + +func (dc draftConfigs) GetMetaSchema(url string) string { + for _, config := range dc { + if config.MetaSchemaURL == url { + return config.MetaSchema + } + } + return "" +} +func (dc draftConfigs) GetDraftVersion(url string) *Draft { + for _, config := range dc { + if config.MetaSchemaURL == url { + return &config.Version + } + } + return nil +} +func (dc draftConfigs) GetSchemaURL(draft Draft) string { + for _, config := range dc { + if config.Version == draft { + return config.MetaSchemaURL + } + } + return "" +} + +func parseSchemaURL(documentNode interface{}) (string, *Draft, error) { + + if isKind(documentNode, reflect.Bool) { + return "", nil, nil + } + + if !isKind(documentNode, reflect.Map) { + return "", nil, errors.New("schema is invalid") + } + + m := documentNode.(map[string]interface{}) + + if existsMapKey(m, KEY_SCHEMA) { + if !isKind(m[KEY_SCHEMA], reflect.String) { + return "", nil, errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{ + "key": KEY_SCHEMA, + "type": TYPE_STRING, + }, + )) + } + + schemaReference, err := gojsonreference.NewJsonReference(m[KEY_SCHEMA].(string)) + + if err != nil { + return "", nil, err + } + + schema := schemaReference.String() + + return schema, drafts.GetDraftVersion(schema), nil + } + + return "", nil, nil +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/errors.go b/vendor/github.com/xeipuuv/gojsonschema/errors.go new file mode 100644 index 0000000000..e4e9814f31 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/errors.go @@ -0,0 +1,364 @@ +package gojsonschema + +import ( + "bytes" + "sync" + "text/template" +) + +var errorTemplates = errorTemplate{template.New("errors-new"), sync.RWMutex{}} + +// template.Template is not thread-safe for writing, so some locking is done +// sync.RWMutex is used for efficiently locking when new templates are created +type errorTemplate struct { + *template.Template + sync.RWMutex +} + +type ( + + // FalseError. ErrorDetails: - + FalseError struct { + ResultErrorFields + } + + // RequiredError indicates that a required field is missing + // ErrorDetails: property string + RequiredError struct { + ResultErrorFields + } + + // InvalidTypeError indicates that a field has the incorrect type + // ErrorDetails: expected, given + InvalidTypeError struct { + ResultErrorFields + } + + // NumberAnyOfError is produced in case of a failing "anyOf" validation + // ErrorDetails: - + NumberAnyOfError struct { + ResultErrorFields + } + + // NumberOneOfError is produced in case of a failing "oneOf" validation + // ErrorDetails: - + NumberOneOfError struct { + ResultErrorFields + } + + // NumberAllOfError is produced in case of a failing "allOf" validation + // ErrorDetails: - + NumberAllOfError struct { + ResultErrorFields + } + + // NumberNotError is produced if a "not" validation failed + // ErrorDetails: - + NumberNotError struct { + ResultErrorFields + } + + // MissingDependencyError is produced in case of a "missing dependency" problem + // ErrorDetails: dependency + MissingDependencyError struct { + ResultErrorFields + } + + // InternalError indicates an internal error + // ErrorDetails: error + InternalError struct { + ResultErrorFields + } + + // ConstError indicates a const error + // ErrorDetails: allowed + ConstError struct { + ResultErrorFields + } + + // EnumError indicates an enum error + // ErrorDetails: allowed + EnumError struct { + ResultErrorFields + } + + // ArrayNoAdditionalItemsError is produced if additional items were found, but not allowed + // ErrorDetails: - + ArrayNoAdditionalItemsError struct { + ResultErrorFields + } + + // ArrayMinItemsError is produced if an array contains less items than the allowed minimum + // ErrorDetails: min + ArrayMinItemsError struct { + ResultErrorFields + } + + // ArrayMaxItemsError is produced if an array contains more items than the allowed maximum + // ErrorDetails: max + ArrayMaxItemsError struct { + ResultErrorFields + } + + // ItemsMustBeUniqueError is produced if an array requires unique items, but contains non-unique items + // ErrorDetails: type, i, j + ItemsMustBeUniqueError struct { + ResultErrorFields + } + + // ArrayContainsError is produced if an array contains invalid items + // ErrorDetails: + ArrayContainsError struct { + ResultErrorFields + } + + // ArrayMinPropertiesError is produced if an object contains less properties than the allowed minimum + // ErrorDetails: min + ArrayMinPropertiesError struct { + ResultErrorFields + } + + // ArrayMaxPropertiesError is produced if an object contains more properties than the allowed maximum + // ErrorDetails: max + ArrayMaxPropertiesError struct { + ResultErrorFields + } + + // AdditionalPropertyNotAllowedError is produced if an object has additional properties, but not allowed + // ErrorDetails: property + AdditionalPropertyNotAllowedError struct { + ResultErrorFields + } + + // InvalidPropertyPatternError is produced if an pattern was found + // ErrorDetails: property, pattern + InvalidPropertyPatternError struct { + ResultErrorFields + } + + // InvalidPropertyNameError is produced if an invalid-named property was found + // ErrorDetails: property + InvalidPropertyNameError struct { + ResultErrorFields + } + + // StringLengthGTEError is produced if a string is shorter than the minimum required length + // ErrorDetails: min + StringLengthGTEError struct { + ResultErrorFields + } + + // StringLengthLTEError is produced if a string is longer than the maximum allowed length + // ErrorDetails: max + StringLengthLTEError struct { + ResultErrorFields + } + + // DoesNotMatchPatternError is produced if a string does not match the defined pattern + // ErrorDetails: pattern + DoesNotMatchPatternError struct { + ResultErrorFields + } + + // DoesNotMatchFormatError is produced if a string does not match the defined format + // ErrorDetails: format + DoesNotMatchFormatError struct { + ResultErrorFields + } + + // MultipleOfError is produced if a number is not a multiple of the defined multipleOf + // ErrorDetails: multiple + MultipleOfError struct { + ResultErrorFields + } + + // NumberGTEError is produced if a number is lower than the allowed minimum + // ErrorDetails: min + NumberGTEError struct { + ResultErrorFields + } + + // NumberGTError is produced if a number is lower than, or equal to the specified minimum, and exclusiveMinimum is set + // ErrorDetails: min + NumberGTError struct { + ResultErrorFields + } + + // NumberLTEError is produced if a number is higher than the allowed maximum + // ErrorDetails: max + NumberLTEError struct { + ResultErrorFields + } + + // NumberLTError is produced if a number is higher than, or equal to the specified maximum, and exclusiveMaximum is set + // ErrorDetails: max + NumberLTError struct { + ResultErrorFields + } + + // ConditionThenError is produced if a condition's "then" validation is invalid + // ErrorDetails: - + ConditionThenError struct { + ResultErrorFields + } + + // ConditionElseError is produced if a condition's "else" condition is invalid + // ErrorDetails: - + ConditionElseError struct { + ResultErrorFields + } +) + +// newError takes a ResultError type and sets the type, context, description, details, value, and field +func newError(err ResultError, context *JsonContext, value interface{}, locale locale, details ErrorDetails) { + var t string + var d string + switch err.(type) { + case *FalseError: + t = "false" + d = locale.False() + case *RequiredError: + t = "required" + d = locale.Required() + case *InvalidTypeError: + t = "invalid_type" + d = locale.InvalidType() + case *NumberAnyOfError: + t = "number_any_of" + d = locale.NumberAnyOf() + case *NumberOneOfError: + t = "number_one_of" + d = locale.NumberOneOf() + case *NumberAllOfError: + t = "number_all_of" + d = locale.NumberAllOf() + case *NumberNotError: + t = "number_not" + d = locale.NumberNot() + case *MissingDependencyError: + t = "missing_dependency" + d = locale.MissingDependency() + case *InternalError: + t = "internal" + d = locale.Internal() + case *ConstError: + t = "const" + d = locale.Const() + case *EnumError: + t = "enum" + d = locale.Enum() + case *ArrayNoAdditionalItemsError: + t = "array_no_additional_items" + d = locale.ArrayNoAdditionalItems() + case *ArrayMinItemsError: + t = "array_min_items" + d = locale.ArrayMinItems() + case *ArrayMaxItemsError: + t = "array_max_items" + d = locale.ArrayMaxItems() + case *ItemsMustBeUniqueError: + t = "unique" + d = locale.Unique() + case *ArrayContainsError: + t = "contains" + d = locale.ArrayContains() + case *ArrayMinPropertiesError: + t = "array_min_properties" + d = locale.ArrayMinProperties() + case *ArrayMaxPropertiesError: + t = "array_max_properties" + d = locale.ArrayMaxProperties() + case *AdditionalPropertyNotAllowedError: + t = "additional_property_not_allowed" + d = locale.AdditionalPropertyNotAllowed() + case *InvalidPropertyPatternError: + t = "invalid_property_pattern" + d = locale.InvalidPropertyPattern() + case *InvalidPropertyNameError: + t = "invalid_property_name" + d = locale.InvalidPropertyName() + case *StringLengthGTEError: + t = "string_gte" + d = locale.StringGTE() + case *StringLengthLTEError: + t = "string_lte" + d = locale.StringLTE() + case *DoesNotMatchPatternError: + t = "pattern" + d = locale.DoesNotMatchPattern() + case *DoesNotMatchFormatError: + t = "format" + d = locale.DoesNotMatchFormat() + case *MultipleOfError: + t = "multiple_of" + d = locale.MultipleOf() + case *NumberGTEError: + t = "number_gte" + d = locale.NumberGTE() + case *NumberGTError: + t = "number_gt" + d = locale.NumberGT() + case *NumberLTEError: + t = "number_lte" + d = locale.NumberLTE() + case *NumberLTError: + t = "number_lt" + d = locale.NumberLT() + case *ConditionThenError: + t = "condition_then" + d = locale.ConditionThen() + case *ConditionElseError: + t = "condition_else" + d = locale.ConditionElse() + } + + err.SetType(t) + err.SetContext(context) + err.SetValue(value) + err.SetDetails(details) + err.SetDescriptionFormat(d) + details["field"] = err.Field() + + if _, exists := details["context"]; !exists && context != nil { + details["context"] = context.String() + } + + err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details)) +} + +// formatErrorDescription takes a string in the default text/template +// format and converts it to a string with replacements. The fields come +// from the ErrorDetails struct and vary for each type of error. +func formatErrorDescription(s string, details ErrorDetails) string { + + var tpl *template.Template + var descrAsBuffer bytes.Buffer + var err error + + errorTemplates.RLock() + tpl = errorTemplates.Lookup(s) + errorTemplates.RUnlock() + + if tpl == nil { + errorTemplates.Lock() + tpl = errorTemplates.New(s) + + if ErrorTemplateFuncs != nil { + tpl.Funcs(ErrorTemplateFuncs) + } + + tpl, err = tpl.Parse(s) + errorTemplates.Unlock() + + if err != nil { + return err.Error() + } + } + + err = tpl.Execute(&descrAsBuffer, details) + if err != nil { + return err.Error() + } + + return descrAsBuffer.String() +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go new file mode 100644 index 0000000000..0aa2fb0520 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go @@ -0,0 +1,393 @@ +package gojsonschema + +import ( + "net" + "net/mail" + "net/url" + "regexp" + "strings" + "sync" + "time" +) + +type ( + // FormatChecker is the interface all formatters added to FormatCheckerChain must implement + FormatChecker interface { + // IsFormat checks if input has the correct format and type + IsFormat(input interface{}) bool + } + + // FormatCheckerChain holds the formatters + FormatCheckerChain struct { + formatters map[string]FormatChecker + } + + // EmailFormatChecker verifies email address formats + EmailFormatChecker struct{} + + // IPV4FormatChecker verifies IP addresses in the IPv4 format + IPV4FormatChecker struct{} + + // IPV6FormatChecker verifies IP addresses in the IPv6 format + IPV6FormatChecker struct{} + + // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6 + // + // Valid formats: + // Partial Time: HH:MM:SS + // Full Date: YYYY-MM-DD + // Full Time: HH:MM:SSZ-07:00 + // Date Time: YYYY-MM-DDTHH:MM:SSZ-0700 + // + // Where + // YYYY = 4DIGIT year + // MM = 2DIGIT month ; 01-12 + // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year + // HH = 2DIGIT hour ; 00-23 + // MM = 2DIGIT ; 00-59 + // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules + // T = Literal + // Z = Literal + // + // Note: Nanoseconds are also suported in all formats + // + // http://tools.ietf.org/html/rfc3339#section-5.6 + DateTimeFormatChecker struct{} + + // DateFormatChecker verifies date formats + // + // Valid format: + // Full Date: YYYY-MM-DD + // + // Where + // YYYY = 4DIGIT year + // MM = 2DIGIT month ; 01-12 + // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year + DateFormatChecker struct{} + + // TimeFormatChecker verifies time formats + // + // Valid formats: + // Partial Time: HH:MM:SS + // Full Time: HH:MM:SSZ-07:00 + // + // Where + // HH = 2DIGIT hour ; 00-23 + // MM = 2DIGIT ; 00-59 + // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules + // T = Literal + // Z = Literal + TimeFormatChecker struct{} + + // URIFormatChecker validates a URI with a valid Scheme per RFC3986 + URIFormatChecker struct{} + + // URIReferenceFormatChecker validates a URI or relative-reference per RFC3986 + URIReferenceFormatChecker struct{} + + // URITemplateFormatChecker validates a URI template per RFC6570 + URITemplateFormatChecker struct{} + + // HostnameFormatChecker validates a hostname is in the correct format + HostnameFormatChecker struct{} + + // UUIDFormatChecker validates a UUID is in the correct format + UUIDFormatChecker struct{} + + // RegexFormatChecker validates a regex is in the correct format + RegexFormatChecker struct{} + + // JSONPointerFormatChecker validates a JSON Pointer per RFC6901 + JSONPointerFormatChecker struct{} + + // RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format + RelativeJSONPointerFormatChecker struct{} + + UnitChecker struct{} + MemoryChecker struct{} +) + +var ( + // FormatCheckers holds the valid formatters, and is a public variable + // so library users can add custom formatters + FormatCheckers = FormatCheckerChain{ + formatters: map[string]FormatChecker{ + "date": DateFormatChecker{}, + "time": TimeFormatChecker{}, + "date-time": DateTimeFormatChecker{}, + "hostname": HostnameFormatChecker{}, + "email": EmailFormatChecker{}, + "idn-email": EmailFormatChecker{}, + "ipv4": IPV4FormatChecker{}, + "ipv6": IPV6FormatChecker{}, + "uri": URIFormatChecker{}, + "uri-reference": URIReferenceFormatChecker{}, + "iri": URIFormatChecker{}, + "iri-reference": URIReferenceFormatChecker{}, + "uri-template": URITemplateFormatChecker{}, + "uuid": UUIDFormatChecker{}, + "regex": RegexFormatChecker{}, + "json-pointer": JSONPointerFormatChecker{}, + "relative-json-pointer": RelativeJSONPointerFormatChecker{}, + "cpu": UnitChecker{}, + "memory": MemoryChecker{}, + }, + } + + // Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname + rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`) + + // Use a regex to make sure curly brackets are balanced properly after validating it as a AURI + rxURITemplate = regexp.MustCompile("^([^{]*({[^}]*})?)*$") + + rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$") + + rxJSONPointer = regexp.MustCompile("^(?:/(?:[^~/]|~0|~1)*)*$") + + rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$") + + UChecker, _ = regexp.Compile("([0-9.]+)m") + MemChecker, _ = regexp.Compile("([0-9.]+)Mi") + + lock = new(sync.RWMutex) +) + +// Add adds a FormatChecker to the FormatCheckerChain +// The name used will be the value used for the format key in your json schema +func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain { + lock.Lock() + c.formatters[name] = f + lock.Unlock() + + return c +} + +// Remove deletes a FormatChecker from the FormatCheckerChain (if it exists) +func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain { + lock.Lock() + delete(c.formatters, name) + lock.Unlock() + + return c +} + +// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name +func (c *FormatCheckerChain) Has(name string) bool { + lock.RLock() + _, ok := c.formatters[name] + lock.RUnlock() + + return ok +} + +// IsFormat will check an input against a FormatChecker with the given name +// to see if it is the correct format +func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool { + lock.RLock() + f, ok := c.formatters[name] + lock.RUnlock() + + // If a format is unrecognized it should always pass validation + if !ok { + return true + } + + return f.IsFormat(input) +} + +// IsFormat checks if input is a correctly formatted e-mail address +func (f EmailFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + _, err := mail.ParseAddress(asString) + return err == nil +} + +// IsFormat checks if input is a correctly formatted IPv4-address +func (f IPV4FormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + // Credit: https://github.com/asaskevich/govalidator + ip := net.ParseIP(asString) + return ip != nil && strings.Contains(asString, ".") +} + +// IsFormat checks if input is a correctly formatted IPv6=address +func (f IPV6FormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + // Credit: https://github.com/asaskevich/govalidator + ip := net.ParseIP(asString) + return ip != nil && strings.Contains(asString, ":") +} + +// IsFormat checks if input is a correctly formatted date/time per RFC3339 5.6 +func (f DateTimeFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + formats := []string{ + "15:04:05", + "15:04:05Z07:00", + "2006-01-02", + time.RFC3339, + time.RFC3339Nano, + } + + for _, format := range formats { + if _, err := time.Parse(format, asString); err == nil { + return true + } + } + + return false +} + +// IsFormat checks if input is a correctly formatted date (YYYY-MM-DD) +func (f DateFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + _, err := time.Parse("2006-01-02", asString) + return err == nil +} + +// IsFormat checks if input correctly formatted time (HH:MM:SS or HH:MM:SSZ-07:00) +func (f TimeFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + if _, err := time.Parse("15:04:05Z07:00", asString); err == nil { + return true + } + + _, err := time.Parse("15:04:05", asString) + return err == nil +} + +// IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986 +func (f URIFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + u, err := url.Parse(asString) + + if err != nil || u.Scheme == "" { + return false + } + + return !strings.Contains(asString, `\`) +} + +// IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986 +func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + _, err := url.Parse(asString) + return err == nil && !strings.Contains(asString, `\`) +} + +// IsFormat checks if input is a correctly formatted URI template per RFC6570 +func (f URITemplateFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + u, err := url.Parse(asString) + if err != nil || strings.Contains(asString, `\`) { + return false + } + + return rxURITemplate.MatchString(u.Path) +} + +// IsFormat checks if input is a correctly formatted hostname +func (f HostnameFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + return rxHostname.MatchString(asString) && len(asString) < 256 +} + +// IsFormat checks if input is a correctly formatted UUID +func (f UUIDFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + return rxUUID.MatchString(asString) +} + +// IsFormat checks if input is a correctly formatted regular expression +func (f RegexFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + if asString == "" { + return true + } + _, err := regexp.Compile(asString) + return err == nil +} + +// IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901 +func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + return rxJSONPointer.MatchString(asString) +} + +// IsFormat checks if input is a correctly formatted relative JSON Pointer +func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + return rxRelJSONPointer.MatchString(asString) +} + +func (f UnitChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return true + } + + return UChecker.MatchString(asString) +} +func (f MemoryChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return true + } + + return MemChecker.MatchString(asString) +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/glide.yaml b/vendor/github.com/xeipuuv/gojsonschema/glide.yaml new file mode 100644 index 0000000000..ab6fb867c5 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/glide.yaml @@ -0,0 +1,13 @@ +package: github.com/xeipuuv/gojsonschema +license: Apache 2.0 +import: +- package: github.com/xeipuuv/gojsonschema + +- package: github.com/xeipuuv/gojsonpointer + +- package: github.com/xeipuuv/gojsonreference + +testImport: +- package: github.com/stretchr/testify + subpackages: + - assert diff --git a/vendor/github.com/xeipuuv/gojsonschema/go.mod b/vendor/github.com/xeipuuv/gojsonschema/go.mod new file mode 100644 index 0000000000..b709d7fcd6 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/go.mod @@ -0,0 +1,7 @@ +module github.com/xeipuuv/gojsonschema + +require ( + github.com/stretchr/testify v1.3.0 + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 +) diff --git a/vendor/github.com/xeipuuv/gojsonschema/go.sum b/vendor/github.com/xeipuuv/gojsonschema/go.sum new file mode 100644 index 0000000000..0e865ac759 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= diff --git a/vendor/github.com/xeipuuv/gojsonschema/internalLog.go b/vendor/github.com/xeipuuv/gojsonschema/internalLog.go new file mode 100644 index 0000000000..4ef7a8d03e --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/internalLog.go @@ -0,0 +1,37 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Very simple log wrapper. +// Used for debugging/testing purposes. +// +// created 01-01-2015 + +package gojsonschema + +import ( + "log" +) + +const internalLogEnabled = false + +func internalLog(format string, v ...interface{}) { + log.Printf(format, v...) +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go b/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go new file mode 100644 index 0000000000..0e979707b4 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go @@ -0,0 +1,73 @@ +// Copyright 2013 MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author tolsen +// author-github https://github.com/tolsen +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Implements a persistent (immutable w/ shared structure) singly-linked list of strings for the purpose of storing a json context +// +// created 04-09-2013 + +package gojsonschema + +import "bytes" + +// JsonContext implements a persistent linked-list of strings +type JsonContext struct { + head string + tail *JsonContext +} + +// NewJsonContext creates a new JsonContext +func NewJsonContext(head string, tail *JsonContext) *JsonContext { + return &JsonContext{head, tail} +} + +// String displays the context in reverse. +// This plays well with the data structure's persistent nature with +// Cons and a json document's tree structure. +func (c *JsonContext) String(del ...string) string { + byteArr := make([]byte, 0, c.stringLen()) + buf := bytes.NewBuffer(byteArr) + c.writeStringToBuffer(buf, del) + + return buf.String() +} + +func (c *JsonContext) stringLen() int { + length := 0 + if c.tail != nil { + length = c.tail.stringLen() + 1 // add 1 for "." + } + + length += len(c.head) + return length +} + +func (c *JsonContext) writeStringToBuffer(buf *bytes.Buffer, del []string) { + if c.tail != nil { + c.tail.writeStringToBuffer(buf, del) + + if len(del) > 0 { + buf.WriteString(del[0]) + } else { + buf.WriteString(".") + } + } + + buf.WriteString(c.head) +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go b/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go new file mode 100644 index 0000000000..5d88af263e --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go @@ -0,0 +1,386 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Different strategies to load JSON files. +// Includes References (file and HTTP), JSON strings and Go types. +// +// created 01-02-2015 + +package gojsonschema + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/xeipuuv/gojsonreference" +) + +var osFS = osFileSystem(os.Open) + +// JSONLoader defines the JSON loader interface +type JSONLoader interface { + JsonSource() interface{} + LoadJSON() (interface{}, error) + JsonReference() (gojsonreference.JsonReference, error) + LoaderFactory() JSONLoaderFactory +} + +// JSONLoaderFactory defines the JSON loader factory interface +type JSONLoaderFactory interface { + // New creates a new JSON loader for the given source + New(source string) JSONLoader +} + +// DefaultJSONLoaderFactory is the default JSON loader factory +type DefaultJSONLoaderFactory struct { +} + +// FileSystemJSONLoaderFactory is a JSON loader factory that uses http.FileSystem +type FileSystemJSONLoaderFactory struct { + fs http.FileSystem +} + +// New creates a new JSON loader for the given source +func (d DefaultJSONLoaderFactory) New(source string) JSONLoader { + return &jsonReferenceLoader{ + fs: osFS, + source: source, + } +} + +// New creates a new JSON loader for the given source +func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader { + return &jsonReferenceLoader{ + fs: f.fs, + source: source, + } +} + +// osFileSystem is a functional wrapper for os.Open that implements http.FileSystem. +type osFileSystem func(string) (*os.File, error) + +// Opens a file with the given name +func (o osFileSystem) Open(name string) (http.File, error) { + return o(name) +} + +// JSON Reference loader +// references are used to load JSONs from files and HTTP + +type jsonReferenceLoader struct { + fs http.FileSystem + source string +} + +func (l *jsonReferenceLoader) JsonSource() interface{} { + return l.source +} + +func (l *jsonReferenceLoader) JsonReference() (gojsonreference.JsonReference, error) { + return gojsonreference.NewJsonReference(l.JsonSource().(string)) +} + +func (l *jsonReferenceLoader) LoaderFactory() JSONLoaderFactory { + return &FileSystemJSONLoaderFactory{ + fs: l.fs, + } +} + +// NewReferenceLoader returns a JSON reference loader using the given source and the local OS file system. +func NewReferenceLoader(source string) JSONLoader { + return &jsonReferenceLoader{ + fs: osFS, + source: source, + } +} + +// NewReferenceLoaderFileSystem returns a JSON reference loader using the given source and file system. +func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) JSONLoader { + return &jsonReferenceLoader{ + fs: fs, + source: source, + } +} + +func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) { + + var err error + + reference, err := gojsonreference.NewJsonReference(l.JsonSource().(string)) + if err != nil { + return nil, err + } + + refToURL := reference + refToURL.GetUrl().Fragment = "" + + var document interface{} + + if reference.HasFileScheme { + + filename := strings.TrimPrefix(refToURL.String(), "file://") + filename, err = url.QueryUnescape(filename) + + if err != nil { + return nil, err + } + + if runtime.GOOS == "windows" { + // on Windows, a file URL may have an extra leading slash, use slashes + // instead of backslashes, and have spaces escaped + filename = strings.TrimPrefix(filename, "/") + filename = filepath.FromSlash(filename) + } + + document, err = l.loadFromFile(filename) + if err != nil { + return nil, err + } + + } else { + + document, err = l.loadFromHTTP(refToURL.String()) + if err != nil { + return nil, err + } + + } + + return document, nil + +} + +func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error) { + + // returned cached versions for metaschemas for drafts 4, 6 and 7 + // for performance and allow for easier offline use + if metaSchema := drafts.GetMetaSchema(address); metaSchema != "" { + return decodeJSONUsingNumber(strings.NewReader(metaSchema)) + } + + resp, err := http.Get(address) + if err != nil { + return nil, err + } + + // must return HTTP Status 200 OK + if resp.StatusCode != http.StatusOK { + return nil, errors.New(formatErrorDescription(Locale.HttpBadStatus(), ErrorDetails{"status": resp.Status})) + } + + bodyBuff, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return decodeJSONUsingNumber(bytes.NewReader(bodyBuff)) +} + +func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) { + f, err := l.fs.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + bodyBuff, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + return decodeJSONUsingNumber(bytes.NewReader(bodyBuff)) + +} + +// JSON string loader + +type jsonStringLoader struct { + source string +} + +func (l *jsonStringLoader) JsonSource() interface{} { + return l.source +} + +func (l *jsonStringLoader) JsonReference() (gojsonreference.JsonReference, error) { + return gojsonreference.NewJsonReference("#") +} + +func (l *jsonStringLoader) LoaderFactory() JSONLoaderFactory { + return &DefaultJSONLoaderFactory{} +} + +// NewStringLoader creates a new JSONLoader, taking a string as source +func NewStringLoader(source string) JSONLoader { + return &jsonStringLoader{source: source} +} + +func (l *jsonStringLoader) LoadJSON() (interface{}, error) { + + return decodeJSONUsingNumber(strings.NewReader(l.JsonSource().(string))) + +} + +// JSON bytes loader + +type jsonBytesLoader struct { + source []byte +} + +func (l *jsonBytesLoader) JsonSource() interface{} { + return l.source +} + +func (l *jsonBytesLoader) JsonReference() (gojsonreference.JsonReference, error) { + return gojsonreference.NewJsonReference("#") +} + +func (l *jsonBytesLoader) LoaderFactory() JSONLoaderFactory { + return &DefaultJSONLoaderFactory{} +} + +// NewBytesLoader creates a new JSONLoader, taking a `[]byte` as source +func NewBytesLoader(source []byte) JSONLoader { + return &jsonBytesLoader{source: source} +} + +func (l *jsonBytesLoader) LoadJSON() (interface{}, error) { + return decodeJSONUsingNumber(bytes.NewReader(l.JsonSource().([]byte))) +} + +// JSON Go (types) loader +// used to load JSONs from the code as maps, interface{}, structs ... + +type jsonGoLoader struct { + source interface{} +} + +func (l *jsonGoLoader) JsonSource() interface{} { + return l.source +} + +func (l *jsonGoLoader) JsonReference() (gojsonreference.JsonReference, error) { + return gojsonreference.NewJsonReference("#") +} + +func (l *jsonGoLoader) LoaderFactory() JSONLoaderFactory { + return &DefaultJSONLoaderFactory{} +} + +// NewGoLoader creates a new JSONLoader from a given Go struct +func NewGoLoader(source interface{}) JSONLoader { + return &jsonGoLoader{source: source} +} + +func (l *jsonGoLoader) LoadJSON() (interface{}, error) { + + // convert it to a compliant JSON first to avoid types "mismatches" + + jsonBytes, err := json.Marshal(l.JsonSource()) + if err != nil { + return nil, err + } + + return decodeJSONUsingNumber(bytes.NewReader(jsonBytes)) + +} + +type jsonIOLoader struct { + buf *bytes.Buffer +} + +// NewReaderLoader creates a new JSON loader using the provided io.Reader +func NewReaderLoader(source io.Reader) (JSONLoader, io.Reader) { + buf := &bytes.Buffer{} + return &jsonIOLoader{buf: buf}, io.TeeReader(source, buf) +} + +// NewWriterLoader creates a new JSON loader using the provided io.Writer +func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) { + buf := &bytes.Buffer{} + return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf) +} + +func (l *jsonIOLoader) JsonSource() interface{} { + return l.buf.String() +} + +func (l *jsonIOLoader) LoadJSON() (interface{}, error) { + return decodeJSONUsingNumber(l.buf) +} + +func (l *jsonIOLoader) JsonReference() (gojsonreference.JsonReference, error) { + return gojsonreference.NewJsonReference("#") +} + +func (l *jsonIOLoader) LoaderFactory() JSONLoaderFactory { + return &DefaultJSONLoaderFactory{} +} + +// JSON raw loader +// In case the JSON is already marshalled to interface{} use this loader +// This is used for testing as otherwise there is no guarantee the JSON is marshalled +// "properly" by using https://golang.org/pkg/encoding/json/#Decoder.UseNumber +type jsonRawLoader struct { + source interface{} +} + +// NewRawLoader creates a new JSON raw loader for the given source +func NewRawLoader(source interface{}) JSONLoader { + return &jsonRawLoader{source: source} +} +func (l *jsonRawLoader) JsonSource() interface{} { + return l.source +} +func (l *jsonRawLoader) LoadJSON() (interface{}, error) { + return l.source, nil +} +func (l *jsonRawLoader) JsonReference() (gojsonreference.JsonReference, error) { + return gojsonreference.NewJsonReference("#") +} +func (l *jsonRawLoader) LoaderFactory() JSONLoaderFactory { + return &DefaultJSONLoaderFactory{} +} + +func decodeJSONUsingNumber(r io.Reader) (interface{}, error) { + + var document interface{} + + decoder := json.NewDecoder(r) + decoder.UseNumber() + + err := decoder.Decode(&document) + if err != nil { + return nil, err + } + + return document, nil + +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/locales.go b/vendor/github.com/xeipuuv/gojsonschema/locales.go new file mode 100644 index 0000000000..a416225cdb --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/locales.go @@ -0,0 +1,472 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Contains const string and messages. +// +// created 01-01-2015 + +package gojsonschema + +type ( + // locale is an interface for defining custom error strings + locale interface { + + // False returns a format-string for "false" schema validation errors + False() string + + // Required returns a format-string for "required" schema validation errors + Required() string + + // InvalidType returns a format-string for "invalid type" schema validation errors + InvalidType() string + + // NumberAnyOf returns a format-string for "anyOf" schema validation errors + NumberAnyOf() string + + // NumberOneOf returns a format-string for "oneOf" schema validation errors + NumberOneOf() string + + // NumberAllOf returns a format-string for "allOf" schema validation errors + NumberAllOf() string + + // NumberNot returns a format-string to format a NumberNotError + NumberNot() string + + // MissingDependency returns a format-string for "missing dependency" schema validation errors + MissingDependency() string + + // Internal returns a format-string for internal errors + Internal() string + + // Const returns a format-string to format a ConstError + Const() string + + // Enum returns a format-string to format an EnumError + Enum() string + + // ArrayNotEnoughItems returns a format-string to format an error for arrays having not enough items to match positional list of schema + ArrayNotEnoughItems() string + + // ArrayNoAdditionalItems returns a format-string to format an ArrayNoAdditionalItemsError + ArrayNoAdditionalItems() string + + // ArrayMinItems returns a format-string to format an ArrayMinItemsError + ArrayMinItems() string + + // ArrayMaxItems returns a format-string to format an ArrayMaxItemsError + ArrayMaxItems() string + + // Unique returns a format-string to format an ItemsMustBeUniqueError + Unique() string + + // ArrayContains returns a format-string to format an ArrayContainsError + ArrayContains() string + + // ArrayMinProperties returns a format-string to format an ArrayMinPropertiesError + ArrayMinProperties() string + + // ArrayMaxProperties returns a format-string to format an ArrayMaxPropertiesError + ArrayMaxProperties() string + + // AdditionalPropertyNotAllowed returns a format-string to format an AdditionalPropertyNotAllowedError + AdditionalPropertyNotAllowed() string + + // InvalidPropertyPattern returns a format-string to format an InvalidPropertyPatternError + InvalidPropertyPattern() string + + // InvalidPropertyName returns a format-string to format an InvalidPropertyNameError + InvalidPropertyName() string + + // StringGTE returns a format-string to format an StringLengthGTEError + StringGTE() string + + // StringLTE returns a format-string to format an StringLengthLTEError + StringLTE() string + + // DoesNotMatchPattern returns a format-string to format an DoesNotMatchPatternError + DoesNotMatchPattern() string + + // DoesNotMatchFormat returns a format-string to format an DoesNotMatchFormatError + DoesNotMatchFormat() string + + // MultipleOf returns a format-string to format an MultipleOfError + MultipleOf() string + + // NumberGTE returns a format-string to format an NumberGTEError + NumberGTE() string + + // NumberGT returns a format-string to format an NumberGTError + NumberGT() string + + // NumberLTE returns a format-string to format an NumberLTEError + NumberLTE() string + + // NumberLT returns a format-string to format an NumberLTError + NumberLT() string + + // Schema validations + + // RegexPattern returns a format-string to format a regex-pattern error + RegexPattern() string + + // GreaterThanZero returns a format-string to format an error where a number must be greater than zero + GreaterThanZero() string + + // MustBeOfA returns a format-string to format an error where a value is of the wrong type + MustBeOfA() string + + // MustBeOfAn returns a format-string to format an error where a value is of the wrong type + MustBeOfAn() string + + // CannotBeUsedWithout returns a format-string to format a "cannot be used without" error + CannotBeUsedWithout() string + + // CannotBeGT returns a format-string to format an error where a value are greater than allowed + CannotBeGT() string + + // MustBeOfType returns a format-string to format an error where a value does not match the required type + MustBeOfType() string + + // MustBeValidRegex returns a format-string to format an error where a regex is invalid + MustBeValidRegex() string + + // MustBeValidFormat returns a format-string to format an error where a value does not match the expected format + MustBeValidFormat() string + + // MustBeGTEZero returns a format-string to format an error where a value must be greater or equal than 0 + MustBeGTEZero() string + + // KeyCannotBeGreaterThan returns a format-string to format an error where a key is greater than the maximum allowed + KeyCannotBeGreaterThan() string + + // KeyItemsMustBeOfType returns a format-string to format an error where a key is of the wrong type + KeyItemsMustBeOfType() string + + // KeyItemsMustBeUnique returns a format-string to format an error where keys are not unique + KeyItemsMustBeUnique() string + + // ReferenceMustBeCanonical returns a format-string to format a "reference must be canonical" error + ReferenceMustBeCanonical() string + + // NotAValidType returns a format-string to format an invalid type error + NotAValidType() string + + // Duplicated returns a format-string to format an error where types are duplicated + Duplicated() string + + // HttpBadStatus returns a format-string for errors when loading a schema using HTTP + HttpBadStatus() string + + // ParseError returns a format-string for JSON parsing errors + ParseError() string + + // ConditionThen returns a format-string for ConditionThenError errors + ConditionThen() string + + // ConditionElse returns a format-string for ConditionElseError errors + ConditionElse() string + + // ErrorFormat returns a format string for errors + ErrorFormat() string + } + + // DefaultLocale is the default locale for this package + DefaultLocale struct{} +) + +// False returns a format-string for "false" schema validation errors +func (l DefaultLocale) False() string { + return "False always fails validation" +} + +// Required returns a format-string for "required" schema validation errors +func (l DefaultLocale) Required() string { + return `{{.property}} is required` +} + +// InvalidType returns a format-string for "invalid type" schema validation errors +func (l DefaultLocale) InvalidType() string { + return `Invalid type. Expected: {{.expected}}, given: {{.given}}` +} + +// NumberAnyOf returns a format-string for "anyOf" schema validation errors +func (l DefaultLocale) NumberAnyOf() string { + return `Must validate at least one schema (anyOf)` +} + +// NumberOneOf returns a format-string for "oneOf" schema validation errors +func (l DefaultLocale) NumberOneOf() string { + return `Must validate one and only one schema (oneOf)` +} + +// NumberAllOf returns a format-string for "allOf" schema validation errors +func (l DefaultLocale) NumberAllOf() string { + return `Must validate all the schemas (allOf)` +} + +// NumberNot returns a format-string to format a NumberNotError +func (l DefaultLocale) NumberNot() string { + return `Must not validate the schema (not)` +} + +// MissingDependency returns a format-string for "missing dependency" schema validation errors +func (l DefaultLocale) MissingDependency() string { + return `Has a dependency on {{.dependency}}` +} + +// Internal returns a format-string for internal errors +func (l DefaultLocale) Internal() string { + return `Internal Error {{.error}}` +} + +// Const returns a format-string to format a ConstError +func (l DefaultLocale) Const() string { + return `{{.field}} does not match: {{.allowed}}` +} + +// Enum returns a format-string to format an EnumError +func (l DefaultLocale) Enum() string { + return `{{.field}} must be one of the following: {{.allowed}}` +} + +// ArrayNoAdditionalItems returns a format-string to format an ArrayNoAdditionalItemsError +func (l DefaultLocale) ArrayNoAdditionalItems() string { + return `No additional items allowed on array` +} + +// ArrayNotEnoughItems returns a format-string to format an error for arrays having not enough items to match positional list of schema +func (l DefaultLocale) ArrayNotEnoughItems() string { + return `Not enough items on array to match positional list of schema` +} + +// ArrayMinItems returns a format-string to format an ArrayMinItemsError +func (l DefaultLocale) ArrayMinItems() string { + return `Array must have at least {{.min}} items` +} + +// ArrayMaxItems returns a format-string to format an ArrayMaxItemsError +func (l DefaultLocale) ArrayMaxItems() string { + return `Array must have at most {{.max}} items` +} + +// Unique returns a format-string to format an ItemsMustBeUniqueError +func (l DefaultLocale) Unique() string { + return `{{.type}} items[{{.i}},{{.j}}] must be unique` +} + +// ArrayContains returns a format-string to format an ArrayContainsError +func (l DefaultLocale) ArrayContains() string { + return `At least one of the items must match` +} + +// ArrayMinProperties returns a format-string to format an ArrayMinPropertiesError +func (l DefaultLocale) ArrayMinProperties() string { + return `Must have at least {{.min}} properties` +} + +// ArrayMaxProperties returns a format-string to format an ArrayMaxPropertiesError +func (l DefaultLocale) ArrayMaxProperties() string { + return `Must have at most {{.max}} properties` +} + +// AdditionalPropertyNotAllowed returns a format-string to format an AdditionalPropertyNotAllowedError +func (l DefaultLocale) AdditionalPropertyNotAllowed() string { + return `Additional property {{.property}} is not allowed` +} + +// InvalidPropertyPattern returns a format-string to format an InvalidPropertyPatternError +func (l DefaultLocale) InvalidPropertyPattern() string { + return `Property "{{.property}}" does not match pattern {{.pattern}}` +} + +// InvalidPropertyName returns a format-string to format an InvalidPropertyNameError +func (l DefaultLocale) InvalidPropertyName() string { + return `Property name of "{{.property}}" does not match` +} + +// StringGTE returns a format-string to format an StringLengthGTEError +func (l DefaultLocale) StringGTE() string { + return `String length must be greater than or equal to {{.min}}` +} + +// StringLTE returns a format-string to format an StringLengthLTEError +func (l DefaultLocale) StringLTE() string { + return `String length must be less than or equal to {{.max}}` +} + +// DoesNotMatchPattern returns a format-string to format an DoesNotMatchPatternError +func (l DefaultLocale) DoesNotMatchPattern() string { + return `Does not match pattern '{{.pattern}}'` +} + +// DoesNotMatchFormat returns a format-string to format an DoesNotMatchFormatError +func (l DefaultLocale) DoesNotMatchFormat() string { + return `Does not match format '{{.format}}'` +} + +// MultipleOf returns a format-string to format an MultipleOfError +func (l DefaultLocale) MultipleOf() string { + return `Must be a multiple of {{.multiple}}` +} + +// NumberGTE returns the format string to format a NumberGTEError +func (l DefaultLocale) NumberGTE() string { + return `Must be greater than or equal to {{.min}}` +} + +// NumberGT returns the format string to format a NumberGTError +func (l DefaultLocale) NumberGT() string { + return `Must be greater than {{.min}}` +} + +// NumberLTE returns the format string to format a NumberLTEError +func (l DefaultLocale) NumberLTE() string { + return `Must be less than or equal to {{.max}}` +} + +// NumberLT returns the format string to format a NumberLTError +func (l DefaultLocale) NumberLT() string { + return `Must be less than {{.max}}` +} + +// Schema validators + +// RegexPattern returns a format-string to format a regex-pattern error +func (l DefaultLocale) RegexPattern() string { + return `Invalid regex pattern '{{.pattern}}'` +} + +// GreaterThanZero returns a format-string to format an error where a number must be greater than zero +func (l DefaultLocale) GreaterThanZero() string { + return `{{.number}} must be strictly greater than 0` +} + +// MustBeOfA returns a format-string to format an error where a value is of the wrong type +func (l DefaultLocale) MustBeOfA() string { + return `{{.x}} must be of a {{.y}}` +} + +// MustBeOfAn returns a format-string to format an error where a value is of the wrong type +func (l DefaultLocale) MustBeOfAn() string { + return `{{.x}} must be of an {{.y}}` +} + +// CannotBeUsedWithout returns a format-string to format a "cannot be used without" error +func (l DefaultLocale) CannotBeUsedWithout() string { + return `{{.x}} cannot be used without {{.y}}` +} + +// CannotBeGT returns a format-string to format an error where a value are greater than allowed +func (l DefaultLocale) CannotBeGT() string { + return `{{.x}} cannot be greater than {{.y}}` +} + +// MustBeOfType returns a format-string to format an error where a value does not match the required type +func (l DefaultLocale) MustBeOfType() string { + return `{{.key}} must be of type {{.type}}` +} + +// MustBeValidRegex returns a format-string to format an error where a regex is invalid +func (l DefaultLocale) MustBeValidRegex() string { + return `{{.key}} must be a valid regex` +} + +// MustBeValidFormat returns a format-string to format an error where a value does not match the expected format +func (l DefaultLocale) MustBeValidFormat() string { + return `{{.key}} must be a valid format {{.given}}` +} + +// MustBeGTEZero returns a format-string to format an error where a value must be greater or equal than 0 +func (l DefaultLocale) MustBeGTEZero() string { + return `{{.key}} must be greater than or equal to 0` +} + +// KeyCannotBeGreaterThan returns a format-string to format an error where a value is greater than the maximum allowed +func (l DefaultLocale) KeyCannotBeGreaterThan() string { + return `{{.key}} cannot be greater than {{.y}}` +} + +// KeyItemsMustBeOfType returns a format-string to format an error where a key is of the wrong type +func (l DefaultLocale) KeyItemsMustBeOfType() string { + return `{{.key}} items must be {{.type}}` +} + +// KeyItemsMustBeUnique returns a format-string to format an error where keys are not unique +func (l DefaultLocale) KeyItemsMustBeUnique() string { + return `{{.key}} items must be unique` +} + +// ReferenceMustBeCanonical returns a format-string to format a "reference must be canonical" error +func (l DefaultLocale) ReferenceMustBeCanonical() string { + return `Reference {{.reference}} must be canonical` +} + +// NotAValidType returns a format-string to format an invalid type error +func (l DefaultLocale) NotAValidType() string { + return `has a primitive type that is NOT VALID -- given: {{.given}} Expected valid values are:{{.expected}}` +} + +// Duplicated returns a format-string to format an error where types are duplicated +func (l DefaultLocale) Duplicated() string { + return `{{.type}} type is duplicated` +} + +// HttpBadStatus returns a format-string for errors when loading a schema using HTTP +func (l DefaultLocale) HttpBadStatus() string { + return `Could not read schema from HTTP, response status is {{.status}}` +} + +// ErrorFormat returns a format string for errors +// Replacement options: field, description, context, value +func (l DefaultLocale) ErrorFormat() string { + return `{{.field}}: {{.description}}` +} + +// ParseError returns a format-string for JSON parsing errors +func (l DefaultLocale) ParseError() string { + return `Expected: {{.expected}}, given: Invalid JSON` +} + +// ConditionThen returns a format-string for ConditionThenError errors +// If/Else +func (l DefaultLocale) ConditionThen() string { + return `Must validate "then" as "if" was valid` +} + +// ConditionElse returns a format-string for ConditionElseError errors +func (l DefaultLocale) ConditionElse() string { + return `Must validate "else" as "if" was not valid` +} + +// constants +const ( + STRING_NUMBER = "number" + STRING_ARRAY_OF_STRINGS = "array of strings" + STRING_ARRAY_OF_SCHEMAS = "array of schemas" + STRING_SCHEMA = "valid schema" + STRING_SCHEMA_OR_ARRAY_OF_STRINGS = "schema or array of strings" + STRING_PROPERTIES = "properties" + STRING_DEPENDENCY = "dependency" + STRING_PROPERTY = "property" + STRING_UNDEFINED = "undefined" + STRING_CONTEXT_ROOT = "(root)" + STRING_ROOT_SCHEMA_PROPERTY = "(root)" +) diff --git a/vendor/github.com/xeipuuv/gojsonschema/result.go b/vendor/github.com/xeipuuv/gojsonschema/result.go new file mode 100644 index 0000000000..0a0179148b --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/result.go @@ -0,0 +1,220 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Result and ResultError implementations. +// +// created 01-01-2015 + +package gojsonschema + +import ( + "fmt" + "strings" +) + +type ( + // ErrorDetails is a map of details specific to each error. + // While the values will vary, every error will contain a "field" value + ErrorDetails map[string]interface{} + + // ResultError is the interface that library errors must implement + ResultError interface { + // Field returns the field name without the root context + // i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName + Field() string + // SetType sets the error-type + SetType(string) + // Type returns the error-type + Type() string + // SetContext sets the JSON-context for the error + SetContext(*JsonContext) + // Context returns the JSON-context of the error + Context() *JsonContext + // SetDescription sets a description for the error + SetDescription(string) + // Description returns the description of the error + Description() string + // SetDescriptionFormat sets the format for the description in the default text/template format + SetDescriptionFormat(string) + // DescriptionFormat returns the format for the description in the default text/template format + DescriptionFormat() string + // SetValue sets the value related to the error + SetValue(interface{}) + // Value returns the value related to the error + Value() interface{} + // SetDetails sets the details specific to the error + SetDetails(ErrorDetails) + // Details returns details about the error + Details() ErrorDetails + // String returns a string representation of the error + String() string + } + + // ResultErrorFields holds the fields for each ResultError implementation. + // ResultErrorFields implements the ResultError interface, so custom errors + // can be defined by just embedding this type + ResultErrorFields struct { + errorType string // A string with the type of error (i.e. invalid_type) + context *JsonContext // Tree like notation of the part that failed the validation. ex (root).a.b ... + description string // A human readable error message + descriptionFormat string // A format for human readable error message + value interface{} // Value given by the JSON file that is the source of the error + details ErrorDetails + } + + // Result holds the result of a validation + Result struct { + errors []ResultError + // Scores how well the validation matched. Useful in generating + // better error messages for anyOf and oneOf. + score int + } +) + +// Field returns the field name without the root context +// i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName +func (v *ResultErrorFields) Field() string { + return strings.TrimPrefix(v.context.String(), STRING_ROOT_SCHEMA_PROPERTY+".") +} + +// SetType sets the error-type +func (v *ResultErrorFields) SetType(errorType string) { + v.errorType = errorType +} + +// Type returns the error-type +func (v *ResultErrorFields) Type() string { + return v.errorType +} + +// SetContext sets the JSON-context for the error +func (v *ResultErrorFields) SetContext(context *JsonContext) { + v.context = context +} + +// Context returns the JSON-context of the error +func (v *ResultErrorFields) Context() *JsonContext { + return v.context +} + +// SetDescription sets a description for the error +func (v *ResultErrorFields) SetDescription(description string) { + v.description = description +} + +// Description returns the description of the error +func (v *ResultErrorFields) Description() string { + return v.description +} + +// SetDescriptionFormat sets the format for the description in the default text/template format +func (v *ResultErrorFields) SetDescriptionFormat(descriptionFormat string) { + v.descriptionFormat = descriptionFormat +} + +// DescriptionFormat returns the format for the description in the default text/template format +func (v *ResultErrorFields) DescriptionFormat() string { + return v.descriptionFormat +} + +// SetValue sets the value related to the error +func (v *ResultErrorFields) SetValue(value interface{}) { + v.value = value +} + +// Value returns the value related to the error +func (v *ResultErrorFields) Value() interface{} { + return v.value +} + +// SetDetails sets the details specific to the error +func (v *ResultErrorFields) SetDetails(details ErrorDetails) { + v.details = details +} + +// Details returns details about the error +func (v *ResultErrorFields) Details() ErrorDetails { + return v.details +} + +// String returns a string representation of the error +func (v ResultErrorFields) String() string { + // as a fallback, the value is displayed go style + valueString := fmt.Sprintf("%v", v.value) + + // marshal the go value value to json + if v.value == nil { + valueString = TYPE_NULL + } else { + if vs, err := marshalToJSONString(v.value); err == nil { + if vs == nil { + valueString = TYPE_NULL + } else { + valueString = *vs + } + } + } + + return formatErrorDescription(Locale.ErrorFormat(), ErrorDetails{ + "context": v.context.String(), + "description": v.description, + "value": valueString, + "field": v.Field(), + }) +} + +// Valid indicates if no errors were found +func (v *Result) Valid() bool { + return len(v.errors) == 0 +} + +// Errors returns the errors that were found +func (v *Result) Errors() []ResultError { + return v.errors +} + +// AddError appends a fully filled error to the error set +// SetDescription() will be called with the result of the parsed err.DescriptionFormat() +func (v *Result) AddError(err ResultError, details ErrorDetails) { + if _, exists := details["context"]; !exists && err.Context() != nil { + details["context"] = err.Context().String() + } + + err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details)) + + v.errors = append(v.errors, err) +} + +func (v *Result) addInternalError(err ResultError, context *JsonContext, value interface{}, details ErrorDetails) { + newError(err, context, value, Locale, details) + v.errors = append(v.errors, err) + v.score -= 2 // results in a net -1 when added to the +1 we get at the end of the validation function +} + +// Used to copy errors from a sub-schema to the main one +func (v *Result) mergeErrors(otherResult *Result) { + v.errors = append(v.errors, otherResult.Errors()...) + v.score += otherResult.score +} + +func (v *Result) incrementScore() { + v.score++ +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schema.go b/vendor/github.com/xeipuuv/gojsonschema/schema.go new file mode 100644 index 0000000000..9e93cd7955 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schema.go @@ -0,0 +1,1087 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Defines Schema, the main entry to every subSchema. +// Contains the parsing logic and error checking. +// +// created 26-02-2013 + +package gojsonschema + +import ( + "errors" + "math/big" + "reflect" + "regexp" + "text/template" + + "github.com/xeipuuv/gojsonreference" +) + +var ( + // Locale is the default locale to use + // Library users can overwrite with their own implementation + Locale locale = DefaultLocale{} + + // ErrorTemplateFuncs allows you to define custom template funcs for use in localization. + ErrorTemplateFuncs template.FuncMap +) + +// NewSchema instances a schema using the given JSONLoader +func NewSchema(l JSONLoader) (*Schema, error) { + return NewSchemaLoader().Compile(l) +} + +// Schema holds a schema +type Schema struct { + documentReference gojsonreference.JsonReference + rootSchema *subSchema + pool *schemaPool + referencePool *schemaReferencePool +} + +func (d *Schema) parse(document interface{}, draft Draft) error { + d.rootSchema = &subSchema{property: STRING_ROOT_SCHEMA_PROPERTY, draft: &draft} + return d.parseSchema(document, d.rootSchema) +} + +// SetRootSchemaName sets the root-schema name +func (d *Schema) SetRootSchemaName(name string) { + d.rootSchema.property = name +} + +// Parses a subSchema +// +// Pretty long function ( sorry :) )... but pretty straight forward, repetitive and boring +// Not much magic involved here, most of the job is to validate the key names and their values, +// then the values are copied into subSchema struct +// +func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) error { + + if currentSchema.draft == nil { + if currentSchema.parent == nil { + return errors.New("Draft not set") + } + currentSchema.draft = currentSchema.parent.draft + } + + // As of draft 6 "true" is equivalent to an empty schema "{}" and false equals "{"not":{}}" + if *currentSchema.draft >= Draft6 && isKind(documentNode, reflect.Bool) { + b := documentNode.(bool) + currentSchema.pass = &b + return nil + } + + if !isKind(documentNode, reflect.Map) { + return errors.New(formatErrorDescription( + Locale.ParseError(), + ErrorDetails{ + "expected": STRING_SCHEMA, + }, + )) + } + + m := documentNode.(map[string]interface{}) + + if currentSchema.parent == nil { + currentSchema.ref = &d.documentReference + currentSchema.id = &d.documentReference + } + + if currentSchema.id == nil && currentSchema.parent != nil { + currentSchema.id = currentSchema.parent.id + } + + // In draft 6 the id keyword was renamed to $id + // Hybrid mode uses the old id by default + var keyID string + + switch *currentSchema.draft { + case Draft4: + keyID = KEY_ID + case Hybrid: + keyID = KEY_ID_NEW + if existsMapKey(m, KEY_ID) { + keyID = KEY_ID + } + default: + keyID = KEY_ID_NEW + } + if existsMapKey(m, keyID) && !isKind(m[keyID], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": keyID, + }, + )) + } + if k, ok := m[keyID].(string); ok { + jsonReference, err := gojsonreference.NewJsonReference(k) + if err != nil { + return err + } + if currentSchema == d.rootSchema { + currentSchema.id = &jsonReference + } else { + ref, err := currentSchema.parent.id.Inherits(jsonReference) + if err != nil { + return err + } + currentSchema.id = ref + } + } + + // definitions + if existsMapKey(m, KEY_DEFINITIONS) { + if isKind(m[KEY_DEFINITIONS], reflect.Map, reflect.Bool) { + for _, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) { + if isKind(dv, reflect.Map, reflect.Bool) { + + newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema} + + err := d.parseSchema(dv, newSchema) + + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_ARRAY_OF_SCHEMAS, + "given": KEY_DEFINITIONS, + }, + )) + } + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_ARRAY_OF_SCHEMAS, + "given": KEY_DEFINITIONS, + }, + )) + } + + } + + // title + if existsMapKey(m, KEY_TITLE) && !isKind(m[KEY_TITLE], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_TITLE, + }, + )) + } + if k, ok := m[KEY_TITLE].(string); ok { + currentSchema.title = &k + } + + // description + if existsMapKey(m, KEY_DESCRIPTION) && !isKind(m[KEY_DESCRIPTION], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_DESCRIPTION, + }, + )) + } + if k, ok := m[KEY_DESCRIPTION].(string); ok { + currentSchema.description = &k + } + + // $ref + if existsMapKey(m, KEY_REF) && !isKind(m[KEY_REF], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_REF, + }, + )) + } + + if k, ok := m[KEY_REF].(string); ok { + + jsonReference, err := gojsonreference.NewJsonReference(k) + if err != nil { + return err + } + + currentSchema.ref = &jsonReference + + if sch, ok := d.referencePool.Get(currentSchema.ref.String()); ok { + currentSchema.refSchema = sch + } else { + err := d.parseReference(documentNode, currentSchema) + + if err != nil { + return err + } + + return nil + } + } + + // type + if existsMapKey(m, KEY_TYPE) { + if isKind(m[KEY_TYPE], reflect.String) { + if k, ok := m[KEY_TYPE].(string); ok { + err := currentSchema.types.Add(k) + if err != nil { + return err + } + } + } else { + if isKind(m[KEY_TYPE], reflect.Slice) { + arrayOfTypes := m[KEY_TYPE].([]interface{}) + for _, typeInArray := range arrayOfTypes { + if reflect.ValueOf(typeInArray).Kind() != reflect.String { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING + "/" + STRING_ARRAY_OF_STRINGS, + "given": KEY_TYPE, + }, + )) + } + if err := currentSchema.types.Add(typeInArray.(string)); err != nil { + return err + } + } + + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING + "/" + STRING_ARRAY_OF_STRINGS, + "given": KEY_TYPE, + }, + )) + } + } + } + + // properties + if existsMapKey(m, KEY_PROPERTIES) { + err := d.parseProperties(m[KEY_PROPERTIES], currentSchema) + if err != nil { + return err + } + } + + // additionalProperties + if existsMapKey(m, KEY_ADDITIONAL_PROPERTIES) { + if isKind(m[KEY_ADDITIONAL_PROPERTIES], reflect.Bool) { + currentSchema.additionalProperties = m[KEY_ADDITIONAL_PROPERTIES].(bool) + } else if isKind(m[KEY_ADDITIONAL_PROPERTIES], reflect.Map) { + newSchema := &subSchema{property: KEY_ADDITIONAL_PROPERTIES, parent: currentSchema, ref: currentSchema.ref} + currentSchema.additionalProperties = newSchema + err := d.parseSchema(m[KEY_ADDITIONAL_PROPERTIES], newSchema) + if err != nil { + return errors.New(err.Error()) + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN + "/" + STRING_SCHEMA, + "given": KEY_ADDITIONAL_PROPERTIES, + }, + )) + } + } + + // patternProperties + if existsMapKey(m, KEY_PATTERN_PROPERTIES) { + if isKind(m[KEY_PATTERN_PROPERTIES], reflect.Map) { + patternPropertiesMap := m[KEY_PATTERN_PROPERTIES].(map[string]interface{}) + if len(patternPropertiesMap) > 0 { + currentSchema.patternProperties = make(map[string]*subSchema) + for k, v := range patternPropertiesMap { + _, err := regexp.MatchString(k, "") + if err != nil { + return errors.New(formatErrorDescription( + Locale.RegexPattern(), + ErrorDetails{"pattern": k}, + )) + } + newSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref} + err = d.parseSchema(v, newSchema) + if err != nil { + return errors.New(err.Error()) + } + currentSchema.patternProperties[k] = newSchema + } + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_SCHEMA, + "given": KEY_PATTERN_PROPERTIES, + }, + )) + } + } + + // propertyNames + if existsMapKey(m, KEY_PROPERTY_NAMES) && *currentSchema.draft >= Draft6 { + if isKind(m[KEY_PROPERTY_NAMES], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_PROPERTY_NAMES, parent: currentSchema, ref: currentSchema.ref} + currentSchema.propertyNames = newSchema + err := d.parseSchema(m[KEY_PROPERTY_NAMES], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_SCHEMA, + "given": KEY_PATTERN_PROPERTIES, + }, + )) + } + } + + // dependencies + if existsMapKey(m, KEY_DEPENDENCIES) { + err := d.parseDependencies(m[KEY_DEPENDENCIES], currentSchema) + if err != nil { + return err + } + } + + // items + if existsMapKey(m, KEY_ITEMS) { + if isKind(m[KEY_ITEMS], reflect.Slice) { + for _, itemElement := range m[KEY_ITEMS].([]interface{}) { + if isKind(itemElement, reflect.Map, reflect.Bool) { + newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} + newSchema.ref = currentSchema.ref + currentSchema.itemsChildren = append(currentSchema.itemsChildren, newSchema) + err := d.parseSchema(itemElement, newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_SCHEMA + "/" + STRING_ARRAY_OF_SCHEMAS, + "given": KEY_ITEMS, + }, + )) + } + currentSchema.itemsChildrenIsSingleSchema = false + } + } else if isKind(m[KEY_ITEMS], reflect.Map, reflect.Bool) { + newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} + newSchema.ref = currentSchema.ref + currentSchema.itemsChildren = append(currentSchema.itemsChildren, newSchema) + err := d.parseSchema(m[KEY_ITEMS], newSchema) + if err != nil { + return err + } + currentSchema.itemsChildrenIsSingleSchema = true + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_SCHEMA + "/" + STRING_ARRAY_OF_SCHEMAS, + "given": KEY_ITEMS, + }, + )) + } + } + + // additionalItems + if existsMapKey(m, KEY_ADDITIONAL_ITEMS) { + if isKind(m[KEY_ADDITIONAL_ITEMS], reflect.Bool) { + currentSchema.additionalItems = m[KEY_ADDITIONAL_ITEMS].(bool) + } else if isKind(m[KEY_ADDITIONAL_ITEMS], reflect.Map) { + newSchema := &subSchema{property: KEY_ADDITIONAL_ITEMS, parent: currentSchema, ref: currentSchema.ref} + currentSchema.additionalItems = newSchema + err := d.parseSchema(m[KEY_ADDITIONAL_ITEMS], newSchema) + if err != nil { + return errors.New(err.Error()) + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN + "/" + STRING_SCHEMA, + "given": KEY_ADDITIONAL_ITEMS, + }, + )) + } + } + + // validation : number / integer + + if existsMapKey(m, KEY_MULTIPLE_OF) { + multipleOfValue := mustBeNumber(m[KEY_MULTIPLE_OF]) + if multipleOfValue == nil { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_NUMBER, + "given": KEY_MULTIPLE_OF, + }, + )) + } + if multipleOfValue.Cmp(big.NewRat(0, 1)) <= 0 { + return errors.New(formatErrorDescription( + Locale.GreaterThanZero(), + ErrorDetails{"number": KEY_MULTIPLE_OF}, + )) + } + currentSchema.multipleOf = multipleOfValue + } + + if existsMapKey(m, KEY_MINIMUM) { + minimumValue := mustBeNumber(m[KEY_MINIMUM]) + if minimumValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_MINIMUM, "y": STRING_NUMBER}, + )) + } + currentSchema.minimum = minimumValue + } + + if existsMapKey(m, KEY_EXCLUSIVE_MINIMUM) { + switch *currentSchema.draft { + case Draft4: + if !isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN, + "given": KEY_EXCLUSIVE_MINIMUM, + }, + )) + } + if currentSchema.minimum == nil { + return errors.New(formatErrorDescription( + Locale.CannotBeUsedWithout(), + ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM}, + )) + } + if m[KEY_EXCLUSIVE_MINIMUM].(bool) { + currentSchema.exclusiveMinimum = currentSchema.minimum + currentSchema.minimum = nil + } + case Hybrid: + if isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) { + if currentSchema.minimum == nil { + return errors.New(formatErrorDescription( + Locale.CannotBeUsedWithout(), + ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM}, + )) + } + if m[KEY_EXCLUSIVE_MINIMUM].(bool) { + currentSchema.exclusiveMinimum = currentSchema.minimum + currentSchema.minimum = nil + } + } else if isJSONNumber(m[KEY_EXCLUSIVE_MINIMUM]) { + currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM]) + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN + "/" + TYPE_NUMBER, + "given": KEY_EXCLUSIVE_MINIMUM, + }, + )) + } + default: + if isJSONNumber(m[KEY_EXCLUSIVE_MINIMUM]) { + currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM]) + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_NUMBER, + "given": KEY_EXCLUSIVE_MINIMUM, + }, + )) + } + } + } + + if existsMapKey(m, KEY_MAXIMUM) { + maximumValue := mustBeNumber(m[KEY_MAXIMUM]) + if maximumValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_MAXIMUM, "y": STRING_NUMBER}, + )) + } + currentSchema.maximum = maximumValue + } + + if existsMapKey(m, KEY_EXCLUSIVE_MAXIMUM) { + switch *currentSchema.draft { + case Draft4: + if !isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN, + "given": KEY_EXCLUSIVE_MAXIMUM, + }, + )) + } + if currentSchema.maximum == nil { + return errors.New(formatErrorDescription( + Locale.CannotBeUsedWithout(), + ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM}, + )) + } + if m[KEY_EXCLUSIVE_MAXIMUM].(bool) { + currentSchema.exclusiveMaximum = currentSchema.maximum + currentSchema.maximum = nil + } + case Hybrid: + if isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) { + if currentSchema.maximum == nil { + return errors.New(formatErrorDescription( + Locale.CannotBeUsedWithout(), + ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM}, + )) + } + if m[KEY_EXCLUSIVE_MAXIMUM].(bool) { + currentSchema.exclusiveMaximum = currentSchema.maximum + currentSchema.maximum = nil + } + } else if isJSONNumber(m[KEY_EXCLUSIVE_MAXIMUM]) { + currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM]) + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN + "/" + TYPE_NUMBER, + "given": KEY_EXCLUSIVE_MAXIMUM, + }, + )) + } + default: + if isJSONNumber(m[KEY_EXCLUSIVE_MAXIMUM]) { + currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM]) + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_NUMBER, + "given": KEY_EXCLUSIVE_MAXIMUM, + }, + )) + } + } + } + + // validation : string + + if existsMapKey(m, KEY_MIN_LENGTH) { + minLengthIntegerValue := mustBeInteger(m[KEY_MIN_LENGTH]) + if minLengthIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MIN_LENGTH, "y": TYPE_INTEGER}, + )) + } + if *minLengthIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MIN_LENGTH}, + )) + } + currentSchema.minLength = minLengthIntegerValue + } + + if existsMapKey(m, KEY_MAX_LENGTH) { + maxLengthIntegerValue := mustBeInteger(m[KEY_MAX_LENGTH]) + if maxLengthIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MAX_LENGTH, "y": TYPE_INTEGER}, + )) + } + if *maxLengthIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MAX_LENGTH}, + )) + } + currentSchema.maxLength = maxLengthIntegerValue + } + + if currentSchema.minLength != nil && currentSchema.maxLength != nil { + if *currentSchema.minLength > *currentSchema.maxLength { + return errors.New(formatErrorDescription( + Locale.CannotBeGT(), + ErrorDetails{"x": KEY_MIN_LENGTH, "y": KEY_MAX_LENGTH}, + )) + } + } + + if existsMapKey(m, KEY_PATTERN) { + if isKind(m[KEY_PATTERN], reflect.String) { + regexpObject, err := regexp.Compile(m[KEY_PATTERN].(string)) + if err != nil { + return errors.New(formatErrorDescription( + Locale.MustBeValidRegex(), + ErrorDetails{"key": KEY_PATTERN}, + )) + } + currentSchema.pattern = regexpObject + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_PATTERN, "y": TYPE_STRING}, + )) + } + } + + if existsMapKey(m, KEY_FORMAT) { + formatString, ok := m[KEY_FORMAT].(string) + if !ok { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{"key": KEY_FORMAT, "type": TYPE_STRING}, + )) + } + currentSchema.format = formatString + } + + // validation : object + + if existsMapKey(m, KEY_MIN_PROPERTIES) { + minPropertiesIntegerValue := mustBeInteger(m[KEY_MIN_PROPERTIES]) + if minPropertiesIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MIN_PROPERTIES, "y": TYPE_INTEGER}, + )) + } + if *minPropertiesIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MIN_PROPERTIES}, + )) + } + currentSchema.minProperties = minPropertiesIntegerValue + } + + if existsMapKey(m, KEY_MAX_PROPERTIES) { + maxPropertiesIntegerValue := mustBeInteger(m[KEY_MAX_PROPERTIES]) + if maxPropertiesIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MAX_PROPERTIES, "y": TYPE_INTEGER}, + )) + } + if *maxPropertiesIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MAX_PROPERTIES}, + )) + } + currentSchema.maxProperties = maxPropertiesIntegerValue + } + + if currentSchema.minProperties != nil && currentSchema.maxProperties != nil { + if *currentSchema.minProperties > *currentSchema.maxProperties { + return errors.New(formatErrorDescription( + Locale.KeyCannotBeGreaterThan(), + ErrorDetails{"key": KEY_MIN_PROPERTIES, "y": KEY_MAX_PROPERTIES}, + )) + } + } + + if existsMapKey(m, KEY_REQUIRED) { + if isKind(m[KEY_REQUIRED], reflect.Slice) { + requiredValues := m[KEY_REQUIRED].([]interface{}) + for _, requiredValue := range requiredValues { + if isKind(requiredValue, reflect.String) { + if isStringInSlice(currentSchema.required, requiredValue.(string)) { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeUnique(), + ErrorDetails{"key": KEY_REQUIRED}, + )) + } + currentSchema.required = append(currentSchema.required, requiredValue.(string)) + } else { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeOfType(), + ErrorDetails{"key": KEY_REQUIRED, "type": TYPE_STRING}, + )) + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_REQUIRED, "y": TYPE_ARRAY}, + )) + } + } + + // validation : array + + if existsMapKey(m, KEY_MIN_ITEMS) { + minItemsIntegerValue := mustBeInteger(m[KEY_MIN_ITEMS]) + if minItemsIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MIN_ITEMS, "y": TYPE_INTEGER}, + )) + } + if *minItemsIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MIN_ITEMS}, + )) + } + currentSchema.minItems = minItemsIntegerValue + } + + if existsMapKey(m, KEY_MAX_ITEMS) { + maxItemsIntegerValue := mustBeInteger(m[KEY_MAX_ITEMS]) + if maxItemsIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MAX_ITEMS, "y": TYPE_INTEGER}, + )) + } + if *maxItemsIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MAX_ITEMS}, + )) + } + currentSchema.maxItems = maxItemsIntegerValue + } + + if existsMapKey(m, KEY_UNIQUE_ITEMS) { + if isKind(m[KEY_UNIQUE_ITEMS], reflect.Bool) { + currentSchema.uniqueItems = m[KEY_UNIQUE_ITEMS].(bool) + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_UNIQUE_ITEMS, "y": TYPE_BOOLEAN}, + )) + } + } + + if existsMapKey(m, KEY_CONTAINS) && *currentSchema.draft >= Draft6 { + newSchema := &subSchema{property: KEY_CONTAINS, parent: currentSchema, ref: currentSchema.ref} + currentSchema.contains = newSchema + err := d.parseSchema(m[KEY_CONTAINS], newSchema) + if err != nil { + return err + } + } + + // validation : all + + if existsMapKey(m, KEY_CONST) && *currentSchema.draft >= Draft6 { + is, err := marshalWithoutNumber(m[KEY_CONST]) + if err != nil { + return err + } + currentSchema._const = is + } + + if existsMapKey(m, KEY_ENUM) { + if isKind(m[KEY_ENUM], reflect.Slice) { + for _, v := range m[KEY_ENUM].([]interface{}) { + is, err := marshalWithoutNumber(v) + if err != nil { + return err + } + if isStringInSlice(currentSchema.enum, *is) { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeUnique(), + ErrorDetails{"key": KEY_ENUM}, + )) + } + currentSchema.enum = append(currentSchema.enum, *is) + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ENUM, "y": TYPE_ARRAY}, + )) + } + } + + // validation : subSchema + + if existsMapKey(m, KEY_ONE_OF) { + if isKind(m[KEY_ONE_OF], reflect.Slice) { + for _, v := range m[KEY_ONE_OF].([]interface{}) { + newSchema := &subSchema{property: KEY_ONE_OF, parent: currentSchema, ref: currentSchema.ref} + currentSchema.oneOf = append(currentSchema.oneOf, newSchema) + err := d.parseSchema(v, newSchema) + if err != nil { + return err + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ONE_OF, "y": TYPE_ARRAY}, + )) + } + } + + if existsMapKey(m, KEY_ANY_OF) { + if isKind(m[KEY_ANY_OF], reflect.Slice) { + for _, v := range m[KEY_ANY_OF].([]interface{}) { + newSchema := &subSchema{property: KEY_ANY_OF, parent: currentSchema, ref: currentSchema.ref} + currentSchema.anyOf = append(currentSchema.anyOf, newSchema) + err := d.parseSchema(v, newSchema) + if err != nil { + return err + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ANY_OF, "y": TYPE_ARRAY}, + )) + } + } + + if existsMapKey(m, KEY_ALL_OF) { + if isKind(m[KEY_ALL_OF], reflect.Slice) { + for _, v := range m[KEY_ALL_OF].([]interface{}) { + newSchema := &subSchema{property: KEY_ALL_OF, parent: currentSchema, ref: currentSchema.ref} + currentSchema.allOf = append(currentSchema.allOf, newSchema) + err := d.parseSchema(v, newSchema) + if err != nil { + return err + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ANY_OF, "y": TYPE_ARRAY}, + )) + } + } + + if existsMapKey(m, KEY_NOT) { + if isKind(m[KEY_NOT], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_NOT, parent: currentSchema, ref: currentSchema.ref} + currentSchema.not = newSchema + err := d.parseSchema(m[KEY_NOT], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_NOT, "y": TYPE_OBJECT}, + )) + } + } + + if *currentSchema.draft >= Draft7 { + if existsMapKey(m, KEY_IF) { + if isKind(m[KEY_IF], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_IF, parent: currentSchema, ref: currentSchema.ref} + currentSchema._if = newSchema + err := d.parseSchema(m[KEY_IF], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_IF, "y": TYPE_OBJECT}, + )) + } + } + + if existsMapKey(m, KEY_THEN) { + if isKind(m[KEY_THEN], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_THEN, parent: currentSchema, ref: currentSchema.ref} + currentSchema._then = newSchema + err := d.parseSchema(m[KEY_THEN], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_THEN, "y": TYPE_OBJECT}, + )) + } + } + + if existsMapKey(m, KEY_ELSE) { + if isKind(m[KEY_ELSE], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_ELSE, parent: currentSchema, ref: currentSchema.ref} + currentSchema._else = newSchema + err := d.parseSchema(m[KEY_ELSE], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ELSE, "y": TYPE_OBJECT}, + )) + } + } + } + + return nil +} + +func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema) error { + var ( + refdDocumentNode interface{} + dsp *schemaPoolDocument + err error + ) + + newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref} + + d.referencePool.Add(currentSchema.ref.String(), newSchema) + + dsp, err = d.pool.GetDocument(*currentSchema.ref) + if err != nil { + return err + } + newSchema.id = currentSchema.ref + + refdDocumentNode = dsp.Document + newSchema.draft = dsp.Draft + + if err != nil { + return err + } + + if !isKind(refdDocumentNode, reflect.Map, reflect.Bool) { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{"key": STRING_SCHEMA, "type": TYPE_OBJECT}, + )) + } + + err = d.parseSchema(refdDocumentNode, newSchema) + if err != nil { + return err + } + + currentSchema.refSchema = newSchema + + return nil + +} + +func (d *Schema) parseProperties(documentNode interface{}, currentSchema *subSchema) error { + + if !isKind(documentNode, reflect.Map) { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{"key": STRING_PROPERTIES, "type": TYPE_OBJECT}, + )) + } + + m := documentNode.(map[string]interface{}) + for k := range m { + schemaProperty := k + newSchema := &subSchema{property: schemaProperty, parent: currentSchema, ref: currentSchema.ref} + currentSchema.propertiesChildren = append(currentSchema.propertiesChildren, newSchema) + err := d.parseSchema(m[k], newSchema) + if err != nil { + return err + } + } + + return nil +} + +func (d *Schema) parseDependencies(documentNode interface{}, currentSchema *subSchema) error { + + if !isKind(documentNode, reflect.Map) { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{"key": KEY_DEPENDENCIES, "type": TYPE_OBJECT}, + )) + } + + m := documentNode.(map[string]interface{}) + currentSchema.dependencies = make(map[string]interface{}) + + for k := range m { + switch reflect.ValueOf(m[k]).Kind() { + + case reflect.Slice: + values := m[k].([]interface{}) + var valuesToRegister []string + + for _, value := range values { + if !isKind(value, reflect.String) { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{ + "key": STRING_DEPENDENCY, + "type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS, + }, + )) + } + valuesToRegister = append(valuesToRegister, value.(string)) + currentSchema.dependencies[k] = valuesToRegister + } + + case reflect.Map, reflect.Bool: + depSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref} + err := d.parseSchema(m[k], depSchema) + if err != nil { + return err + } + currentSchema.dependencies[k] = depSchema + + default: + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{ + "key": STRING_DEPENDENCY, + "type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS, + }, + )) + } + + } + + return nil +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaLoader.go b/vendor/github.com/xeipuuv/gojsonschema/schemaLoader.go new file mode 100644 index 0000000000..20db0c1f99 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaLoader.go @@ -0,0 +1,206 @@ +// Copyright 2018 johandorland ( https://github.com/johandorland ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gojsonschema + +import ( + "bytes" + "errors" + + "github.com/xeipuuv/gojsonreference" +) + +// SchemaLoader is used to load schemas +type SchemaLoader struct { + pool *schemaPool + AutoDetect bool + Validate bool + Draft Draft +} + +// NewSchemaLoader creates a new NewSchemaLoader +func NewSchemaLoader() *SchemaLoader { + + ps := &SchemaLoader{ + pool: &schemaPool{ + schemaPoolDocuments: make(map[string]*schemaPoolDocument), + }, + AutoDetect: true, + Validate: false, + Draft: Hybrid, + } + ps.pool.autoDetect = &ps.AutoDetect + + return ps +} + +func (sl *SchemaLoader) validateMetaschema(documentNode interface{}) error { + + var ( + schema string + err error + ) + if sl.AutoDetect { + schema, _, err = parseSchemaURL(documentNode) + if err != nil { + return err + } + } + + // If no explicit "$schema" is used, use the default metaschema associated with the draft used + if schema == "" { + if sl.Draft == Hybrid { + return nil + } + schema = drafts.GetSchemaURL(sl.Draft) + } + + //Disable validation when loading the metaschema to prevent an infinite recursive loop + sl.Validate = false + + metaSchema, err := sl.Compile(NewReferenceLoader(schema)) + + if err != nil { + return err + } + + sl.Validate = true + + result := metaSchema.validateDocument(documentNode) + + if !result.Valid() { + var res bytes.Buffer + for _, err := range result.Errors() { + res.WriteString(err.String()) + res.WriteString("\n") + } + return errors.New(res.String()) + } + + return nil +} + +// AddSchemas adds an arbritrary amount of schemas to the schema cache. As this function does not require +// an explicit URL, every schema should contain an $id, so that it can be referenced by the main schema +func (sl *SchemaLoader) AddSchemas(loaders ...JSONLoader) error { + emptyRef, _ := gojsonreference.NewJsonReference("") + + for _, loader := range loaders { + doc, err := loader.LoadJSON() + + if err != nil { + return err + } + + if sl.Validate { + if err := sl.validateMetaschema(doc); err != nil { + return err + } + } + + // Directly use the Recursive function, so that it get only added to the schema pool by $id + // and not by the ref of the document as it's empty + if err = sl.pool.parseReferences(doc, emptyRef, false); err != nil { + return err + } + } + + return nil +} + +//AddSchema adds a schema under the provided URL to the schema cache +func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error { + + ref, err := gojsonreference.NewJsonReference(url) + + if err != nil { + return err + } + + doc, err := loader.LoadJSON() + + if err != nil { + return err + } + + if sl.Validate { + if err := sl.validateMetaschema(doc); err != nil { + return err + } + } + + return sl.pool.parseReferences(doc, ref, true) +} + +// Compile loads and compiles a schema +func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) { + + ref, err := rootSchema.JsonReference() + + if err != nil { + return nil, err + } + + d := Schema{} + d.pool = sl.pool + d.pool.jsonLoaderFactory = rootSchema.LoaderFactory() + d.documentReference = ref + d.referencePool = newSchemaReferencePool() + + var doc interface{} + if ref.String() != "" { + // Get document from schema pool + spd, err := d.pool.GetDocument(d.documentReference) + if err != nil { + return nil, err + } + doc = spd.Document + } else { + // Load JSON directly + doc, err = rootSchema.LoadJSON() + if err != nil { + return nil, err + } + // References need only be parsed if loading JSON directly + // as pool.GetDocument already does this for us if loading by reference + err = sl.pool.parseReferences(doc, ref, true) + if err != nil { + return nil, err + } + } + + if sl.Validate { + if err := sl.validateMetaschema(doc); err != nil { + return nil, err + } + } + + draft := sl.Draft + if sl.AutoDetect { + _, detectedDraft, err := parseSchemaURL(doc) + if err != nil { + return nil, err + } + if detectedDraft != nil { + draft = *detectedDraft + } + } + + err = d.parse(doc, draft) + if err != nil { + return nil, err + } + + return &d, nil +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go b/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go new file mode 100644 index 0000000000..35b1cc6306 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go @@ -0,0 +1,215 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Defines resources pooling. +// Eases referencing and avoids downloading the same resource twice. +// +// created 26-02-2013 + +package gojsonschema + +import ( + "errors" + "fmt" + "reflect" + + "github.com/xeipuuv/gojsonreference" +) + +type schemaPoolDocument struct { + Document interface{} + Draft *Draft +} + +type schemaPool struct { + schemaPoolDocuments map[string]*schemaPoolDocument + jsonLoaderFactory JSONLoaderFactory + autoDetect *bool +} + +func (p *schemaPool) parseReferences(document interface{}, ref gojsonreference.JsonReference, pooled bool) error { + + var ( + draft *Draft + err error + reference = ref.String() + ) + // Only the root document should be added to the schema pool if pooled is true + if _, ok := p.schemaPoolDocuments[reference]; pooled && ok { + return fmt.Errorf("Reference already exists: \"%s\"", reference) + } + + if *p.autoDetect { + _, draft, err = parseSchemaURL(document) + if err != nil { + return err + } + } + + err = p.parseReferencesRecursive(document, ref, draft) + + if pooled { + p.schemaPoolDocuments[reference] = &schemaPoolDocument{Document: document, Draft: draft} + } + + return err +} + +func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonreference.JsonReference, draft *Draft) error { + // parseReferencesRecursive parses a JSON document and resolves all $id and $ref references. + // For $ref references it takes into account the $id scope it is in and replaces + // the reference by the absolute resolved reference + + // When encountering errors it fails silently. Error handling is done when the schema + // is syntactically parsed and any error encountered here should also come up there. + switch m := document.(type) { + case []interface{}: + for _, v := range m { + p.parseReferencesRecursive(v, ref, draft) + } + case map[string]interface{}: + localRef := &ref + + keyID := KEY_ID_NEW + if existsMapKey(m, KEY_ID) { + keyID = KEY_ID + } + if existsMapKey(m, keyID) && isKind(m[keyID], reflect.String) { + jsonReference, err := gojsonreference.NewJsonReference(m[keyID].(string)) + if err == nil { + localRef, err = ref.Inherits(jsonReference) + if err == nil { + if _, ok := p.schemaPoolDocuments[localRef.String()]; ok { + return fmt.Errorf("Reference already exists: \"%s\"", localRef.String()) + } + p.schemaPoolDocuments[localRef.String()] = &schemaPoolDocument{Document: document, Draft: draft} + } + } + } + + if existsMapKey(m, KEY_REF) && isKind(m[KEY_REF], reflect.String) { + jsonReference, err := gojsonreference.NewJsonReference(m[KEY_REF].(string)) + if err == nil { + absoluteRef, err := localRef.Inherits(jsonReference) + if err == nil { + m[KEY_REF] = absoluteRef.String() + } + } + } + + for k, v := range m { + // const and enums should be interpreted literally, so ignore them + if k == KEY_CONST || k == KEY_ENUM { + continue + } + // Something like a property or a dependency is not a valid schema, as it might describe properties named "$ref", "$id" or "const", etc + // Therefore don't treat it like a schema. + if k == KEY_PROPERTIES || k == KEY_DEPENDENCIES || k == KEY_PATTERN_PROPERTIES { + if child, ok := v.(map[string]interface{}); ok { + for _, v := range child { + p.parseReferencesRecursive(v, *localRef, draft) + } + } + } else { + p.parseReferencesRecursive(v, *localRef, draft) + } + } + } + return nil +} + +func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) { + + var ( + spd *schemaPoolDocument + draft *Draft + ok bool + err error + ) + + if internalLogEnabled { + internalLog("Get Document ( %s )", reference.String()) + } + + // Create a deep copy, so we can remove the fragment part later on without altering the original + refToURL, _ := gojsonreference.NewJsonReference(reference.String()) + + // First check if the given fragment is a location independent identifier + // http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3 + + if spd, ok = p.schemaPoolDocuments[refToURL.String()]; ok { + if internalLogEnabled { + internalLog(" From pool") + } + return spd, nil + } + + // If the given reference is not a location independent identifier, + // strip the fragment and look for a document with it's base URI + + refToURL.GetUrl().Fragment = "" + + if cachedSpd, ok := p.schemaPoolDocuments[refToURL.String()]; ok { + document, _, err := reference.GetPointer().Get(cachedSpd.Document) + + if err != nil { + return nil, err + } + + if internalLogEnabled { + internalLog(" From pool") + } + + spd = &schemaPoolDocument{Document: document, Draft: cachedSpd.Draft} + p.schemaPoolDocuments[reference.String()] = spd + + return spd, nil + } + + // It is not possible to load anything remotely that is not canonical... + if !reference.IsCanonical() { + return nil, errors.New(formatErrorDescription( + Locale.ReferenceMustBeCanonical(), + ErrorDetails{"reference": reference.String()}, + )) + } + + jsonReferenceLoader := p.jsonLoaderFactory.New(reference.String()) + document, err := jsonReferenceLoader.LoadJSON() + + if err != nil { + return nil, err + } + + // add the whole document to the pool for potential re-use + p.parseReferences(document, refToURL, true) + + _, draft, _ = parseSchemaURL(document) + + // resolve the potential fragment and also cache it + document, _, err = reference.GetPointer().Get(document) + + if err != nil { + return nil, err + } + + return &schemaPoolDocument{Document: document, Draft: draft}, nil +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go b/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go new file mode 100644 index 0000000000..6e5e1b5cdb --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go @@ -0,0 +1,68 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Pool of referenced schemas. +// +// created 25-06-2013 + +package gojsonschema + +import ( + "fmt" +) + +type schemaReferencePool struct { + documents map[string]*subSchema +} + +func newSchemaReferencePool() *schemaReferencePool { + + p := &schemaReferencePool{} + p.documents = make(map[string]*subSchema) + + return p +} + +func (p *schemaReferencePool) Get(ref string) (r *subSchema, o bool) { + + if internalLogEnabled { + internalLog(fmt.Sprintf("Schema Reference ( %s )", ref)) + } + + if sch, ok := p.documents[ref]; ok { + if internalLogEnabled { + internalLog(fmt.Sprintf(" From pool")) + } + return sch, true + } + + return nil, false +} + +func (p *schemaReferencePool) Add(ref string, sch *subSchema) { + + if internalLogEnabled { + internalLog(fmt.Sprintf("Add Schema Reference %s to pool", ref)) + } + if _, ok := p.documents[ref]; !ok { + p.documents[ref] = sch + } +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaType.go b/vendor/github.com/xeipuuv/gojsonschema/schemaType.go new file mode 100644 index 0000000000..36b447a291 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaType.go @@ -0,0 +1,83 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Helper structure to handle schema types, and the combination of them. +// +// created 28-02-2013 + +package gojsonschema + +import ( + "errors" + "fmt" + "strings" +) + +type jsonSchemaType struct { + types []string +} + +// Is the schema typed ? that is containing at least one type +// When not typed, the schema does not need any type validation +func (t *jsonSchemaType) IsTyped() bool { + return len(t.types) > 0 +} + +func (t *jsonSchemaType) Add(etype string) error { + + if !isStringInSlice(JSON_TYPES, etype) { + return errors.New(formatErrorDescription(Locale.NotAValidType(), ErrorDetails{"given": "/" + etype + "/", "expected": JSON_TYPES})) + } + + if t.Contains(etype) { + return errors.New(formatErrorDescription(Locale.Duplicated(), ErrorDetails{"type": etype})) + } + + t.types = append(t.types, etype) + + return nil +} + +func (t *jsonSchemaType) Contains(etype string) bool { + + for _, v := range t.types { + if v == etype { + return true + } + } + + return false +} + +func (t *jsonSchemaType) String() string { + + if len(t.types) == 0 { + return STRING_UNDEFINED // should never happen + } + + // Displayed as a list [type1,type2,...] + if len(t.types) > 1 { + return fmt.Sprintf("[%s]", strings.Join(t.types, ",")) + } + + // Only one type: name only + return t.types[0] +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/subSchema.go b/vendor/github.com/xeipuuv/gojsonschema/subSchema.go new file mode 100644 index 0000000000..ec779812c3 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/subSchema.go @@ -0,0 +1,149 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Defines the structure of a sub-subSchema. +// A sub-subSchema can contain other sub-schemas. +// +// created 27-02-2013 + +package gojsonschema + +import ( + "github.com/xeipuuv/gojsonreference" + "math/big" + "regexp" +) + +// Constants +const ( + KEY_SCHEMA = "$schema" + KEY_ID = "id" + KEY_ID_NEW = "$id" + KEY_REF = "$ref" + KEY_TITLE = "title" + KEY_DESCRIPTION = "description" + KEY_TYPE = "type" + KEY_ITEMS = "items" + KEY_ADDITIONAL_ITEMS = "additionalItems" + KEY_PROPERTIES = "properties" + KEY_PATTERN_PROPERTIES = "patternProperties" + KEY_ADDITIONAL_PROPERTIES = "additionalProperties" + KEY_PROPERTY_NAMES = "propertyNames" + KEY_DEFINITIONS = "definitions" + KEY_MULTIPLE_OF = "multipleOf" + KEY_MINIMUM = "minimum" + KEY_MAXIMUM = "maximum" + KEY_EXCLUSIVE_MINIMUM = "exclusiveMinimum" + KEY_EXCLUSIVE_MAXIMUM = "exclusiveMaximum" + KEY_MIN_LENGTH = "minLength" + KEY_MAX_LENGTH = "maxLength" + KEY_PATTERN = "pattern" + KEY_FORMAT = "format" + KEY_MIN_PROPERTIES = "minProperties" + KEY_MAX_PROPERTIES = "maxProperties" + KEY_DEPENDENCIES = "dependencies" + KEY_REQUIRED = "required" + KEY_MIN_ITEMS = "minItems" + KEY_MAX_ITEMS = "maxItems" + KEY_UNIQUE_ITEMS = "uniqueItems" + KEY_CONTAINS = "contains" + KEY_CONST = "const" + KEY_ENUM = "enum" + KEY_ONE_OF = "oneOf" + KEY_ANY_OF = "anyOf" + KEY_ALL_OF = "allOf" + KEY_NOT = "not" + KEY_IF = "if" + KEY_THEN = "then" + KEY_ELSE = "else" +) + +type subSchema struct { + draft *Draft + + // basic subSchema meta properties + id *gojsonreference.JsonReference + title *string + description *string + + property string + + // Quick pass/fail for boolean schemas + pass *bool + + // Types associated with the subSchema + types jsonSchemaType + + // Reference url + ref *gojsonreference.JsonReference + // Schema referenced + refSchema *subSchema + + // hierarchy + parent *subSchema + itemsChildren []*subSchema + itemsChildrenIsSingleSchema bool + propertiesChildren []*subSchema + + // validation : number / integer + multipleOf *big.Rat + maximum *big.Rat + exclusiveMaximum *big.Rat + minimum *big.Rat + exclusiveMinimum *big.Rat + + // validation : string + minLength *int + maxLength *int + pattern *regexp.Regexp + format string + + // validation : object + minProperties *int + maxProperties *int + required []string + + dependencies map[string]interface{} + additionalProperties interface{} + patternProperties map[string]*subSchema + propertyNames *subSchema + + // validation : array + minItems *int + maxItems *int + uniqueItems bool + contains *subSchema + + additionalItems interface{} + + // validation : all + _const *string //const is a golang keyword + enum []string + + // validation : subSchema + oneOf []*subSchema + anyOf []*subSchema + allOf []*subSchema + not *subSchema + _if *subSchema // if/else are golang keywords + _then *subSchema + _else *subSchema +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/types.go b/vendor/github.com/xeipuuv/gojsonschema/types.go new file mode 100644 index 0000000000..0e6fd51735 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/types.go @@ -0,0 +1,62 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Contains const types for schema and JSON. +// +// created 28-02-2013 + +package gojsonschema + +// Type constants +const ( + TYPE_ARRAY = `array` + TYPE_BOOLEAN = `boolean` + TYPE_INTEGER = `integer` + TYPE_NUMBER = `number` + TYPE_NULL = `null` + TYPE_OBJECT = `object` + TYPE_STRING = `string` +) + +// JSON_TYPES hosts the list of type that are supported in JSON +var JSON_TYPES []string + +// SCHEMA_TYPES hosts the list of type that are supported in schemas +var SCHEMA_TYPES []string + +func init() { + JSON_TYPES = []string{ + TYPE_ARRAY, + TYPE_BOOLEAN, + TYPE_INTEGER, + TYPE_NUMBER, + TYPE_NULL, + TYPE_OBJECT, + TYPE_STRING} + + SCHEMA_TYPES = []string{ + TYPE_ARRAY, + TYPE_BOOLEAN, + TYPE_INTEGER, + TYPE_NUMBER, + TYPE_OBJECT, + TYPE_STRING} +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/utils.go b/vendor/github.com/xeipuuv/gojsonschema/utils.go new file mode 100644 index 0000000000..a17d22e3bd --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/utils.go @@ -0,0 +1,197 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Various utility functions. +// +// created 26-02-2013 + +package gojsonschema + +import ( + "encoding/json" + "math/big" + "reflect" +) + +func isKind(what interface{}, kinds ...reflect.Kind) bool { + target := what + if isJSONNumber(what) { + // JSON Numbers are strings! + target = *mustBeNumber(what) + } + targetKind := reflect.ValueOf(target).Kind() + for _, kind := range kinds { + if targetKind == kind { + return true + } + } + return false +} + +func existsMapKey(m map[string]interface{}, k string) bool { + _, ok := m[k] + return ok +} + +func isStringInSlice(s []string, what string) bool { + for i := range s { + if s[i] == what { + return true + } + } + return false +} + +// indexStringInSlice returns the index of the first instance of 'what' in s or -1 if it is not found in s. +func indexStringInSlice(s []string, what string) int { + for i := range s { + if s[i] == what { + return i + } + } + return -1 +} + +func marshalToJSONString(value interface{}) (*string, error) { + + mBytes, err := json.Marshal(value) + if err != nil { + return nil, err + } + + sBytes := string(mBytes) + return &sBytes, nil +} + +func marshalWithoutNumber(value interface{}) (*string, error) { + + // The JSON is decoded using https://golang.org/pkg/encoding/json/#Decoder.UseNumber + // This means the numbers are internally still represented as strings and therefore 1.00 is unequal to 1 + // One way to eliminate these differences is to decode and encode the JSON one more time without Decoder.UseNumber + // so that these differences in representation are removed + + jsonString, err := marshalToJSONString(value) + if err != nil { + return nil, err + } + + var document interface{} + + err = json.Unmarshal([]byte(*jsonString), &document) + if err != nil { + return nil, err + } + + return marshalToJSONString(document) +} + +func isJSONNumber(what interface{}) bool { + + switch what.(type) { + + case json.Number: + return true + } + + return false +} + +func checkJSONInteger(what interface{}) (isInt bool) { + + jsonNumber := what.(json.Number) + + bigFloat, isValidNumber := new(big.Rat).SetString(string(jsonNumber)) + + return isValidNumber && bigFloat.IsInt() + +} + +// same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER +const ( + maxJSONFloat = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1 + minJSONFloat = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1 +) + +func mustBeInteger(what interface{}) *int { + + if isJSONNumber(what) { + + number := what.(json.Number) + + isInt := checkJSONInteger(number) + + if isInt { + + int64Value, err := number.Int64() + if err != nil { + return nil + } + + int32Value := int(int64Value) + return &int32Value + } + + } + + return nil +} + +func mustBeNumber(what interface{}) *big.Rat { + + if isJSONNumber(what) { + number := what.(json.Number) + float64Value, success := new(big.Rat).SetString(string(number)) + if success { + return float64Value + } + } + + return nil + +} + +func convertDocumentNode(val interface{}) interface{} { + + if lval, ok := val.([]interface{}); ok { + + res := []interface{}{} + for _, v := range lval { + res = append(res, convertDocumentNode(v)) + } + + return res + + } + + if mval, ok := val.(map[interface{}]interface{}); ok { + + res := map[string]interface{}{} + + for k, v := range mval { + res[k.(string)] = convertDocumentNode(v) + } + + return res + + } + + return val +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/validation.go b/vendor/github.com/xeipuuv/gojsonschema/validation.go new file mode 100644 index 0000000000..74091bca19 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/validation.go @@ -0,0 +1,858 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Extends Schema and subSchema, implements the validation phase. +// +// created 28-02-2013 + +package gojsonschema + +import ( + "encoding/json" + "math/big" + "reflect" + "regexp" + "strconv" + "strings" + "unicode/utf8" +) + +// Validate loads and validates a JSON schema +func Validate(ls JSONLoader, ld JSONLoader) (*Result, error) { + // load schema + schema, err := NewSchema(ls) + if err != nil { + return nil, err + } + return schema.Validate(ld) +} + +// Validate loads and validates a JSON document +func (v *Schema) Validate(l JSONLoader) (*Result, error) { + root, err := l.LoadJSON() + if err != nil { + return nil, err + } + return v.validateDocument(root), nil +} + +func (v *Schema) validateDocument(root interface{}) *Result { + result := &Result{} + context := NewJsonContext(STRING_CONTEXT_ROOT, nil) + v.rootSchema.validateRecursive(v.rootSchema, root, result, context) + return result +} + +func (v *subSchema) subValidateWithContext(document interface{}, context *JsonContext) *Result { + result := &Result{} + v.validateRecursive(v, document, result, context) + return result +} + +// Walker function to validate the json recursively against the subSchema +func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *JsonContext) { + + if internalLogEnabled { + internalLog("validateRecursive %s", context.String()) + internalLog(" %v", currentNode) + } + + // Handle true/false schema as early as possible as all other fields will be nil + if currentSubSchema.pass != nil { + if !*currentSubSchema.pass { + result.addInternalError( + new(FalseError), + context, + currentNode, + ErrorDetails{}, + ) + } + return + } + + // Handle referenced schemas, returns directly when a $ref is found + if currentSubSchema.refSchema != nil { + v.validateRecursive(currentSubSchema.refSchema, currentNode, result, context) + return + } + + // Check for null value + if currentNode == nil { + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_NULL) { + result.addInternalError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_NULL, + }, + ) + return + } + + currentSubSchema.validateSchema(currentSubSchema, currentNode, result, context) + v.validateCommon(currentSubSchema, currentNode, result, context) + + } else { // Not a null value + + if isJSONNumber(currentNode) { + + value := currentNode.(json.Number) + + isInt := checkJSONInteger(value) + + validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isInt && currentSubSchema.types.Contains(TYPE_INTEGER)) + + if currentSubSchema.types.IsTyped() && !validType { + + givenType := TYPE_INTEGER + if !isInt { + givenType = TYPE_NUMBER + } + + result.addInternalError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": givenType, + }, + ) + return + } + + currentSubSchema.validateSchema(currentSubSchema, value, result, context) + v.validateNumber(currentSubSchema, value, result, context) + v.validateCommon(currentSubSchema, value, result, context) + v.validateString(currentSubSchema, value, result, context) + + } else { + + rValue := reflect.ValueOf(currentNode) + rKind := rValue.Kind() + + switch rKind { + + // Slice => JSON array + + case reflect.Slice: + + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_ARRAY) { + result.addInternalError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_ARRAY, + }, + ) + return + } + + castCurrentNode := currentNode.([]interface{}) + + currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context) + + v.validateArray(currentSubSchema, castCurrentNode, result, context) + v.validateCommon(currentSubSchema, castCurrentNode, result, context) + + // Map => JSON object + + case reflect.Map: + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_OBJECT) { + result.addInternalError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_OBJECT, + }, + ) + return + } + + castCurrentNode, ok := currentNode.(map[string]interface{}) + if !ok { + castCurrentNode = convertDocumentNode(currentNode).(map[string]interface{}) + } + + currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context) + + v.validateObject(currentSubSchema, castCurrentNode, result, context) + v.validateCommon(currentSubSchema, castCurrentNode, result, context) + + for _, pSchema := range currentSubSchema.propertiesChildren { + nextNode, ok := castCurrentNode[pSchema.property] + if ok { + subContext := NewJsonContext(pSchema.property, context) + v.validateRecursive(pSchema, nextNode, result, subContext) + } + } + + // Simple JSON values : string, number, boolean + + case reflect.Bool: + + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_BOOLEAN) { + result.addInternalError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_BOOLEAN, + }, + ) + return + } + + value := currentNode.(bool) + + currentSubSchema.validateSchema(currentSubSchema, value, result, context) + v.validateNumber(currentSubSchema, value, result, context) + v.validateCommon(currentSubSchema, value, result, context) + v.validateString(currentSubSchema, value, result, context) + + case reflect.String: + + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_STRING) { + result.addInternalError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_STRING, + }, + ) + return + } + + value := currentNode.(string) + + currentSubSchema.validateSchema(currentSubSchema, value, result, context) + v.validateNumber(currentSubSchema, value, result, context) + v.validateCommon(currentSubSchema, value, result, context) + v.validateString(currentSubSchema, value, result, context) + + } + + } + + } + + result.incrementScore() +} + +// Different kinds of validation there, subSchema / common / array / object / string... +func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *JsonContext) { + + if internalLogEnabled { + internalLog("validateSchema %s", context.String()) + internalLog(" %v", currentNode) + } + + if len(currentSubSchema.anyOf) > 0 { + + validatedAnyOf := false + var bestValidationResult *Result + + for _, anyOfSchema := range currentSubSchema.anyOf { + if !validatedAnyOf { + validationResult := anyOfSchema.subValidateWithContext(currentNode, context) + validatedAnyOf = validationResult.Valid() + + if !validatedAnyOf && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) { + bestValidationResult = validationResult + } + } + } + if !validatedAnyOf { + + result.addInternalError(new(NumberAnyOfError), context, currentNode, ErrorDetails{}) + + if bestValidationResult != nil { + // add error messages of closest matching subSchema as + // that's probably the one the user was trying to match + result.mergeErrors(bestValidationResult) + } + } + } + + if len(currentSubSchema.oneOf) > 0 { + + nbValidated := 0 + var bestValidationResult *Result + + for _, oneOfSchema := range currentSubSchema.oneOf { + validationResult := oneOfSchema.subValidateWithContext(currentNode, context) + if validationResult.Valid() { + nbValidated++ + } else if nbValidated == 0 && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) { + bestValidationResult = validationResult + } + } + + if nbValidated != 1 { + + result.addInternalError(new(NumberOneOfError), context, currentNode, ErrorDetails{}) + + if nbValidated == 0 { + // add error messages of closest matching subSchema as + // that's probably the one the user was trying to match + result.mergeErrors(bestValidationResult) + } + } + + } + + if len(currentSubSchema.allOf) > 0 { + nbValidated := 0 + + for _, allOfSchema := range currentSubSchema.allOf { + validationResult := allOfSchema.subValidateWithContext(currentNode, context) + if validationResult.Valid() { + nbValidated++ + } + result.mergeErrors(validationResult) + } + + if nbValidated != len(currentSubSchema.allOf) { + result.addInternalError(new(NumberAllOfError), context, currentNode, ErrorDetails{}) + } + } + + if currentSubSchema.not != nil { + validationResult := currentSubSchema.not.subValidateWithContext(currentNode, context) + if validationResult.Valid() { + result.addInternalError(new(NumberNotError), context, currentNode, ErrorDetails{}) + } + } + + if currentSubSchema.dependencies != nil && len(currentSubSchema.dependencies) > 0 { + if isKind(currentNode, reflect.Map) { + for elementKey := range currentNode.(map[string]interface{}) { + if dependency, ok := currentSubSchema.dependencies[elementKey]; ok { + switch dependency := dependency.(type) { + + case []string: + for _, dependOnKey := range dependency { + if _, dependencyResolved := currentNode.(map[string]interface{})[dependOnKey]; !dependencyResolved { + result.addInternalError( + new(MissingDependencyError), + context, + currentNode, + ErrorDetails{"dependency": dependOnKey}, + ) + } + } + + case *subSchema: + dependency.validateRecursive(dependency, currentNode, result, context) + } + } + } + } + } + + if currentSubSchema._if != nil { + validationResultIf := currentSubSchema._if.subValidateWithContext(currentNode, context) + if currentSubSchema._then != nil && validationResultIf.Valid() { + validationResultThen := currentSubSchema._then.subValidateWithContext(currentNode, context) + if !validationResultThen.Valid() { + result.addInternalError(new(ConditionThenError), context, currentNode, ErrorDetails{}) + result.mergeErrors(validationResultThen) + } + } + if currentSubSchema._else != nil && !validationResultIf.Valid() { + validationResultElse := currentSubSchema._else.subValidateWithContext(currentNode, context) + if !validationResultElse.Valid() { + result.addInternalError(new(ConditionElseError), context, currentNode, ErrorDetails{}) + result.mergeErrors(validationResultElse) + } + } + } + + result.incrementScore() +} + +func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) { + + if internalLogEnabled { + internalLog("validateCommon %s", context.String()) + internalLog(" %v", value) + } + + // const: + if currentSubSchema._const != nil { + vString, err := marshalWithoutNumber(value) + if err != nil { + result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err}) + } + if *vString != *currentSubSchema._const { + result.addInternalError(new(ConstError), + context, + value, + ErrorDetails{ + "allowed": *currentSubSchema._const, + }, + ) + } + } + + // enum: + if len(currentSubSchema.enum) > 0 { + vString, err := marshalWithoutNumber(value) + if err != nil { + result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err}) + } + if !isStringInSlice(currentSubSchema.enum, *vString) { + result.addInternalError( + new(EnumError), + context, + value, + ErrorDetails{ + "allowed": strings.Join(currentSubSchema.enum, ", "), + }, + ) + } + } + + result.incrementScore() +} + +func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface{}, result *Result, context *JsonContext) { + + if internalLogEnabled { + internalLog("validateArray %s", context.String()) + internalLog(" %v", value) + } + + nbValues := len(value) + + // TODO explain + if currentSubSchema.itemsChildrenIsSingleSchema { + for i := range value { + subContext := NewJsonContext(strconv.Itoa(i), context) + validationResult := currentSubSchema.itemsChildren[0].subValidateWithContext(value[i], subContext) + result.mergeErrors(validationResult) + } + } else { + if currentSubSchema.itemsChildren != nil && len(currentSubSchema.itemsChildren) > 0 { + + nbItems := len(currentSubSchema.itemsChildren) + + // while we have both schemas and values, check them against each other + for i := 0; i != nbItems && i != nbValues; i++ { + subContext := NewJsonContext(strconv.Itoa(i), context) + validationResult := currentSubSchema.itemsChildren[i].subValidateWithContext(value[i], subContext) + result.mergeErrors(validationResult) + } + + if nbItems < nbValues { + // we have less schemas than elements in the instance array, + // but that might be ok if "additionalItems" is specified. + + switch currentSubSchema.additionalItems.(type) { + case bool: + if !currentSubSchema.additionalItems.(bool) { + result.addInternalError(new(ArrayNoAdditionalItemsError), context, value, ErrorDetails{}) + } + case *subSchema: + additionalItemSchema := currentSubSchema.additionalItems.(*subSchema) + for i := nbItems; i != nbValues; i++ { + subContext := NewJsonContext(strconv.Itoa(i), context) + validationResult := additionalItemSchema.subValidateWithContext(value[i], subContext) + result.mergeErrors(validationResult) + } + } + } + } + } + + // minItems & maxItems + if currentSubSchema.minItems != nil { + if nbValues < int(*currentSubSchema.minItems) { + result.addInternalError( + new(ArrayMinItemsError), + context, + value, + ErrorDetails{"min": *currentSubSchema.minItems}, + ) + } + } + if currentSubSchema.maxItems != nil { + if nbValues > int(*currentSubSchema.maxItems) { + result.addInternalError( + new(ArrayMaxItemsError), + context, + value, + ErrorDetails{"max": *currentSubSchema.maxItems}, + ) + } + } + + // uniqueItems: + if currentSubSchema.uniqueItems { + var stringifiedItems = make(map[string]int) + for j, v := range value { + vString, err := marshalWithoutNumber(v) + if err != nil { + result.addInternalError(new(InternalError), context, value, ErrorDetails{"err": err}) + } + if i, ok := stringifiedItems[*vString]; ok { + result.addInternalError( + new(ItemsMustBeUniqueError), + context, + value, + ErrorDetails{"type": TYPE_ARRAY, "i": i, "j": j}, + ) + } + stringifiedItems[*vString] = j + } + } + + // contains: + + if currentSubSchema.contains != nil { + validatedOne := false + var bestValidationResult *Result + + for i, v := range value { + subContext := NewJsonContext(strconv.Itoa(i), context) + + validationResult := currentSubSchema.contains.subValidateWithContext(v, subContext) + if validationResult.Valid() { + validatedOne = true + break + } else { + if bestValidationResult == nil || validationResult.score > bestValidationResult.score { + bestValidationResult = validationResult + } + } + } + if !validatedOne { + result.addInternalError( + new(ArrayContainsError), + context, + value, + ErrorDetails{}, + ) + if bestValidationResult != nil { + result.mergeErrors(bestValidationResult) + } + } + } + + result.incrementScore() +} + +func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string]interface{}, result *Result, context *JsonContext) { + + if internalLogEnabled { + internalLog("validateObject %s", context.String()) + internalLog(" %v", value) + } + + // minProperties & maxProperties: + if currentSubSchema.minProperties != nil { + if len(value) < int(*currentSubSchema.minProperties) { + result.addInternalError( + new(ArrayMinPropertiesError), + context, + value, + ErrorDetails{"min": *currentSubSchema.minProperties}, + ) + } + } + if currentSubSchema.maxProperties != nil { + if len(value) > int(*currentSubSchema.maxProperties) { + result.addInternalError( + new(ArrayMaxPropertiesError), + context, + value, + ErrorDetails{"max": *currentSubSchema.maxProperties}, + ) + } + } + + // required: + for _, requiredProperty := range currentSubSchema.required { + _, ok := value[requiredProperty] + if ok { + result.incrementScore() + } else { + result.addInternalError( + new(RequiredError), + context, + value, + ErrorDetails{"property": requiredProperty}, + ) + } + } + + // additionalProperty & patternProperty: + for pk := range value { + + // Check whether this property is described by "properties" + found := false + for _, spValue := range currentSubSchema.propertiesChildren { + if pk == spValue.property { + found = true + } + } + + // Check whether this property is described by "patternProperties" + ppMatch := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context) + + // If it is not described by neither "properties" nor "patternProperties" it must pass "additionalProperties" + if !found && !ppMatch { + switch ap := currentSubSchema.additionalProperties.(type) { + case bool: + // Handle the boolean case separately as it's cleaner to return a specific error than failing to pass the false schema + if !ap { + result.addInternalError( + new(AdditionalPropertyNotAllowedError), + context, + value[pk], + ErrorDetails{"property": pk}, + ) + + } + case *subSchema: + validationResult := ap.subValidateWithContext(value[pk], NewJsonContext(pk, context)) + result.mergeErrors(validationResult) + } + } + } + + // propertyNames: + if currentSubSchema.propertyNames != nil { + for pk := range value { + validationResult := currentSubSchema.propertyNames.subValidateWithContext(pk, context) + if !validationResult.Valid() { + result.addInternalError(new(InvalidPropertyNameError), + context, + value, ErrorDetails{ + "property": pk, + }) + result.mergeErrors(validationResult) + } + } + } + + result.incrementScore() +} + +func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *JsonContext) bool { + + if internalLogEnabled { + internalLog("validatePatternProperty %s", context.String()) + internalLog(" %s %v", key, value) + } + + validated := false + + for pk, pv := range currentSubSchema.patternProperties { + if matches, _ := regexp.MatchString(pk, key); matches { + validated = true + subContext := NewJsonContext(key, context) + validationResult := pv.subValidateWithContext(value, subContext) + result.mergeErrors(validationResult) + } + } + + if !validated { + return false + } + + result.incrementScore() + return true +} + +func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) { + + // Ignore JSON numbers + if isJSONNumber(value) { + return + } + + // Ignore non strings + if !isKind(value, reflect.String) { + return + } + + if internalLogEnabled { + internalLog("validateString %s", context.String()) + internalLog(" %v", value) + } + + stringValue := value.(string) + + // minLength & maxLength: + if currentSubSchema.minLength != nil { + if utf8.RuneCount([]byte(stringValue)) < int(*currentSubSchema.minLength) { + result.addInternalError( + new(StringLengthGTEError), + context, + value, + ErrorDetails{"min": *currentSubSchema.minLength}, + ) + } + } + if currentSubSchema.maxLength != nil { + if utf8.RuneCount([]byte(stringValue)) > int(*currentSubSchema.maxLength) { + result.addInternalError( + new(StringLengthLTEError), + context, + value, + ErrorDetails{"max": *currentSubSchema.maxLength}, + ) + } + } + + // pattern: + if currentSubSchema.pattern != nil { + if !currentSubSchema.pattern.MatchString(stringValue) { + result.addInternalError( + new(DoesNotMatchPatternError), + context, + value, + ErrorDetails{"pattern": currentSubSchema.pattern}, + ) + + } + } + + // format + if currentSubSchema.format != "" { + if !FormatCheckers.IsFormat(currentSubSchema.format, stringValue) { + result.addInternalError( + new(DoesNotMatchFormatError), + context, + value, + ErrorDetails{"format": currentSubSchema.format}, + ) + } + } + + result.incrementScore() +} + +func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) { + + // Ignore non numbers + if !isJSONNumber(value) { + return + } + + if internalLogEnabled { + internalLog("validateNumber %s", context.String()) + internalLog(" %v", value) + } + + number := value.(json.Number) + float64Value, _ := new(big.Rat).SetString(string(number)) + + // multipleOf: + if currentSubSchema.multipleOf != nil { + if q := new(big.Rat).Quo(float64Value, currentSubSchema.multipleOf); !q.IsInt() { + result.addInternalError( + new(MultipleOfError), + context, + number, + ErrorDetails{ + "multiple": new(big.Float).SetRat(currentSubSchema.multipleOf), + }, + ) + } + } + + //maximum & exclusiveMaximum: + if currentSubSchema.maximum != nil { + if float64Value.Cmp(currentSubSchema.maximum) == 1 { + result.addInternalError( + new(NumberLTEError), + context, + number, + ErrorDetails{ + "max": new(big.Float).SetRat(currentSubSchema.maximum), + }, + ) + } + } + if currentSubSchema.exclusiveMaximum != nil { + if float64Value.Cmp(currentSubSchema.exclusiveMaximum) >= 0 { + result.addInternalError( + new(NumberLTError), + context, + number, + ErrorDetails{ + "max": new(big.Float).SetRat(currentSubSchema.exclusiveMaximum), + }, + ) + } + } + + //minimum & exclusiveMinimum: + if currentSubSchema.minimum != nil { + if float64Value.Cmp(currentSubSchema.minimum) == -1 { + result.addInternalError( + new(NumberGTEError), + context, + number, + ErrorDetails{ + "min": new(big.Float).SetRat(currentSubSchema.minimum), + }, + ) + } + } + if currentSubSchema.exclusiveMinimum != nil { + if float64Value.Cmp(currentSubSchema.exclusiveMinimum) <= 0 { + result.addInternalError( + new(NumberGTError), + context, + number, + ErrorDetails{ + "min": new(big.Float).SetRat(currentSubSchema.exclusiveMinimum), + }, + ) + } + } + + // format + if currentSubSchema.format != "" { + if !FormatCheckers.IsFormat(currentSubSchema.format, float64Value) { + result.addInternalError( + new(DoesNotMatchFormatError), + context, + value, + ErrorDetails{"format": currentSubSchema.format}, + ) + } + } + + result.incrementScore() +} From 5d62313c258f5d77b9056c66789980d315a8e0fb Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Tue, 5 Oct 2021 11:55:48 +0530 Subject: [PATCH 03/26] json schema --- api/restHandler/PipelineConfigRestHandler.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index ec7cec8b1f..8f9769ed0b 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -23,6 +23,13 @@ import ( "encoding/json" "errors" "fmt" + "io" + "log" + "net/http" + "os" + "strconv" + "strings" + "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" bean2 "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/client/argocdServer/application" @@ -48,12 +55,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "gopkg.in/go-playground/validator.v9" - "io" - "log" - "net/http" - "os" - "strconv" - "strings" ) type PipelineConfigRestHandler interface { From d9e88f3ea048df18d97d2930e29a477248ef807f Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Tue, 5 Oct 2021 12:13:22 +0530 Subject: [PATCH 04/26] sql name change --- .../{24_docker_registry_down.sql => 24_docker_registry.down.sql} | 0 .../sql/{24_docker_registry_up.sql => 24_docker_registry.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename scripts/sql/{24_docker_registry_down.sql => 24_docker_registry.down.sql} (100%) rename scripts/sql/{24_docker_registry_up.sql => 24_docker_registry.up.sql} (100%) diff --git a/scripts/sql/24_docker_registry_down.sql b/scripts/sql/24_docker_registry.down.sql similarity index 100% rename from scripts/sql/24_docker_registry_down.sql rename to scripts/sql/24_docker_registry.down.sql diff --git a/scripts/sql/24_docker_registry_up.sql b/scripts/sql/24_docker_registry.up.sql similarity index 100% rename from scripts/sql/24_docker_registry_up.sql rename to scripts/sql/24_docker_registry.up.sql From 80ac883b4ce408961ceea557480363d1288ee8ad Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Tue, 5 Oct 2021 12:17:44 +0530 Subject: [PATCH 05/26] json files --- api/restHandler/PipelineConfigRestHandler.go | 6 +- tests/testdata/schema.json | 917 +++++++++++++++++++ 2 files changed, 920 insertions(+), 3 deletions(-) create mode 100644 tests/testdata/schema.json diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 8f9769ed0b..6578e1aa2e 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -1247,7 +1247,7 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr if err := json.Unmarshal(buff, &dat); err != nil { panic(err) } - f, err := os.Create("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/values.json") + f, err := os.Create("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/values.json") if err != nil { log.Fatal(err) @@ -3312,8 +3312,8 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo } func validatejson() { - schemaLoader := gojsonschema.NewReferenceLoader("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/schema.json") - documentLoader := gojsonschema.NewReferenceLoader("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/values.json") + schemaLoader := gojsonschema.NewReferenceLoader("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/schema.json") + documentLoader := gojsonschema.NewReferenceLoader("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/values.json") result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { diff --git a/tests/testdata/schema.json b/tests/testdata/schema.json new file mode 100644 index 0000000000..1dcc825ef7 --- /dev/null +++ b/tests/testdata/schema.json @@ -0,0 +1,917 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type" : "integer" + }, + "appId": { + "type" : "integer" + }, + "refChartTemplate": { + "type" : "string" + }, + "refChartTemplateVersion": { + "type" : "string" + }, + "valuesOverride": { + "type" : "object", + "properties": { + "ContainerPort": { + "type" : "object", + "properties": { + "envoyPort": { + "type" : "integer" + }, + "idleTimeout": { + "type" : "string" + }, + "name": { + "type" : "string" + }, + "port": { + "type" : "integer" + }, + "servicePort": { + "type" : "integer" + }, + "supportStreaming": { + "type" : "boolean" + }, + "useHTTP2": { + "type" : "boolean" + } + } + }, + "EnvVariables": { + "type" : "array" + }, + "GracePeriod": { + "type" : "integer" + }, + "LivenessProbe": { + "type" : "array", + "properties": { + "Path": { + "type" : "string" + }, + "command": { + "type" : "array" + }, + "failureThreshold": { + "type" : "integer" + }, + "httpHeader": { + "type" : "object", + "properties": { + "name": { + "type" : "string" + }, + "value": { + "type" : "string" + } + } + }, + "initialDelaySeconds": { + "type" : "integer" + }, + "periodSeconds": { + "type" : "integer" + }, + "port": { + "type" : "integer" + }, + "scheme": { + "type" : "string" + }, + "successThreshold": { + "type" : "integer" + }, + "tcp": { + "type" : "boolean" + }, + "timeoutSeconds": { + "type" : "integer" + } + } + }, + "MaxSurge": { + "type" : "integer" + }, + "MaxUnavailable": { + "type" : "integer" + }, + "MinReadySeconds": { + "type" : "integer" + }, + "ReadinessProbe": { + "type" : "object", + "properties": { + "Path": { + "type" : "string" + }, + "command": { + "type" : "array" + }, + "failureThreshold": { + "type" : "integer" + }, + "httpHeader": { + "type" : "object", + "properties": { + "name": { + "type" : "string" + }, + "value": { + "type" : "string" + } + } + }, + "initialDelaySeconds": { + "type" : "integer" + }, + "periodSeconds": { + "type" : "integer" + }, + "port": { + "type" : "integer" + }, + "scheme": { + "type" : "string" + }, + "successThreshold": { + "type" : "integer" + }, + "tcp": { + "type" : "boolean" + }, + "timeoutSeconds": { + "type" : "integer" + } + } + }, + "Spec": { + "type" : "object", + "properties": { + "Affinity": { + "type" : "object", + "properties": { + "Key": { + "type":"null" + }, + "Values": { + "type" : "string" + }, + "key": { + "type" : "string" + } + } + } + } + }, + "args": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" + }, + "value": { + "type" : "array" + } + } + }, + "autoscaling": { + "type" : "object", + "properties": { + "MaxReplicas": { + "type" : "integer" + }, + "MinReplicas": { + "type" : "integer" + }, + "TargetCPUUtilizationPercentage": { + "type" : "integer" + }, + "TargetMemoryUtilizationPercentage": { + "type" : "integer" + }, + "enabled": { + "type" : "boolean" + }, + "extraMetrics": { + "type" : "array" + } + } + }, + "command": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" + }, + "value": { + "type" : "array" + } + } + }, + "containers": { + "type" : "array" + }, + "dbMigrationConfig": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" + } + } + }, + "envoyproxy": { + "type" : "object", + "properties": { + "configMapName": { + "type" : "string" + }, + "image": { + "type" : "string" + }, + "resources": { + "type" : "object", + "properties": { + "limits": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + } + } + }, + "requests": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + + } + } + } + } + } + } + }, + "image": { + "type" : "object", + "properties": { + "pullPolicy": { + "type" : "string" + } + } + }, + "imagePullSecrets": { + "type" : "array" + }, + "ingress": { + "type" : "object", + "properties": { + "annotations": { + "type" : "object", + "properties": { + "kubernetes.io/ingress.class": { + "type" : "string" + }, + "nginx.ingress.kubernetes.io/force-ssl-redirect": { + "type" : "string" + }, + "nginx.ingress.kubernetes.io/ssl-redirect": { + "type" : "string" + } + } + }, + "enabled": { + "type" : "boolean" + }, + "hosts": { + "type" : "array", + "properties": { + "host": { + "type" : "string" + }, + "paths": { + "type" : "array" + } + } + }, + "tls": { + "type" : "array" + } + } + }, + "ingressInternal": { + "type" : "object", + "properties": { + "annotations": { + "type" : "object" + }, + "enabled": { + "type" : "boolean" + }, + "hosts": { + "type" : "array", + "properties": { + "host": { + "type" : "string" + }, + "paths": { + "type" : "array" + } + } + }, + "tls": { + "type" : "array" + } + } + }, + "initContainers": { + "type" : "array" + }, + "pauseForSecondsBeforeSwitchActive": { + "type" : "integer" + }, + "podAnnotations": { + "type" : "object" + }, + "podLabels": { + "type" : "object" + }, + "prometheus": { + "type" : "object", + "properties": { + "release": { + "type" : "string" + } + } + }, + "rawYaml": { + "type" : "array" + }, + "replicaCount": { + "type" : "integer" + }, + "resources": { + "type" : "object", + "properties": { + "limits": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + } + } + }, + "requests": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + + } + } + } + } + }, + "secret": { + "type" : "object", + "properties": { + "data": { + "type" : "object" + }, + "enabled": { + "type" : "boolean" + } + } + }, + "server": { + "type" : "object", + "properties": { + "deployment": { + "type" : "object", + "properties": { + "image": { + "type" : "string" + }, + "image_tag": { + "type" : "string" + } + } + } + } + }, + "service": { + "type" : "object", + "properties": { + "annotations": { + "type" : "object" + }, + "type": { + "type" : "string" + } + } + }, + "servicemonitor": { + "type" : "object", + "properties": { + "additionalLabels": { + "type" : "object" + } + } + }, + "tolerations": { + "type" : "array" + }, + "volumeMounts": { + "type" : "array" + }, + "volumes": { + "type" : "array" + }, + "waitForSecondsBeforeScalingDown": { + "type" : "integer" + } + + } + }, + "defaultAppOverride": { + "type" : "object", + "properties": { + "ContainerPort": { + "type" : "array", + "properties": { + "envoyPort": { + "type" : "integer" + }, + "idleTimeout": { + "type" : "string" + }, + "name": { + "type" : "string" + }, + "port": { + "type" : "integer" + }, + "servicePort": { + "type" : "integer" + }, + "supportStreaming": { + "type" : "boolean" + }, + "useHTTP2": { + "type" : "boolean" + } + } + }, + "EnvVariables": { + "type" : "array" + }, + "GracePeriod": { + "type" : "integer" + }, + "LivenessProbe": { + "type" : "object", + "properties": { + "Path": { + "type" : "string" + }, + "command": { + "type" : "array" + }, + "failureThreshold": { + "type" : "integer" + }, + "httpHeader": { + "type" : "object", + "properties": { + "name": { + "type" : "string" + }, + "value": { + "type" : "string" + } + } + }, + "initialDelaySeconds": { + "type" : "integer" + }, + "periodSeconds": { + "type" : "integer" + }, + "port": { + "type" : "integer" + }, + "scheme": { + "type" : "string" + }, + "successThreshold": { + "type" : "integer" + }, + "tcp": { + "type" : "boolean" + }, + "timeoutSeconds": { + "type" : "integer" + } + } + }, + "MaxSurge": { + "type" : "integer" + }, + "MaxUnavailable": { + "type" : "integer" + }, + "MinReadySeconds": { + "type" : "integer" + }, + "ReadinessProbe": { + "type" : "object", + "properties": { + "Path": { + "type" : "string" + }, + "command": { + "type" : "array" + }, + "failureThreshold": { + "type" : "integer" + }, + "httpHeader": { + "type" : "object", + "properties": { + "name": { + "type" : "string" + }, + "value": { + "type" : "string" + } + } + }, + "initialDelaySeconds": { + "type" : "integer" + }, + "periodSeconds": { + "type" : "integer" + }, + "port": { + "type" : "integer" + }, + "scheme": { + "type" : "string" + }, + "successThreshold": { + "type" : "integer" + }, + "tcp": { + "type" : "boolean" + }, + "timeoutSeconds": { + "type" : "integer" + } + } + }, + "Spec": { + "type" : "object", + "properties": { + "Affinity": { + "type" : "object", + "properties": { + "Key": { + "type":"null" + }, + "Values": { + "type" : "string" + }, + "key": { + "type" : "string" + } + } + } + } + }, + "args": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" + }, + "value": { + "type" : "array" + } + } + }, + "autoscaling": { + "type" : "object", + "properties": { + "MaxReplicas": { + "type" : "integer" + }, + "MinReplicas": { + "type" : "integer" + }, + "TargetCPUUtilizationPercentage": { + "type" : "integer" + }, + "TargetMemoryUtilizationPercentage": { + "type" : "integer" + }, + "enabled": { + "type" : "boolean" + }, + "extraMetrics": { + "type" : "array" + } + } + }, + "command": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" + }, + "value": { + "type" : "array" + } + } + }, + "containers": { + "type" : "array" + }, + "dbMigrationConfig": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" + } + } + }, + "envoyproxy": { + "type" : "object", + "properties": { + "configMapName": { + "type" : "string" + }, + "image": { + "type" : "string" + }, + "resources": { + "type" : "object", + "properties": { + "limits": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + } + } + }, + "requests": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + + } + } + } + } + } + } + }, + "image": { + "type" : "object", + "properties": { + "pullPolicy": { + "type" : "string" + } + } + }, + "imagePullSecrets": { + "type" : "array" + }, + "ingress": { + "type" : "object", + "properties": { + "annotations": { + "type" : "object", + "properties": { + "kubernetes.io/ingress.class": { + "type" : "string" + }, + "nginx.ingress.kubernetes.io/force-ssl-redirect": { + "type" : "string" + }, + "nginx.ingress.kubernetes.io/ssl-redirect": { + "type" : "string" + } + } + }, + "enabled": { + "type" : "boolean" + }, + "hosts": { + "type" : "array", + "properties": { + "host": { + "type" : "string" + }, + "paths": { + "type" : "array" + } + } + }, + "tls": { + "type" : "array" + } + } + }, + "ingressInternal": { + "type" : "object", + "properties": { + "annotations": { + "type" : "object" + }, + "enabled": { + "type" : "boolean" + }, + "hosts": { + "type" : "array", + "properties": { + "host": { + "type" : "string" + }, + "paths": { + "type" : "array" + } + } + }, + "tls": { + "type" : "array" + } + } + }, + "initContainers": { + "type" : "array" + }, + "pauseForSecondsBeforeSwitchActive": { + "type" : "integer" + }, + "podAnnotations": { + "type" : "object" + }, + "podLabels": { + "type" : "object" + }, + "prometheus": { + "type" : "object", + "properties": { + "release": { + "type" : "string" + } + } + }, + "rawYaml": { + "type" : "array" + }, + "replicaCount": { + "type" : "integer" + }, + "resources": { + "type" : "object", + "properties": { + "limits": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + } + } + }, + "requests": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + + } + } + } + } + }, + "secret": { + "type" : "object", + "properties": { + "data": { + "type" : "object" + }, + "enabled": { + "type" : "boolean" + } + } + }, + "server": { + "type" : "object", + "properties": { + "deployment": { + "type" : "object", + "properties": { + "image": { + "type" : "string" + }, + "image_tag": { + "type" : "string" + } + } + } + } + }, + "service": { + "type" : "object", + "properties": { + "annotations": { + "type" : "object" + }, + "type": { + "type" : "string" + } + } + }, + "servicemonitor": { + "type" : "object", + "properties": { + "additionalLabels": { + "type" : "object" + } + } + }, + "tolerations": { + "type" : "array" + }, + "volumeMounts": { + "type" : "array" + }, + "volumes": { + "type" : "array" + }, + "waitForSecondsBeforeScalingDown": { + "type" : "integer" + } + + } + }, + "chartRefId": { + "type" : "integer" + }, + "latest": { + "type" : "boolean" + }, + "isAppMetricsEnabled": { + "type" : "boolean" + } + } + } From a452bb65c92b2de1093fbf3a7b3e5a647bab366b Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Tue, 5 Oct 2021 13:39:14 +0530 Subject: [PATCH 06/26] json schema code --- api/restHandler/PipelineConfigRestHandler.go | 60 +++++++++++--------- tests/testdata/schema.json | 8 +-- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 6578e1aa2e..047ac69fe7 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -24,9 +24,9 @@ import ( "errors" "fmt" "io" - "log" + "net/http" - "os" + "strconv" "strings" @@ -1238,34 +1238,38 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr return } handler.Logger.Infow("request payload, UpdateAppOverride", "payload", templateRequest) - buff, merr := json.Marshal(templateRequest) - if merr != nil { - handler.Logger.Errorw("marshal err, handleForwardResponseStreamError", "err", merr, "response", templateRequest) - } - var dat map[string]interface{} + // buff, merr := json.Marshal(templateRequest.ValuesOverride) + //if merr != nil { + // handler.Logger.Errorw("marshal err, handleForwardResponseStreamError", "err", merr, "response", templateRequest) + //} - if err := json.Unmarshal(buff, &dat); err != nil { - panic(err) - } - f, err := os.Create("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/values.json") + // var dat map[string]interface{} - if err != nil { - log.Fatal(err) - } - - defer f.Close() - - _, err2 := f.WriteString(string(buff)) - - if err2 != nil { - log.Fatal(err2) - } - // os.MkdirAll("/etc/docker/certs.d/"+domain, os.ModePerm) + // if err := json.Unmarshal(buff, &dat); err != nil { + // panic(err) + // } + fmt.Println(3) validatejson() + fmt.Println(4) + + //f, err := os.Create("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/values.json") + // + //if err != nil { + // log.Fatal(err) + //} + // + //defer f.Close() + // + //_, err2 := f.WriteString(string(buff)) + // + //if err2 != nil { + // log.Fatal(err2) + //} + // os.MkdirAll("/etc/docker/certs.d/"+domain, os.ModePerm) - strs_limit := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - strs_requests := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - fmt.Println(strs_limit["cpu"], strs_limit["memory"], strs_requests["cpu"], strs_requests["memory"], "aviral") + // strs_limit := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + // strs_requests := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + // fmt.Println(strs_limit["cpu"], strs_limit["memory"], strs_requests["cpu"], strs_requests["memory"], "aviral") token := r.Header.Get("token") app, err := handler.pipelineBuilder.GetApp(templateRequest.AppId) @@ -3312,8 +3316,8 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo } func validatejson() { - schemaLoader := gojsonschema.NewReferenceLoader("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/schema.json") - documentLoader := gojsonschema.NewReferenceLoader("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/values.json") + schemaLoader := gojsonschema.NewReferenceLoader("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/schema.json") + documentLoader := gojsonschema.NewReferenceLoader("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/values.json") result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { diff --git a/tests/testdata/schema.json b/tests/testdata/schema.json index 1dcc825ef7..5d6ff1f906 100644 --- a/tests/testdata/schema.json +++ b/tests/testdata/schema.json @@ -18,7 +18,7 @@ "type" : "object", "properties": { "ContainerPort": { - "type" : "object", + "type" : "array", "properties": { "envoyPort": { "type" : "integer" @@ -50,7 +50,7 @@ "type" : "integer" }, "LivenessProbe": { - "type" : "array", + "type" : "object", "properties": { "Path": { "type" : "string" @@ -157,7 +157,7 @@ "type" : "object", "properties": { "Key": { - "type":"null" + "type":"string" }, "Values": { "type" : "string" @@ -602,7 +602,7 @@ "type" : "object", "properties": { "Key": { - "type":"null" + "type":"string" }, "Values": { "type" : "string" From 9f8d391840137bc3bfb773843284456eaa2a4379 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 6 Oct 2021 01:47:07 +0530 Subject: [PATCH 07/26] validate json --- api/restHandler/PipelineConfigRestHandler.go | 29 ++++++++------- tests/testdata/schema.json | 6 ++-- tests/testdata/values.json | 2 +- .../xeipuuv/gojsonschema/format_checkers.go | 35 +++++++++++++++---- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 047ac69fe7..7c3fcadb4d 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -1238,19 +1238,17 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr return } handler.Logger.Infow("request payload, UpdateAppOverride", "payload", templateRequest) - // buff, merr := json.Marshal(templateRequest.ValuesOverride) - //if merr != nil { - // handler.Logger.Errorw("marshal err, handleForwardResponseStreamError", "err", merr, "response", templateRequest) - //} + buff, merr := json.Marshal(templateRequest) + if merr != nil { + handler.Logger.Errorw("marshal err, handleForwardResponseStreamError", "err", merr, "response", templateRequest) + } - // var dat map[string]interface{} + var dat pipeline.TemplateRequest - // if err := json.Unmarshal(buff, &dat); err != nil { - // panic(err) - // } - fmt.Println(3) - validatejson() - fmt.Println(4) + if err := json.Unmarshal(buff, &dat); err != nil { + panic(err) + } + x := validatejson(dat) //f, err := os.Create("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/values.json") // @@ -3315,9 +3313,10 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo writeJsonResp(w, err, suggestedName, http.StatusOK) } -func validatejson() { +func validatejson(jsondoc pipeline.TemplateRequest) bool { + schemaLoader := gojsonschema.NewReferenceLoader("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/schema.json") - documentLoader := gojsonschema.NewReferenceLoader("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/values.json") + documentLoader := gojsonschema.NewGoLoader(jsondoc) result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { @@ -3327,7 +3326,7 @@ func validatejson() { if result.Valid() { fmt.Println("ok") - + return true } else { fmt.Printf("The document is not valid. see errors :\n") @@ -3335,6 +3334,6 @@ func validatejson() { fmt.Println("not ok", err) } + return false } - } diff --git a/tests/testdata/schema.json b/tests/testdata/schema.json index 5d6ff1f906..8e5536dff2 100644 --- a/tests/testdata/schema.json +++ b/tests/testdata/schema.json @@ -157,7 +157,7 @@ "type" : "object", "properties": { "Key": { - "type":"string" + "type":"null" }, "Values": { "type" : "string" @@ -602,7 +602,7 @@ "type" : "object", "properties": { "Key": { - "type":"string" + "type":"null" }, "Values": { "type" : "string" @@ -914,4 +914,4 @@ "type" : "boolean" } } - } + } \ No newline at end of file diff --git a/tests/testdata/values.json b/tests/testdata/values.json index c35cb54bd4..41d12ba2c8 100644 --- a/tests/testdata/values.json +++ b/tests/testdata/values.json @@ -271,7 +271,7 @@ "image": "envoyproxy/envoy:v1.14.1", "resources": { "limits": { - "cpu": 50, + "cpu": "50", "memory": "50Mi" }, "requests": { diff --git a/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go index 0aa2fb0520..7046ec8bf1 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go +++ b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go @@ -146,8 +146,13 @@ var ( rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$") - UChecker, _ = regexp.Compile("([0-9.]+)m") - MemChecker, _ = regexp.Compile("([0-9.]+)Mi") + UChecker, _ = regexp.Compile("^([0-9.]+)m$") + NoUChecker, _ = regexp.Compile("^([0-9.]+)$") + MiChecker, _ = regexp.Compile("^[0-9]+Mi$") + GiChecker, _ = regexp.Compile("^[0-9]+Gi$") + TiChecker, _ = regexp.Compile("^[0-9]+Ti$") + PiChecker, _ = regexp.Compile("^[0-9]+Pi$") + KiChecker, _ = regexp.Compile("^[0-9]+Ki$") lock = new(sync.RWMutex) ) @@ -374,20 +379,36 @@ func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool { return rxRelJSONPointer.MatchString(asString) } - func (f UnitChecker) IsFormat(input interface{}) bool { asString, ok := input.(string) if !ok { return true } - - return UChecker.MatchString(asString) + if UChecker.MatchString(asString) { + return true + } else if NoUChecker.MatchString(asString) { + return true + } else { + return false + } } func (f MemoryChecker) IsFormat(input interface{}) bool { asString, ok := input.(string) if !ok { return true } - - return MemChecker.MatchString(asString) + // fmt.Println("hello", asString) + if MiChecker.MatchString(asString) { + return true + } else if GiChecker.MatchString(asString) { + return true + } else if TiChecker.MatchString(asString) { + return true + } else if PiChecker.MatchString(asString) { + return true + } else if KiChecker.MatchString(asString) { + return true + } else { + return false + } } From a5d28d0fc02dc71dcab66fb2a9419adf8e26434c Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 6 Oct 2021 13:48:57 +0530 Subject: [PATCH 08/26] json schema validation --- api/restHandler/PipelineConfigRestHandler.go | 197 +++++++++++++++---- 1 file changed, 160 insertions(+), 37 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 7c3fcadb4d..3c53153945 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -24,6 +24,8 @@ import ( "errors" "fmt" "io" + "math" + "regexp" "net/http" @@ -210,6 +212,16 @@ func NewPipelineRestHandlerImpl(pipelineBuilder pipeline.PipelineBuilder, Logger } } +type resourceParser struct { + name string + pattern string + regex *regexp.Regexp + conversions map[string]float64 +} + +var memoryParser *resourceParser +var cpuParser *resourceParser + const devtron = "DEVTRON" func (handler PipelineConfigRestHandlerImpl) DeleteApp(w http.ResponseWriter, r *http.Request) { @@ -1248,47 +1260,31 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr if err := json.Unmarshal(buff, &dat); err != nil { panic(err) } - x := validatejson(dat) - - //f, err := os.Create("https://github.com/devtron-labs/devtron/blob/deployment-template/tests/testdata/values.json") - // - //if err != nil { - // log.Fatal(err) - //} - // - //defer f.Close() - // - //_, err2 := f.WriteString(string(buff)) - // - //if err2 != nil { - // log.Fatal(err2) - //} - // os.MkdirAll("/etc/docker/certs.d/"+domain, os.ModePerm) - - // strs_limit := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - // strs_requests := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - // fmt.Println(strs_limit["cpu"], strs_limit["memory"], strs_requests["cpu"], strs_requests["memory"], "aviral") + if validatejson(dat) { + token := r.Header.Get("token") + app, err := handler.pipelineBuilder.GetApp(templateRequest.AppId) + if err != nil { + writeJsonResp(w, err, nil, http.StatusBadRequest) + return + } - token := r.Header.Get("token") - app, err := handler.pipelineBuilder.GetApp(templateRequest.AppId) - if err != nil { - writeJsonResp(w, err, nil, http.StatusBadRequest) - return - } + resourceName := handler.enforcerUtil.GetAppRBACName(app.AppName) + if ok := handler.enforcer.Enforce(token, rbac.ResourceApplications, rbac.ActionCreate, resourceName); !ok { + writeJsonResp(w, fmt.Errorf("unauthorized user"), "Unauthorized User", http.StatusForbidden) + return + } - resourceName := handler.enforcerUtil.GetAppRBACName(app.AppName) - if ok := handler.enforcer.Enforce(token, rbac.ResourceApplications, rbac.ActionCreate, resourceName); !ok { - writeJsonResp(w, fmt.Errorf("unauthorized user"), "Unauthorized User", http.StatusForbidden) - return + createResp, err := handler.chartService.UpdateAppOverride(&templateRequest) + if err != nil { + handler.Logger.Errorw("service err, UpdateAppOverride", "err", err, "payload", templateRequest) + writeJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + writeJsonResp(w, err, createResp, http.StatusOK) + } else { + fmt.Println("Values are incorrect") } - createResp, err := handler.chartService.UpdateAppOverride(&templateRequest) - if err != nil { - handler.Logger.Errorw("service err, UpdateAppOverride", "err", err, "payload", templateRequest) - writeJsonResp(w, err, nil, http.StatusInternalServerError) - return - } - writeJsonResp(w, err, createResp, http.StatusOK) } func (handler PipelineConfigRestHandlerImpl) FetchArtifactForRollback(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -3317,7 +3313,38 @@ func validatejson(jsondoc pipeline.TemplateRequest) bool { schemaLoader := gojsonschema.NewReferenceLoader("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/schema.json") documentLoader := gojsonschema.NewGoLoader(jsondoc) + buff, merr := json.Marshal(jsondoc) + if merr != nil { + panic(merr) + } + var dat map[string]interface{} + + if err := json.Unmarshal(buff, &dat); err != nil { + panic(err) + } + for _, i := range []string{"valuesOverride", "defaultAppOverride"} { + + limit := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + request := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + cpu_limit, _ := CpuToNumber(limit["cpu"].(string)) + memory_limit, _ := MemoryToNumber(limit["memory"].(string)) + cpu_request, _ := CpuToNumber(request["cpu"].(string)) + memory_request, _ := MemoryToNumber(request["memory"].(string)) + + envoproxy_limit := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + envoproxy_request := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + envoproxy_cpu_limit, _ := CpuToNumber(envoproxy_limit["cpu"].(string)) + envoproxy_memory_limit, _ := MemoryToNumber(envoproxy_limit["memory"].(string)) + envoproxy_cpu_request, _ := CpuToNumber(envoproxy_request["cpu"].(string)) + envoproxy_memory_request, _ := MemoryToNumber(envoproxy_request["memory"].(string)) + if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { + return false + } + + } result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { panic(err.Error()) @@ -3337,3 +3364,99 @@ func validatejson(jsondoc pipeline.TemplateRequest) bool { return false } } + +func MemoryToNumber(memory string) (float64, error) { + if memoryParser == nil { + pattern := "(\\d*e?\\d*)(Ei?|Pi?|Ti?|Gi?|Mi?|Ki?|$)" + re, _ := regexp.Compile(pattern) + memoryParser = &resourceParser{ + name: "memory", + pattern: pattern, + regex: re, + conversions: map[string]float64{ + "E": float64(1000000000000000000), + "P": float64(1000000000000000), + "T": float64(1000000000000), + "G": float64(1000000000), + "M": float64(1000000), + "K": float64(1000), + "Ei": float64(1152921504606846976), + "Pi": float64(1125899906842624), + "Ti": float64(1099511627776), + "Gi": float64(1073741824), + "Mi": float64(1048576), + "Ki": float64(1024), + }, + } + } + return convertResource(memoryParser, memory) +} +func CpuToNumber(cpu string) (float64, error) { + if cpuParser == nil { + pattern := "(\\d*e?\\d*)(m?)" + re, _ := regexp.Compile(pattern) + cpuParser = &resourceParser{ + name: "cpu", + pattern: pattern, + regex: re, + conversions: map[string]float64{ + "m": .001, + }, + } + } + return convertResource(cpuParser, cpu) +} +func convertResource(rp *resourceParser, resource string) (float64, error) { + matches := rp.regex.FindAllStringSubmatch(resource, -1) + if len(matches[0]) < 2 { + fmt.Printf("expected pattern for %s should match %s, found %s\n", rp.name, rp.pattern, resource) + return float64(0), fmt.Errorf("expected pattern for %s should match %s, found %s", rp.name, rp.pattern, resource) + } + num, err := ParseFloat(matches[0][1]) + if err != nil { + fmt.Println(err) + return float64(0), err + } + if len(matches[0]) == 3 && matches[0][2] != "" { + if suffix, ok := rp.conversions[matches[0][2]]; ok { + return num * suffix, nil + } + } else { + return num, nil + } + fmt.Printf("expected pattern for %s should match %s, found %s\n", rp.name, rp.pattern, resource) + return float64(0), fmt.Errorf("expected pattern for %s should match %s, found %s", rp.name, rp.pattern, resource) +} + +func ParseFloat(str string) (float64, error) { + val, err := strconv.ParseFloat(str, 64) + if err == nil { + return val, nil + } + + //Some number may be seperated by comma, for example, 23,120,123, so remove the comma firstly + str = strings.Replace(str, ",", "", -1) + + //Some number is specifed in scientific notation + pos := strings.IndexAny(str, "eE") + if pos < 0 { + return strconv.ParseFloat(str, 64) + } + + var baseVal float64 + var expVal int64 + + baseStr := str[0:pos] + baseVal, err = strconv.ParseFloat(baseStr, 64) + if err != nil { + return 0, err + } + + expStr := str[(pos + 1):] + expVal, err = strconv.ParseInt(expStr, 10, 64) + if err != nil { + return 0, err + } + + return baseVal * math.Pow10(int(expVal)), nil +} From 93f28a363db8f52987d07001bd2fe755af755d25 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 6 Oct 2021 14:11:28 +0530 Subject: [PATCH 09/26] jsonSchemaValidation --- api/restHandler/PipelineConfigRestHandler.go | 7 ++++--- .../testdata/{schema.json => reference-chart_3-11-0.json} | 0 2 files changed, 4 insertions(+), 3 deletions(-) rename tests/testdata/{schema.json => reference-chart_3-11-0.json} (100%) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 3c53153945..c255dcbbcb 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -1260,7 +1260,8 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr if err := json.Unmarshal(buff, &dat); err != nil { panic(err) } - if validatejson(dat) { + chartVersion := dat.RefChartTemplate + if validatejson(dat, chartVersion) { token := r.Header.Get("token") app, err := handler.pipelineBuilder.GetApp(templateRequest.AppId) if err != nil { @@ -3309,9 +3310,9 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo writeJsonResp(w, err, suggestedName, http.StatusOK) } -func validatejson(jsondoc pipeline.TemplateRequest) bool { +func validatejson(jsondoc pipeline.TemplateRequest, chartVersion string) bool { - schemaLoader := gojsonschema.NewReferenceLoader("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/schema.json") + schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/%s.json", chartVersion)) documentLoader := gojsonschema.NewGoLoader(jsondoc) buff, merr := json.Marshal(jsondoc) if merr != nil { diff --git a/tests/testdata/schema.json b/tests/testdata/reference-chart_3-11-0.json similarity index 100% rename from tests/testdata/schema.json rename to tests/testdata/reference-chart_3-11-0.json From 4d5780dc352e1a362ca2a4a08aba351f5ad59358 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 6 Oct 2021 17:01:53 +0530 Subject: [PATCH 10/26] DeploymentTemplateValidate --- api/restHandler/PipelineConfigRestHandler.go | 27 ++++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index c255dcbbcb..613ad42d4b 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -25,13 +25,10 @@ import ( "fmt" "io" "math" - "regexp" - "net/http" - + "regexp" "strconv" "strings" - "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" bean2 "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/client/argocdServer/application" @@ -1252,16 +1249,18 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr handler.Logger.Infow("request payload, UpdateAppOverride", "payload", templateRequest) buff, merr := json.Marshal(templateRequest) if merr != nil { - handler.Logger.Errorw("marshal err, handleForwardResponseStreamError", "err", merr, "response", templateRequest) + handler.Logger.Errorw("marshal err, UpdateAppOverride", "err", merr, "payload", templateRequest) + writeJsonResp(w, err, nil, http.StatusBadRequest) } var dat pipeline.TemplateRequest if err := json.Unmarshal(buff, &dat); err != nil { - panic(err) + handler.Logger.Errorw("unmarshal err, UpdateAppOverride", "err", err, "payload", templateRequest) + writeJsonResp(w, err, nil, http.StatusBadRequest) } chartVersion := dat.RefChartTemplate - if validatejson(dat, chartVersion) { + if DeploymentTemplateValidate(dat, chartVersion) { token := r.Header.Get("token") app, err := handler.pipelineBuilder.GetApp(templateRequest.AppId) if err != nil { @@ -3310,19 +3309,16 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo writeJsonResp(w, err, suggestedName, http.StatusOK) } -func validatejson(jsondoc pipeline.TemplateRequest, chartVersion string) bool { +func DeploymentTemplateValidate(jsondoc pipeline.TemplateRequest, chartVersion string) bool { schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/%s.json", chartVersion)) documentLoader := gojsonschema.NewGoLoader(jsondoc) - buff, merr := json.Marshal(jsondoc) - if merr != nil { - panic(merr) - } + buff, _ := json.Marshal(jsondoc) var dat map[string]interface{} if err := json.Unmarshal(buff, &dat); err != nil { - panic(err) + fmt.Println(err) } for _, i := range []string{"valuesOverride", "defaultAppOverride"} { @@ -3346,10 +3342,7 @@ func validatejson(jsondoc pipeline.TemplateRequest, chartVersion string) bool { } } - result, err := gojsonschema.Validate(schemaLoader, documentLoader) - if err != nil { - panic(err.Error()) - } + result, _ := gojsonschema.Validate(schemaLoader, documentLoader) if result.Valid() { From 04d67a2419dfc0bb6aa32494481a5711a85363fc Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 6 Oct 2021 17:58:27 +0530 Subject: [PATCH 11/26] deploymentTemplate --- api/restHandler/PipelineConfigRestHandler.go | 24 +++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 613ad42d4b..cc7b2d1b77 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -1232,6 +1232,7 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr writeJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) return } + var templateRequest pipeline.TemplateRequest err = decoder.Decode(&templateRequest) templateRequest.UserId = userId @@ -1240,13 +1241,6 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr writeJsonResp(w, err, nil, http.StatusBadRequest) return } - err = handler.validator.Struct(templateRequest) - if err != nil { - handler.Logger.Errorw("validation err, UpdateAppOverride", "err", err, "payload", templateRequest) - writeJsonResp(w, err, nil, http.StatusBadRequest) - return - } - handler.Logger.Infow("request payload, UpdateAppOverride", "payload", templateRequest) buff, merr := json.Marshal(templateRequest) if merr != nil { handler.Logger.Errorw("marshal err, UpdateAppOverride", "err", merr, "payload", templateRequest) @@ -1259,8 +1253,16 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr handler.Logger.Errorw("unmarshal err, UpdateAppOverride", "err", err, "payload", templateRequest) writeJsonResp(w, err, nil, http.StatusBadRequest) } - chartVersion := dat.RefChartTemplate - if DeploymentTemplateValidate(dat, chartVersion) { + ChartVersion := dat.RefChartTemplate + if DeploymentTemplateValidate(dat, ChartVersion) { + err = handler.validator.Struct(templateRequest) + if err != nil { + handler.Logger.Errorw("validation err, UpdateAppOverride", "err", err, "payload", templateRequest) + writeJsonResp(w, err, nil, http.StatusBadRequest) + return + } + handler.Logger.Infow("request payload, UpdateAppOverride", "payload", templateRequest) + token := r.Header.Get("token") app, err := handler.pipelineBuilder.GetApp(templateRequest.AppId) if err != nil { @@ -3309,9 +3311,9 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo writeJsonResp(w, err, suggestedName, http.StatusOK) } -func DeploymentTemplateValidate(jsondoc pipeline.TemplateRequest, chartVersion string) bool { +func DeploymentTemplateValidate(jsondoc pipeline.TemplateRequest, schemafile string) bool { - schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/%s.json", chartVersion)) + schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/%s.json", schemafile)) documentLoader := gojsonschema.NewGoLoader(jsondoc) buff, _ := json.Marshal(jsondoc) From ffc45ce0f428f714c02fdb6ea6ea9dc03e7fb8d5 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 6 Oct 2021 20:03:07 +0530 Subject: [PATCH 12/26] updates json schema --- api/restHandler/PipelineConfigRestHandler.go | 161 ++++-------------- .../deployment-template.md | 67 +------- tests/testdata/reference-chart_3-11-0.json | 36 ++-- util/helper.go | 111 +++++++++++- 4 files changed, 171 insertions(+), 204 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index cc7b2d1b77..27f49fa4a9 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -24,11 +24,13 @@ import ( "errors" "fmt" "io" - "math" + "io/ioutil" "net/http" + "os" "regexp" "strconv" "strings" + "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" bean2 "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/client/argocdServer/application" @@ -3311,43 +3313,46 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo writeJsonResp(w, err, suggestedName, http.StatusOK) } -func DeploymentTemplateValidate(jsondoc pipeline.TemplateRequest, schemafile string) bool { - - schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file:///Users/aviralsrivastava/GolandProjects/devtron/tests/testdata/%s.json", schemafile)) - documentLoader := gojsonschema.NewGoLoader(jsondoc) - buff, _ := json.Marshal(jsondoc) - - var dat map[string]interface{} +func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafile string) bool { + jsonFile, _ := os.Open(fmt.Sprintf("tests/testdata/%s.json", schemafile)) + byteValue, _ := ioutil.ReadAll(jsonFile) + var schemajson map[string]interface{} + json.Unmarshal([]byte(byteValue), &schemajson) + schemaLoader := gojsonschema.NewGoLoader(schemajson) + documentLoader := gojsonschema.NewGoLoader(templatejson) + buff, _ := json.Marshal(templatejson) - if err := json.Unmarshal(buff, &dat); err != nil { - fmt.Println(err) - } - for _, i := range []string{"valuesOverride", "defaultAppOverride"} { + result, _ := gojsonschema.Validate(schemaLoader, documentLoader) - limit := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - request := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + if result.Valid() { + var dat map[string]interface{} - cpu_limit, _ := CpuToNumber(limit["cpu"].(string)) - memory_limit, _ := MemoryToNumber(limit["memory"].(string)) - cpu_request, _ := CpuToNumber(request["cpu"].(string)) - memory_request, _ := MemoryToNumber(request["memory"].(string)) + if err := json.Unmarshal(buff, &dat); err != nil { + fmt.Println(err) + } + //limits and requests are mandatory fields in schema + for _, i := range []string{"valuesOverride", "defaultAppOverride"} { - envoproxy_limit := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - envoproxy_request := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + limit := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + request := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - envoproxy_cpu_limit, _ := CpuToNumber(envoproxy_limit["cpu"].(string)) - envoproxy_memory_limit, _ := MemoryToNumber(envoproxy_limit["memory"].(string)) - envoproxy_cpu_request, _ := CpuToNumber(envoproxy_request["cpu"].(string)) - envoproxy_memory_request, _ := MemoryToNumber(envoproxy_request["memory"].(string)) - if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { - return false - } + cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) + memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) + cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) + memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) - } - result, _ := gojsonschema.Validate(schemaLoader, documentLoader) + envoproxy_limit := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + envoproxy_request := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - if result.Valid() { + envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) + envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) + envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) + envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) + if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { + return false + } + } fmt.Println("ok") return true } else { @@ -3360,99 +3365,3 @@ func DeploymentTemplateValidate(jsondoc pipeline.TemplateRequest, schemafile str return false } } - -func MemoryToNumber(memory string) (float64, error) { - if memoryParser == nil { - pattern := "(\\d*e?\\d*)(Ei?|Pi?|Ti?|Gi?|Mi?|Ki?|$)" - re, _ := regexp.Compile(pattern) - memoryParser = &resourceParser{ - name: "memory", - pattern: pattern, - regex: re, - conversions: map[string]float64{ - "E": float64(1000000000000000000), - "P": float64(1000000000000000), - "T": float64(1000000000000), - "G": float64(1000000000), - "M": float64(1000000), - "K": float64(1000), - "Ei": float64(1152921504606846976), - "Pi": float64(1125899906842624), - "Ti": float64(1099511627776), - "Gi": float64(1073741824), - "Mi": float64(1048576), - "Ki": float64(1024), - }, - } - } - return convertResource(memoryParser, memory) -} -func CpuToNumber(cpu string) (float64, error) { - if cpuParser == nil { - pattern := "(\\d*e?\\d*)(m?)" - re, _ := regexp.Compile(pattern) - cpuParser = &resourceParser{ - name: "cpu", - pattern: pattern, - regex: re, - conversions: map[string]float64{ - "m": .001, - }, - } - } - return convertResource(cpuParser, cpu) -} -func convertResource(rp *resourceParser, resource string) (float64, error) { - matches := rp.regex.FindAllStringSubmatch(resource, -1) - if len(matches[0]) < 2 { - fmt.Printf("expected pattern for %s should match %s, found %s\n", rp.name, rp.pattern, resource) - return float64(0), fmt.Errorf("expected pattern for %s should match %s, found %s", rp.name, rp.pattern, resource) - } - num, err := ParseFloat(matches[0][1]) - if err != nil { - fmt.Println(err) - return float64(0), err - } - if len(matches[0]) == 3 && matches[0][2] != "" { - if suffix, ok := rp.conversions[matches[0][2]]; ok { - return num * suffix, nil - } - } else { - return num, nil - } - fmt.Printf("expected pattern for %s should match %s, found %s\n", rp.name, rp.pattern, resource) - return float64(0), fmt.Errorf("expected pattern for %s should match %s, found %s", rp.name, rp.pattern, resource) -} - -func ParseFloat(str string) (float64, error) { - val, err := strconv.ParseFloat(str, 64) - if err == nil { - return val, nil - } - - //Some number may be seperated by comma, for example, 23,120,123, so remove the comma firstly - str = strings.Replace(str, ",", "", -1) - - //Some number is specifed in scientific notation - pos := strings.IndexAny(str, "eE") - if pos < 0 { - return strconv.ParseFloat(str, 64) - } - - var baseVal float64 - var expVal int64 - - baseStr := str[0:pos] - baseVal, err = strconv.ParseFloat(baseStr, 64) - if err != nil { - return 0, err - } - - expStr := str[(pos + 1):] - expVal, err = strconv.ParseInt(expStr, 10, 64) - if err != nil { - return 0, err - } - - return baseVal * math.Pow10(int(expVal)), nil -} diff --git a/docs/user-guide/creating-application/deployment-template.md b/docs/user-guide/creating-application/deployment-template.md index 2acdc3edb8..b86b0a34b3 100644 --- a/docs/user-guide/creating-application/deployment-template.md +++ b/docs/user-guide/creating-application/deployment-template.md @@ -33,7 +33,6 @@ This defines ports on which application services will be exposed to other servic ```yaml ContainerPort: -<<<<<<< HEAD envoyPort: 8799 idleTimeout: name: app @@ -41,31 +40,10 @@ ContainerPort: servicePort: 80 supportStreaming: true useHTTP2: true -======= - - name: app - port: 8080 - servicePort: 80 - envoyPort: 8799 - useHTTP2: true - supportStreaming: true - idleTimeout: 1800s - servicemonitor: - enabled: true - path: /metrics - scheme: 'http' - interval: 30s - scrapeTimeout: 20s - metricRelabelings: - - sourceLabels: [namespace] - regex: '(.*)' - replacement: myapp - targetLabel: target_namespace ->>>>>>> 87d977d198ef55c4292012cef8440475f200fd6a ``` -| Key | Description |optional| +| Key | Description | | :--- | :--- | -<<<<<<< HEAD | `envoyPort` | envoy port for the container. | | `idleTimeout` | the duration of time that a connection is idle before the connection is terminated. | | `name` | name of the port. | @@ -79,27 +57,6 @@ ContainerPort: EnvVariables: [] ``` To set environment variables for the containers that run in the Pod. -======= -| `name` | name of the container port| -| `port` | port no in container | -| `servicePort` | port In service | -| `envoyPort` | envoy proxy port | -| `useHTTP2`| envoy should use http2 as proxy | -| `supportStreaming` | streaming should be enabled | -| `idleTimeout` | ideal timeout for envoy proxy | -| `servicemonitor.` | this configuration is directly passed to service monitor | -| `servicemonitor.enabled`| serviceMonitor enabled | -| `servicemonitor.path`| path for serviceMonitor target| -| `servicemonitor.scheme`| scheme for serviceMonitor target | -| `servicemonitor.interval`| polling interval for serviceMonitor| -| `servicemonitor.scrapeTimeout`| timeout for serviceMonitor target | -| `servicemonitor.metricRelabelings`| metrics relabling for serviceMonitor | https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#relabelconfig -| `servicemonitor.metricRelabelings.sourceLabels`| | -| `servicemonitor.metricRelabelings.regex`| | -| `servicemonitor.metricRelabelings.replacement`| | -| `servicemonitor.metricRelabelings.targetLabel`| | - ->>>>>>> 87d977d198ef55c4292012cef8440475f200fd6a ### Liveness Probe @@ -109,12 +66,6 @@ If this check fails, kubernetes restarts the pod. This should return error code LivenessProbe: Path: "" port: 8080 - scheme: "" - httpHeader: - name: "" - value: "" - tcp: false - command: [] initialDelaySeconds: 20 periodSeconds: 10 successThreshold: 1 @@ -124,25 +75,15 @@ LivenessProbe: scheme: "" tcp: true ``` -<<<<<<< HEAD | Key | Description | -======= - - -| Key | Description | optional ->>>>>>> 87d977d198ef55c4292012cef8440475f200fd6a | :--- | :--- | -| `Path` | It define the path where the Liveness needs to be checked. | -| `port` | port for Liveness probe | -| `scheme` | scheme for Liveness probe | optional -| `httpHeader` | headers for Liveness probe request in key val pair (http/https) | optional +| `Path` | It define the path where the liveness needs to be checked. | | `failureThreshold` | It defines the maximum number of failures that are acceptable before a given container is not considered as live. | | `initialDelaySeconds` | It defines the time to wait before a given container is checked for liveliness. | | `periodSeconds` | It defines the time to check a given container for liveness. | | `successThreshold` | It defines the number of successes required before a given container is said to fulfil the liveness probe. | | `timeoutSeconds` | It defines the time for checking timeout. | -<<<<<<< HEAD | `httpHeader` | Custom headers to set in the request. HTTP allows repeated headers,You can override the default headers by defining .httpHeaders for the probe. | | `scheme` | Scheme to use for connecting to the host (HTTP or HTTPS). Defaults to HTTP. | `tcp` | The kubelet will attempt to open a socket to your container on the specified port. If it can establish a connection, the container is considered healthy. | @@ -169,10 +110,6 @@ The default value of "MaxSurge: " is 25%. MinReadySeconds: 60 ``` This specifies the minimum number of seconds for which a newly created Pod should be ready without any of its containers crashing, for it to be considered available. This defaults to 0 (the Pod will be considered available as soon as it is ready). -======= -| `tcp` | | -| `command`| ->>>>>>> 87d977d198ef55c4292012cef8440475f200fd6a ### Readiness Probe diff --git a/tests/testdata/reference-chart_3-11-0.json b/tests/testdata/reference-chart_3-11-0.json index 8e5536dff2..490c8b7899 100644 --- a/tests/testdata/reference-chart_3-11-0.json +++ b/tests/testdata/reference-chart_3-11-0.json @@ -248,7 +248,8 @@ "type" : "string", "format": "memory" } - } + }, + "required":["cpu","memory"] }, "requests": { "type" : "object", @@ -262,9 +263,11 @@ "format": "memory" } + }, + "required":["cpu","memory"] } - } - } + }, + "required":["limits","requests"] } } }, @@ -380,7 +383,8 @@ "type" : "string", "format": "memory" } - } + }, + "required":["cpu","memory"] }, "requests": { "type" : "object", @@ -394,9 +398,11 @@ "format": "memory" } + }, + "required":["cpu","memory"] } - } - } + }, + "required":["limits","requests"] }, "secret": { "type" : "object", @@ -693,7 +699,8 @@ "type" : "string", "format": "memory" } - } + }, + "required":["cpu","memory"] }, "requests": { "type" : "object", @@ -707,9 +714,11 @@ "format": "memory" } + }, + "required":["cpu","memory"] } - } - } + }, + "required":["limits","requests"] } } }, @@ -825,7 +834,8 @@ "type" : "string", "format": "memory" } - } + }, + "required":["cpu","memory"] }, "requests": { "type" : "object", @@ -839,9 +849,11 @@ "format": "memory" } + }, + "required":["cpu","memory"] } - } - } + }, + "required":["limits","requests"] }, "secret": { "type" : "object", diff --git a/util/helper.go b/util/helper.go index 4c9c59956e..5b1e049ae6 100644 --- a/util/helper.go +++ b/util/helper.go @@ -20,15 +20,28 @@ package util import ( "encoding/json" "fmt" - "go.uber.org/zap" "io/ioutil" + "math" "math/rand" "net/http" + "regexp" "strconv" "strings" "time" + + "go.uber.org/zap" ) +type resourceParser struct { + name string + pattern string + regex *regexp.Regexp + conversions map[string]float64 +} + +var memoryParser *resourceParser +var cpuParser *resourceParser + func ContainsString(list []string, element string) bool { if len(list) == 0 { return false @@ -118,3 +131,99 @@ func HttpRequest(url string) (map[string]interface{}, error) { } return nil, err } + +func MemoryToNumber(memory string) (float64, error) { + if memoryParser == nil { + pattern := "(\\d*e?\\d*)(Ei?|Pi?|Ti?|Gi?|Mi?|Ki?|$)" + re, _ := regexp.Compile(pattern) + memoryParser = &resourceParser{ + name: "memory", + pattern: pattern, + regex: re, + conversions: map[string]float64{ + "E": float64(1000000000000000000), + "P": float64(1000000000000000), + "T": float64(1000000000000), + "G": float64(1000000000), + "M": float64(1000000), + "K": float64(1000), + "Ei": float64(1152921504606846976), + "Pi": float64(1125899906842624), + "Ti": float64(1099511627776), + "Gi": float64(1073741824), + "Mi": float64(1048576), + "Ki": float64(1024), + }, + } + } + return convertResource(memoryParser, memory) +} +func CpuToNumber(cpu string) (float64, error) { + if cpuParser == nil { + pattern := "(\\d*e?\\d*)(m?)" + re, _ := regexp.Compile(pattern) + cpuParser = &resourceParser{ + name: "cpu", + pattern: pattern, + regex: re, + conversions: map[string]float64{ + "m": .001, + }, + } + } + return convertResource(cpuParser, cpu) +} +func convertResource(rp *resourceParser, resource string) (float64, error) { + matches := rp.regex.FindAllStringSubmatch(resource, -1) + if len(matches[0]) < 2 { + fmt.Printf("expected pattern for %s should match %s, found %s\n", rp.name, rp.pattern, resource) + return float64(0), fmt.Errorf("expected pattern for %s should match %s, found %s", rp.name, rp.pattern, resource) + } + num, err := ParseFloat(matches[0][1]) + if err != nil { + fmt.Println(err) + return float64(0), err + } + if len(matches[0]) == 3 && matches[0][2] != "" { + if suffix, ok := rp.conversions[matches[0][2]]; ok { + return num * suffix, nil + } + } else { + return num, nil + } + fmt.Printf("expected pattern for %s should match %s, found %s\n", rp.name, rp.pattern, resource) + return float64(0), fmt.Errorf("expected pattern for %s should match %s, found %s", rp.name, rp.pattern, resource) +} + +func ParseFloat(str string) (float64, error) { + val, err := strconv.ParseFloat(str, 64) + if err == nil { + return val, nil + } + + //Some number may be seperated by comma, for example, 23,120,123, so remove the comma firstly + str = strings.Replace(str, ",", "", -1) + + //Some number is specifed in scientific notation + pos := strings.IndexAny(str, "eE") + if pos < 0 { + return strconv.ParseFloat(str, 64) + } + + var baseVal float64 + var expVal int64 + + baseStr := str[0:pos] + baseVal, err = strconv.ParseFloat(baseStr, 64) + if err != nil { + return 0, err + } + + expStr := str[(pos + 1):] + expVal, err = strconv.ParseInt(expStr, 10, 64) + if err != nil { + return 0, err + } + + return baseVal * math.Pow10(int(expVal)), nil +} From ceb88d0b3b11719bb0a13610282f16e6c76be3ec Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 6 Oct 2021 20:42:09 +0530 Subject: [PATCH 13/26] schema json --- api/restHandler/PipelineConfigRestHandler.go | 17 +++++++++++------ .../reference-chart_3-11-0.json | 0 2 files changed, 11 insertions(+), 6 deletions(-) rename {tests/testdata => schema}/reference-chart_3-11-0.json (100%) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 27f49fa4a9..e0d86b4bdb 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -25,6 +25,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "net/http" "os" "regexp" @@ -3314,21 +3315,25 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo } func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafile string) bool { - jsonFile, _ := os.Open(fmt.Sprintf("tests/testdata/%s.json", schemafile)) + jsonFile, _ := os.Open(fmt.Sprintf("schema/%s.json", schemafile)) byteValue, _ := ioutil.ReadAll(jsonFile) var schemajson map[string]interface{} json.Unmarshal([]byte(byteValue), &schemajson) schemaLoader := gojsonschema.NewGoLoader(schemajson) documentLoader := gojsonschema.NewGoLoader(templatejson) - buff, _ := json.Marshal(templatejson) - - result, _ := gojsonschema.Validate(schemaLoader, documentLoader) - + buff, err := json.Marshal(templatejson) + if err != nil { + log.Fatal(err) + } + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + log.Fatal(err) + } if result.Valid() { var dat map[string]interface{} if err := json.Unmarshal(buff, &dat); err != nil { - fmt.Println(err) + log.Fatal(err) } //limits and requests are mandatory fields in schema for _, i := range []string{"valuesOverride", "defaultAppOverride"} { diff --git a/tests/testdata/reference-chart_3-11-0.json b/schema/reference-chart_3-11-0.json similarity index 100% rename from tests/testdata/reference-chart_3-11-0.json rename to schema/reference-chart_3-11-0.json From e000daa8bc1bea45a91339ba0c6a1c63d70f74f2 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Thu, 7 Oct 2021 12:14:26 +0530 Subject: [PATCH 14/26] deploymenttemplate --- api/restHandler/PipelineConfigRestHandler.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index e0d86b4bdb..649687ec09 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -1257,7 +1257,8 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr writeJsonResp(w, err, nil, http.StatusBadRequest) } ChartVersion := dat.RefChartTemplate - if DeploymentTemplateValidate(dat, ChartVersion) { + validate, error := DeploymentTemplateValidate(dat, ChartVersion) + if validate { err = handler.validator.Struct(templateRequest) if err != nil { handler.Logger.Errorw("validation err, UpdateAppOverride", "err", err, "payload", templateRequest) @@ -1288,6 +1289,8 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr writeJsonResp(w, err, createResp, http.StatusOK) } else { fmt.Println("Values are incorrect") + writeJsonResp(w, error, nil, http.StatusBadRequest) + return } } @@ -3314,7 +3317,7 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo writeJsonResp(w, err, suggestedName, http.StatusOK) } -func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafile string) bool { +func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafile string) (bool, error) { jsonFile, _ := os.Open(fmt.Sprintf("schema/%s.json", schemafile)) byteValue, _ := ioutil.ReadAll(jsonFile) var schemajson map[string]interface{} @@ -3324,16 +3327,19 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil buff, err := json.Marshal(templatejson) if err != nil { log.Fatal(err) + return false, err } result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { log.Fatal(err) + return false, err } if result.Valid() { var dat map[string]interface{} if err := json.Unmarshal(buff, &dat); err != nil { log.Fatal(err) + return false, err } //limits and requests are mandatory fields in schema for _, i := range []string{"valuesOverride", "defaultAppOverride"} { @@ -3354,12 +3360,12 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { - return false + return false, nil } } fmt.Println("ok") - return true + return true, nil } else { fmt.Printf("The document is not valid. see errors :\n") @@ -3367,6 +3373,6 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil fmt.Println("not ok", err) } - return false + return false, err } } From 0599a8bb1dd52c9d56168cdbfdc185885c66dc02 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Thu, 7 Oct 2021 13:41:23 +0530 Subject: [PATCH 15/26] changed schema --- api/restHandler/PipelineConfigRestHandler.go | 38 ++++++++++---------- schema/reference-chart_3-11-0.json | 29 +++++++-------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 649687ec09..8ae5e08ef5 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -3343,26 +3343,28 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil } //limits and requests are mandatory fields in schema for _, i := range []string{"valuesOverride", "defaultAppOverride"} { + autoscaleEnabled := dat[i].(map[string]interface{})["autoscaling"].(map[string]interface{}) + if autoscaleEnabled["enabled"].(bool) { + limit := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + request := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) + memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) + cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) + memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) + + envoproxy_limit := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + envoproxy_request := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) + envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) + envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) + envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) + if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { + return false, errors.New("requests is greater than limits") + } - limit := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - request := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - - cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) - memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) - cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) - memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) - - envoproxy_limit := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - envoproxy_request := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - - envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) - envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) - envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) - envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) - if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { - return false, nil } - } fmt.Println("ok") return true, nil diff --git a/schema/reference-chart_3-11-0.json b/schema/reference-chart_3-11-0.json index 490c8b7899..4d13ac749e 100644 --- a/schema/reference-chart_3-11-0.json +++ b/schema/reference-chart_3-11-0.json @@ -263,11 +263,11 @@ "format": "memory" } - }, - "required":["cpu","memory"] + } + } }, - "required":["limits","requests"] + "required":["limits"] } } }, @@ -398,11 +398,10 @@ "format": "memory" } - }, - "required":["cpu","memory"] + } } }, - "required":["limits","requests"] + "required":["limits"] }, "secret": { "type" : "object", @@ -463,7 +462,8 @@ "type" : "integer" } - } + }, + "required":["autoscaling"] }, "defaultAppOverride": { "type" : "object", @@ -653,6 +653,7 @@ "type" : "array" } } + }, "command": { "type" : "object", @@ -714,11 +715,11 @@ "format": "memory" } - }, - "required":["cpu","memory"] + } + } }, - "required":["limits","requests"] + "required":["limits"] } } }, @@ -849,11 +850,10 @@ "format": "memory" } - }, - "required":["cpu","memory"] + } } }, - "required":["limits","requests"] + "required":["limits"] }, "secret": { "type" : "object", @@ -914,7 +914,8 @@ "type" : "integer" } - } + }, + "required":["autoscaling"] }, "chartRefId": { "type" : "integer" From b6943e2c3cf640abaacd70562f2eac3b9bbb1f1c Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Fri, 8 Oct 2021 10:00:28 +0530 Subject: [PATCH 16/26] docs update --- 466 | 0 api/restHandler/PipelineConfigRestHandler.go | 7 +- .../deployment-template.md | 65 ++++++++++++++++--- 3 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 466 diff --git a/466 b/466 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 8ae5e08ef5..2fd776256c 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -1288,7 +1288,7 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr } writeJsonResp(w, err, createResp, http.StatusOK) } else { - fmt.Println("Values are incorrect") + fmt.Println("Values are incorrect", error) writeJsonResp(w, error, nil, http.StatusBadRequest) return } @@ -3371,10 +3371,11 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil } else { fmt.Printf("The document is not valid. see errors :\n") + var errorString string for _, err := range result.Errors() { - fmt.Println("not ok", err) + errorString = errorString + err.Field() + ", discription: " + err.Description() + "; " } - return false, err + return false, errors.New(errorString) } } diff --git a/docs/user-guide/creating-application/deployment-template.md b/docs/user-guide/creating-application/deployment-template.md index b86b0a34b3..75d34014f4 100644 --- a/docs/user-guide/creating-application/deployment-template.md +++ b/docs/user-guide/creating-application/deployment-template.md @@ -240,11 +240,11 @@ These define minimum and maximum RAM and CPU available to the application. ```yaml resources: limits: - cpu: '1' - memory: 200Mi + cpu: "1" + memory: "200Mi" requests: - cpu: '0.10' - memory: 100Mi + cpu: "0.10" + memory: "100Mi" ``` Resources are required to set CPU and memory usage. @@ -502,11 +502,11 @@ envoyproxy: configMapName: "" resources: limits: - cpu: 50m - memory: 50Mi + cpu: "50m" + memory: "50Mi" requests: - cpu: 50m - memory: 50Mi + cpu: "50m" + memory: "50Mi" ``` Envoy is attached as a sidecar to the application container to collect metrics like 4XX, 5XX, Throughput and latency. You can now configure the envoy settings such as idleTimeout, resources etc. @@ -575,3 +575,52 @@ If you want to see application metrics like different HTTP status codes metrics, Once all the Deployment template configurations are done, click on `Save` to save your deployment configuration. Now you are ready to create [Workflow](workflow/) to do CI/CD. +### Helm Chart Json Schema + +Helm Chart json schema is used to validate the deployment template values. + +``` +"resources": { +"type" : "object", +"properties": { + "limits": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + } + }, + "required":["cpu","memory"] + }, + "requests": { + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + + } + } + + } +}, +"required":["limits"] +} +``` + +### Format + +Format is used to accept the helm chart values in a particular form. + +1. ```"format" : "cpu"``` means format to enter the value in ```cpu``` should be ```"1"``` or ```"1000m"```. + +2. ```"format" : "memory"``` means format to enter the value in ```memory``` should be ```"1000Mi"``` or ```"1Gi"```. From 0228824147e74381517dbecd034a4bedd42cab08 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Mon, 11 Oct 2021 13:37:59 +0530 Subject: [PATCH 17/26] deployment template valaidation --- api/restHandler/PipelineConfigRestHandler.go | 72 +- schema/reference-chart_3-11-0.json | 1074 +++++------------ .../xeipuuv/gojsonschema/format_checkers.go | 46 - 3 files changed, 389 insertions(+), 803 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 2fd776256c..8b4a5a3e4d 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -3317,6 +3317,63 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo writeJsonResp(w, err, suggestedName, http.StatusOK) } +type ( + UnitChecker struct{} + MemoryChecker struct{} +) + +var ( + UChecker, _ = regexp.Compile("^([0-9.]+)m$") + NoUChecker, _ = regexp.Compile("^([0-9.]+)$") + MiChecker, _ = regexp.Compile("^[0-9]+Mi$") + GiChecker, _ = regexp.Compile("^[0-9]+Gi$") + TiChecker, _ = regexp.Compile("^[0-9]+Ti$") + PiChecker, _ = regexp.Compile("^[0-9]+Pi$") + KiChecker, _ = regexp.Compile("^[0-9]+Ki$") +) + +func (f UnitChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return true + } + + if UChecker.MatchString(asString) { + return true + } else if NoUChecker.MatchString(asString) { + return true + } else { + return false + } +} + +func (f MemoryChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return true + } + + // fmt.Println("hello", asString) + if MiChecker.MatchString(asString) { + return true + } else if GiChecker.MatchString(asString) { + return true + } else if TiChecker.MatchString(asString) { + return true + } else if PiChecker.MatchString(asString) { + return true + } else if KiChecker.MatchString(asString) { + return true + } else { + return false + } +} + +const memoryPattern = `"100Mi" or "1Gi" or "1Ti"` +const cpuPattern = `"50m" or "0.05"` +const cpu = "cpu" +const memory = "memory" + func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafile string) (bool, error) { jsonFile, _ := os.Open(fmt.Sprintf("schema/%s.json", schemafile)) byteValue, _ := ioutil.ReadAll(jsonFile) @@ -3369,13 +3426,18 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil fmt.Println("ok") return true, nil } else { - + var stringerror string fmt.Printf("The document is not valid. see errors :\n") - var errorString string for _, err := range result.Errors() { - errorString = errorString + err.Field() + ", discription: " + err.Description() + "; " - + fmt.Println(err.Details()["format"]) + if err.Details()["format"] == cpu { + stringerror = stringerror + "Error in " + err.Field() + ". Format should be like " + cpuPattern + "\n" + } else if err.Details()["format"] == memory { + stringerror = stringerror + "Error in " + err.Field() + ". Format should be like " + memoryPattern + "\n" + } else { + stringerror = stringerror + err.String() + "\n" + } } - return false, errors.New(errorString) + return false, errors.New(stringerror) } } diff --git a/schema/reference-chart_3-11-0.json b/schema/reference-chart_3-11-0.json index 4d13ac749e..ca86e325f5 100644 --- a/schema/reference-chart_3-11-0.json +++ b/schema/reference-chart_3-11-0.json @@ -19,365 +19,238 @@ "properties": { "ContainerPort": { "type" : "array", - "properties": { + "items": { + "type":"object", + "properties": { "envoyPort": { - "type" : "integer" + "type" : "integer", + "enum": [8799,8800] }, - "idleTimeout": { + "idleTimeout": { "type" : "string" }, - "name": { + "name": { "type" : "string" }, - "port": { + "port": { "type" : "integer" }, - "servicePort": { + "servicePort": { "type" : "integer" }, - "supportStreaming": { + "supportStreaming": { "type" : "boolean" }, - "useHTTP2": { + "useHTTP2": { "type" : "boolean" } - } - }, - "EnvVariables": { - "type" : "array" }, - "GracePeriod": { - "type" : "integer" - }, - "LivenessProbe": { - "type" : "object", - "properties": { - "Path": { - "type" : "string" - }, - "command": { - "type" : "array" - }, - "failureThreshold": { - "type" : "integer" - }, - "httpHeader": { - "type" : "object", - "properties": { - "name": { - "type" : "string" - }, - "value": { - "type" : "string" - } - } - }, - "initialDelaySeconds": { - "type" : "integer" - }, - "periodSeconds": { - "type" : "integer" - }, - "port": { - "type" : "integer" - }, - "scheme": { + "required":["port"] + } + + }, + "EnvVariables": { + "type" : "array" + }, + "GracePeriod": { + "type" : "integer" + }, + "LivenessProbe": { + "type" : "object", + "properties": { + "Path": { + "type" : "string" + }, + "command": { + "type" : "array" + }, + "failureThreshold": { + "type" : "integer" + }, + "httpHeader": { + "type" : "object", + "properties": { + "name": { "type" : "string" }, - "successThreshold": { - "type" : "integer" - }, - "tcp": { - "type" : "boolean" - }, - "timeoutSeconds": { - "type" : "integer" - } - } - }, - "MaxSurge": { - "type" : "integer" - }, - "MaxUnavailable": { - "type" : "integer" - }, - "MinReadySeconds": { - "type" : "integer" - }, - "ReadinessProbe": { - "type" : "object", - "properties": { - "Path": { + "value": { "type" : "string" - }, - "command": { - "type" : "array" - }, - "failureThreshold": { - "type" : "integer" - }, - "httpHeader": { - "type" : "object", - "properties": { - "name": { - "type" : "string" - }, - "value": { - "type" : "string" - } } - }, - "initialDelaySeconds": { - "type" : "integer" - }, - "periodSeconds": { - "type" : "integer" - }, - "port": { - "type" : "integer" - }, - "scheme": { - "type" : "string" - }, - "successThreshold": { - "type" : "integer" - }, - "tcp": { - "type" : "boolean" - }, - "timeoutSeconds": { - "type" : "integer" - } - } - }, - "Spec": { - "type" : "object", - "properties": { - "Affinity": { - "type" : "object", - "properties": { - "Key": { - "type":"null" - }, - "Values": { - "type" : "string" - }, - "key": { - "type" : "string" - } - } - } - } - }, - "args": { - "type" : "object", - "properties": { - "enabled": { - "type" : "boolean" - }, - "value": { - "type" : "array" } - } - }, - "autoscaling": { - "type" : "object", - "properties": { - "MaxReplicas": { - "type" : "integer" }, - "MinReplicas": { + "initialDelaySeconds": { "type" : "integer" }, - "TargetCPUUtilizationPercentage": { + "periodSeconds": { "type" : "integer" }, - "TargetMemoryUtilizationPercentage": { + "port": { "type" : "integer" }, - "enabled": { - "type" : "boolean" + "scheme": { + "type" : "string" }, - "extraMetrics": { - "type" : "array" - } - } - }, - "command": { - "type" : "object", - "properties": { - "enabled": { - "type" : "boolean" + "successThreshold": { + "type" : "integer" }, - "value": { - "type" : "array" - } - } - }, - "containers": { - "type" : "array" - }, - "dbMigrationConfig": { - "type" : "object", - "properties": { - "enabled": { + "tcp": { "type" : "boolean" + }, + "timeoutSeconds": { + "type" : "integer" } - } - }, - "envoyproxy": { - "type" : "object", - "properties": { - "configMapName": { + } + }, + "MaxSurge": { + "type" : "integer" + }, + "MaxUnavailable": { + "type" : "integer" + }, + "MinReadySeconds": { + "type" : "integer" + }, + "ReadinessProbe": { + "type" : "object", + "properties": { + "Path": { "type" : "string" }, - "image": { - "type" : "string" + "command": { + "type" : "array" }, - "resources": { - "type" : "object", - "properties": { - "limits": { - "type" : "object", - "properties": { - "cpu": { - "type" : "string", - "format": "cpu" - }, - "memory": { - "type" : "string", - "format": "memory" - } - }, - "required":["cpu","memory"] - }, - "requests": { - "type" : "object", - "properties": { - "cpu": { - "type" : "string", - "format": "cpu" - }, - "memory": { - "type" : "string", - "format": "memory" - - } - } - - } + "failureThreshold": { + "type" : "integer" }, - "required":["limits"] - } - } - }, - "image": { - "type" : "object", - "properties": { - "pullPolicy": { - "type" : "string" - } - } - }, - "imagePullSecrets": { - "type" : "array" - }, - "ingress": { - "type" : "object", - "properties": { - "annotations": { + "httpHeader": { "type" : "object", "properties": { - "kubernetes.io/ingress.class": { - "type" : "string" - }, - "nginx.ingress.kubernetes.io/force-ssl-redirect": { + "name": { "type" : "string" }, - "nginx.ingress.kubernetes.io/ssl-redirect": { + "value": { "type" : "string" } } }, - "enabled": { - "type" : "boolean" + "initialDelaySeconds": { + "type" : "integer" }, - "hosts": { - "type" : "array", - "properties": { - "host": { - "type" : "string" - }, - "paths": { - "type" : "array" - } - } + "periodSeconds": { + "type" : "integer" }, - "tls": { - "type" : "array" - } - } - }, - "ingressInternal": { - "type" : "object", - "properties": { - "annotations": { - "type" : "object" + "port": { + "type" : "integer" + }, + "scheme": { + "type" : "string" + }, + "successThreshold": { + "type" : "integer" }, - "enabled": { + "tcp": { "type" : "boolean" }, - "hosts": { - "type" : "array", + "timeoutSeconds": { + "type" : "integer" + } + } + }, + "Spec": { + "type" : "object", + "properties": { + "Affinity": { + "type" : "object", "properties": { - "host": { + "Key": { + "type":"null" + }, + "Values": { "type" : "string" }, - "paths": { - "type" : "array" + "key": { + "type" : "string" } } - }, - "tls": { - "type" : "array" } - } + } + }, + "args": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" }, - "initContainers": { + "value": { "type" : "array" + } + } + }, + "autoscaling": { + "type" : "object", + "properties": { + "MaxReplicas": { + "type" : "integer" }, - "pauseForSecondsBeforeSwitchActive": { + "MinReplicas": { "type" : "integer" }, - "podAnnotations": { - "type" : "object" + "TargetCPUUtilizationPercentage": { + "type" : "integer" }, - "podLabels": { - "type" : "object" + "TargetMemoryUtilizationPercentage": { + "type" : "integer" }, - "prometheus": { - "type" : "object", - "properties": { - "release": { - "type" : "string" - } + "enabled": { + "type" : "boolean" + }, + "extraMetrics": { + "type" : "array" } + } + }, + "command": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" }, - "rawYaml": { + "value": { "type" : "array" + } + } + }, + "containers": { + "type" : "array" + }, + "dbMigrationConfig": { + "type" : "object", + "properties": { + "enabled": { + "type" : "boolean" + } + } + }, + "envoyproxy": { + "type" : "object", + "properties": { + "configMapName": { + "type" : "string" }, - "replicaCount": { - "type" : "integer" + "image": { + "type" : "string" }, "resources": { "type" : "object", "properties": { "limits": { + "description": "Maximum Resource that container can utilize", "type" : "object", "properties": { "cpu": { "type" : "string", "format": "cpu" + }, "memory": { "type" : "string", @@ -396,527 +269,224 @@ "memory": { "type" : "string", "format": "memory" - + } } + } }, "required":["limits"] - }, - "secret": { - "type" : "object", - "properties": { - "data": { - "type" : "object" - }, - "enabled": { - "type" : "boolean" - } - } - }, - "server": { - "type" : "object", - "properties": { - "deployment": { - "type" : "object", - "properties": { - "image": { - "type" : "string" - }, - "image_tag": { - "type" : "string" - } - } - } } - }, - "service": { - "type" : "object", - "properties": { - "annotations": { - "type" : "object" + } + }, + "image": { + "type" : "object", + "properties": { + "pullPolicy": { + "type" : "string" + } + } + }, + "imagePullSecrets": { + "type" : "array" + }, + "ingress": { + "type" : "object", + "properties": { + "annotations": { + "type" : "object", + "patternproperties": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { + "type" : "string" }, - "type": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { "type" : "string" - } - } - }, - "servicemonitor": { - "type" : "object", - "properties": { - "additionalLabels": { - "type" : "object" - } - } - }, - "tolerations": { - "type" : "array" - }, - "volumeMounts": { - "type" : "array" - }, - "volumes": { - "type" : "array" - }, - "waitForSecondsBeforeScalingDown": { - "type" : "integer" - } - - }, - "required":["autoscaling"] - }, - "defaultAppOverride": { - "type" : "object", - "properties": { - "ContainerPort": { - "type" : "array", - "properties": { - "envoyPort": { - "type" : "integer" - }, - "idleTimeout": { - "type" : "string" - }, - "name": { - "type" : "string" - }, - "port": { - "type" : "integer" - }, - "servicePort": { - "type" : "integer" - }, - "supportStreaming": { - "type" : "boolean" - }, - "useHTTP2": { - "type" : "boolean" - } - } - }, - "EnvVariables": { - "type" : "array" - }, - "GracePeriod": { - "type" : "integer" - }, - "LivenessProbe": { - "type" : "object", - "properties": { - "Path": { - "type" : "string" - }, - "command": { - "type" : "array" - }, - "failureThreshold": { - "type" : "integer" - }, - "httpHeader": { - "type" : "object", - "properties": { - "name": { - "type" : "string" - }, - "value": { - "type" : "string" - } - } - }, - "initialDelaySeconds": { - "type" : "integer" - }, - "periodSeconds": { - "type" : "integer" - }, - "port": { - "type" : "integer" - }, - "scheme": { - "type" : "string" - }, - "successThreshold": { - "type" : "integer" - }, - "tcp": { - "type" : "boolean" - }, - "timeoutSeconds": { - "type" : "integer" - } - } - }, - "MaxSurge": { - "type" : "integer" - }, - "MaxUnavailable": { - "type" : "integer" - }, - "MinReadySeconds": { - "type" : "integer" - }, - "ReadinessProbe": { - "type" : "object", - "properties": { - "Path": { - "type" : "string" - }, - "command": { - "type" : "array" - }, - "failureThreshold": { - "type" : "integer" - }, - "httpHeader": { - "type" : "object", - "properties": { - "name": { - "type" : "string" - }, - "value": { - "type" : "string" - } - } - }, - "initialDelaySeconds": { - "type" : "integer" - }, - "periodSeconds": { - "type" : "integer" - }, - "port": { - "type" : "integer" - }, - "scheme": { - "type" : "string" - }, - "successThreshold": { - "type" : "integer" - }, - "tcp": { - "type" : "boolean" - }, - "timeoutSeconds": { - "type" : "integer" - } - } - }, - "Spec": { - "type" : "object", - "properties": { - "Affinity": { - "type" : "object", - "properties": { - "Key": { - "type":"null" - }, - "Values": { - "type" : "string" - }, - "key": { - "type" : "string" - } - } - } - } - }, - "args": { - "type" : "object", - "properties": { - "enabled": { - "type" : "boolean" }, - "value": { - "type" : "array" + "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { + "type" : "string" } } }, - "autoscaling": { - "type" : "object", - "properties": { - "MaxReplicas": { - "type" : "integer" - }, - "MinReplicas": { - "type" : "integer" - }, - "TargetCPUUtilizationPercentage": { - "type" : "integer" - }, - "TargetMemoryUtilizationPercentage": { - "type" : "integer" - }, - "enabled": { - "type" : "boolean" - }, - "extraMetrics": { - "type" : "array" - } - } - + "enabled": { + "type" : "boolean" }, - "command": { - "type" : "object", + "hosts": { + "type" : "array", "properties": { - "enabled": { - "type" : "boolean" + "host": { + "type" : "string" }, - "value": { + "paths": { "type" : "array" } } }, - "containers": { + "tls": { "type" : "array" - }, - "dbMigrationConfig": { - "type" : "object", - "properties": { - "enabled": { - "type" : "boolean" - } } - }, - "envoyproxy": { + } + }, + "ingressInternal": { + "type" : "object", + "properties": { + "annotations": { "type" : "object", - "properties": { - "configMapName": { + "patternproperties": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { "type" : "string" }, - "image": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { "type" : "string" }, - "resources": { - "type" : "object", - "properties": { - "limits": { - "type" : "object", - "properties": { - "cpu": { - "type" : "string", - "format": "cpu" - }, - "memory": { - "type" : "string", - "format": "memory" - } - }, - "required":["cpu","memory"] - }, - "requests": { - "type" : "object", - "properties": { - "cpu": { - "type" : "string", - "format": "cpu" - }, - "memory": { - "type" : "string", - "format": "memory" - - } - } - - } - }, - "required":["limits"] - } - } - }, - "image": { - "type" : "object", - "properties": { - "pullPolicy": { + "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { "type" : "string" } } }, - "imagePullSecrets": { - "type" : "array" - }, - "ingress": { - "type" : "object", - "properties": { - "annotations": { - "type" : "object", - "properties": { - "kubernetes.io/ingress.class": { - "type" : "string" - }, - "nginx.ingress.kubernetes.io/force-ssl-redirect": { - "type" : "string" - }, - "nginx.ingress.kubernetes.io/ssl-redirect": { - "type" : "string" - } - } - }, - "enabled": { - "type" : "boolean" - }, - "hosts": { - "type" : "array", - "properties": { - "host": { - "type" : "string" - }, - "paths": { - "type" : "array" - } - } - }, - "tls": { - "type" : "array" - } - } + "enabled": { + "type" : "boolean" }, - "ingressInternal": { - "type" : "object", + "hosts": { + "type" : "array", "properties": { - "annotations": { - "type" : "object" - }, - "enabled": { - "type" : "boolean" - }, - "hosts": { - "type" : "array", - "properties": { - "host": { - "type" : "string" - }, - "paths": { - "type" : "array" - } - } + "host": { + "type" : "string" }, - "tls": { + "paths": { "type" : "array" } } }, - "initContainers": { + "tls": { "type" : "array" - }, - "pauseForSecondsBeforeSwitchActive": { - "type" : "integer" - }, - "podAnnotations": { - "type" : "object" - }, - "podLabels": { - "type" : "object" - }, - "prometheus": { - "type" : "object", - "properties": { - "release": { - "type" : "string" - } - } - }, - "rawYaml": { - "type" : "array" - }, - "replicaCount": { - "type" : "integer" - }, - "resources": { - "type" : "object", - "properties": { - "limits": { - "type" : "object", - "properties": { - "cpu": { - "type" : "string", - "format": "cpu" - }, - "memory": { - "type" : "string", - "format": "memory" - } - }, - "required":["cpu","memory"] - }, - "requests": { - "type" : "object", - "properties": { - "cpu": { - "type" : "string", - "format": "cpu" - }, - "memory": { - "type" : "string", - "format": "memory" - - } - } - } - }, - "required":["limits"] - }, - "secret": { - "type" : "object", - "properties": { - "data": { - "type" : "object" - }, - "enabled": { - "type" : "boolean" - } } + } + }, + "initContainers": { + "type" : "array" + }, + "pauseForSecondsBeforeSwitchActive": { + "type" : "integer" + }, + "podAnnotations": { + "type" : "object" + }, + "podLabels": { + "type" : "object" + }, + "prometheus": { + "type" : "object", + "properties": { + "release": { + "type" : "string" + } + } + }, + "rawYaml": { + "type" : "array" + }, + "replicaCount": { + "type" : "integer" + }, + "resources": { + "type" : "object", + "properties": { + "limits": { + "description": "Maximum Resource that container can utilize", + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + } + }, + "required":["cpu","memory"] + }, + "requests": { + "description": "Maximum Resource that container can utilize", + "type" : "object", + "properties": { + "cpu": { + "type" : "string", + "format": "cpu" + }, + "memory": { + "type" : "string", + "format": "memory" + + } + } + } + }, + "required":["limits"] + }, + "secret": { + "type" : "object", + "properties": { + "data": { + "type" : "object" }, - "server": { - "type" : "object", - "properties": { - "deployment": { - "type" : "object", - "properties": { - "image": { - "type" : "string" - }, - "image_tag": { - "type" : "string" - } - } - } + "enabled": { + "type" : "boolean" } - }, - "service": { + } + }, + "server": { + "type" : "object", + "properties": { + "deployment": { "type" : "object", "properties": { - "annotations": { - "type" : "object" + "image": { + "type" : "string" }, - "type": { + "image_tag": { "type" : "string" } } - }, - "servicemonitor": { - "type" : "object", - "properties": { - "additionalLabels": { - "type" : "object" - } } + } + }, + "service": { + "type" : "object", + "properties": { + "annotations": { + "type" : "object" }, - "tolerations": { - "type" : "array" - }, - "volumeMounts": { - "type" : "array" - }, - "volumes": { - "type" : "array" - }, - "waitForSecondsBeforeScalingDown": { - "type" : "integer" + "type": { + "type" : "string" + } + } + }, + "servicemonitor": { + "type" : "object", + "properties": { + "additionalLabels": { + "type" : "object" } + } + }, + "tolerations": { + "type" : "array" + }, + "volumeMounts": { + "type" : "array" + }, + "volumes": { + "type" : "array" + }, + "waitForSecondsBeforeScalingDown": { + "type" : "integer" + } }, - "required":["autoscaling"] + "required":["replicaCount","ContainerPort","ingress","ingressInternal"] }, + "chartRefId": { "type" : "integer" }, diff --git a/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go index 7046ec8bf1..873ffc7d79 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go +++ b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go @@ -102,9 +102,6 @@ type ( // RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format RelativeJSONPointerFormatChecker struct{} - - UnitChecker struct{} - MemoryChecker struct{} ) var ( @@ -129,8 +126,6 @@ var ( "regex": RegexFormatChecker{}, "json-pointer": JSONPointerFormatChecker{}, "relative-json-pointer": RelativeJSONPointerFormatChecker{}, - "cpu": UnitChecker{}, - "memory": MemoryChecker{}, }, } @@ -146,14 +141,6 @@ var ( rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$") - UChecker, _ = regexp.Compile("^([0-9.]+)m$") - NoUChecker, _ = regexp.Compile("^([0-9.]+)$") - MiChecker, _ = regexp.Compile("^[0-9]+Mi$") - GiChecker, _ = regexp.Compile("^[0-9]+Gi$") - TiChecker, _ = regexp.Compile("^[0-9]+Ti$") - PiChecker, _ = regexp.Compile("^[0-9]+Pi$") - KiChecker, _ = regexp.Compile("^[0-9]+Ki$") - lock = new(sync.RWMutex) ) @@ -379,36 +366,3 @@ func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool { return rxRelJSONPointer.MatchString(asString) } -func (f UnitChecker) IsFormat(input interface{}) bool { - asString, ok := input.(string) - if !ok { - return true - } - if UChecker.MatchString(asString) { - return true - } else if NoUChecker.MatchString(asString) { - return true - } else { - return false - } -} -func (f MemoryChecker) IsFormat(input interface{}) bool { - asString, ok := input.(string) - if !ok { - return true - } - // fmt.Println("hello", asString) - if MiChecker.MatchString(asString) { - return true - } else if GiChecker.MatchString(asString) { - return true - } else if TiChecker.MatchString(asString) { - return true - } else if PiChecker.MatchString(asString) { - return true - } else if KiChecker.MatchString(asString) { - return true - } else { - return false - } -} From 61c7c81532b57b22e26943218ccbd46dbc94e24a Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Mon, 11 Oct 2021 15:43:04 +0530 Subject: [PATCH 18/26] resolve code conflicts --- api/restHandler/PipelineConfigRestHandler.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 8b4a5a3e4d..cb4f470bd5 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -212,16 +212,6 @@ func NewPipelineRestHandlerImpl(pipelineBuilder pipeline.PipelineBuilder, Logger } } -type resourceParser struct { - name string - pattern string - regex *regexp.Regexp - conversions map[string]float64 -} - -var memoryParser *resourceParser -var cpuParser *resourceParser - const devtron = "DEVTRON" func (handler PipelineConfigRestHandlerImpl) DeleteApp(w http.ResponseWriter, r *http.Request) { From 5ab5301cfaab0080837d3363cb35e20140dc577d Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Mon, 11 Oct 2021 18:12:53 +0530 Subject: [PATCH 19/26] no change --- api/restHandler/PipelineConfigRestHandler.go | 1 + pkg/pipeline/ChartService.go | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index cb4f470bd5..75728f4874 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -988,6 +988,7 @@ func (handler PipelineConfigRestHandlerImpl) GetDeploymentTemplate(w http.Respon writeJsonResp(w, err, nil, http.StatusInternalServerError) return } + if pg.ErrNoRows == err { template.ChartRefId = chartRefId template.Id = 0 diff --git a/pkg/pipeline/ChartService.go b/pkg/pipeline/ChartService.go index c67c95b05a..fb7aba6342 100644 --- a/pkg/pipeline/ChartService.go +++ b/pkg/pipeline/ChartService.go @@ -22,6 +22,14 @@ import ( "context" "encoding/json" "fmt" + "io/ioutil" + "net/http" + "path" + "path/filepath" + "strconv" + "strings" + "time" + repository2 "github.com/argoproj/argo-cd/pkg/apiclient/repository" "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" "github.com/devtron-labs/devtron/client/argocdServer/repository" @@ -36,15 +44,8 @@ import ( "github.com/go-pg/pg" "github.com/juju/errors" "go.uber.org/zap" - "io/ioutil" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/proto/hapi/chart" - "net/http" - "path" - "path/filepath" - "strconv" - "strings" - "time" ) type TemplateRequest struct { From f8f92c98015b36ce860daa0eb0d55b4ead904901 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Tue, 12 Oct 2021 14:46:46 +0530 Subject: [PATCH 20/26] deployment template --- api/restHandler/PipelineConfigRestHandler.go | 133 +----------------- schema/gojsonschema.go | 140 +++++++++++++++++++ 2 files changed, 142 insertions(+), 131 deletions(-) create mode 100644 schema/gojsonschema.go diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index 75728f4874..bdeba9999f 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -24,11 +24,7 @@ import ( "errors" "fmt" "io" - "io/ioutil" - "log" "net/http" - "os" - "regexp" "strconv" "strings" @@ -47,12 +43,12 @@ import ( security2 "github.com/devtron-labs/devtron/pkg/security" "github.com/devtron-labs/devtron/pkg/team" "github.com/devtron-labs/devtron/pkg/user" + schema2 "github.com/devtron-labs/devtron/schema" util2 "github.com/devtron-labs/devtron/util" "github.com/devtron-labs/devtron/util/rbac" "github.com/go-pg/pg" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" - "github.com/xeipuuv/gojsonschema" "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -1248,7 +1244,7 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr writeJsonResp(w, err, nil, http.StatusBadRequest) } ChartVersion := dat.RefChartTemplate - validate, error := DeploymentTemplateValidate(dat, ChartVersion) + validate, error := schema2.DeploymentTemplateValidate(dat, ChartVersion) if validate { err = handler.validator.Struct(templateRequest) if err != nil { @@ -3307,128 +3303,3 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo } writeJsonResp(w, err, suggestedName, http.StatusOK) } - -type ( - UnitChecker struct{} - MemoryChecker struct{} -) - -var ( - UChecker, _ = regexp.Compile("^([0-9.]+)m$") - NoUChecker, _ = regexp.Compile("^([0-9.]+)$") - MiChecker, _ = regexp.Compile("^[0-9]+Mi$") - GiChecker, _ = regexp.Compile("^[0-9]+Gi$") - TiChecker, _ = regexp.Compile("^[0-9]+Ti$") - PiChecker, _ = regexp.Compile("^[0-9]+Pi$") - KiChecker, _ = regexp.Compile("^[0-9]+Ki$") -) - -func (f UnitChecker) IsFormat(input interface{}) bool { - asString, ok := input.(string) - if !ok { - return true - } - - if UChecker.MatchString(asString) { - return true - } else if NoUChecker.MatchString(asString) { - return true - } else { - return false - } -} - -func (f MemoryChecker) IsFormat(input interface{}) bool { - asString, ok := input.(string) - if !ok { - return true - } - - // fmt.Println("hello", asString) - if MiChecker.MatchString(asString) { - return true - } else if GiChecker.MatchString(asString) { - return true - } else if TiChecker.MatchString(asString) { - return true - } else if PiChecker.MatchString(asString) { - return true - } else if KiChecker.MatchString(asString) { - return true - } else { - return false - } -} - -const memoryPattern = `"100Mi" or "1Gi" or "1Ti"` -const cpuPattern = `"50m" or "0.05"` -const cpu = "cpu" -const memory = "memory" - -func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafile string) (bool, error) { - jsonFile, _ := os.Open(fmt.Sprintf("schema/%s.json", schemafile)) - byteValue, _ := ioutil.ReadAll(jsonFile) - var schemajson map[string]interface{} - json.Unmarshal([]byte(byteValue), &schemajson) - schemaLoader := gojsonschema.NewGoLoader(schemajson) - documentLoader := gojsonschema.NewGoLoader(templatejson) - buff, err := json.Marshal(templatejson) - if err != nil { - log.Fatal(err) - return false, err - } - result, err := gojsonschema.Validate(schemaLoader, documentLoader) - if err != nil { - log.Fatal(err) - return false, err - } - if result.Valid() { - var dat map[string]interface{} - - if err := json.Unmarshal(buff, &dat); err != nil { - log.Fatal(err) - return false, err - } - //limits and requests are mandatory fields in schema - for _, i := range []string{"valuesOverride", "defaultAppOverride"} { - autoscaleEnabled := dat[i].(map[string]interface{})["autoscaling"].(map[string]interface{}) - if autoscaleEnabled["enabled"].(bool) { - limit := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - request := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - - cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) - memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) - cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) - memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) - - envoproxy_limit := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - envoproxy_request := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - - envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) - envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) - envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) - envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) - if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { - return false, errors.New("requests is greater than limits") - } - - } - } - fmt.Println("ok") - return true, nil - } else { - var stringerror string - fmt.Printf("The document is not valid. see errors :\n") - for _, err := range result.Errors() { - fmt.Println(err.Details()["format"]) - if err.Details()["format"] == cpu { - stringerror = stringerror + "Error in " + err.Field() + ". Format should be like " + cpuPattern + "\n" - } else if err.Details()["format"] == memory { - stringerror = stringerror + "Error in " + err.Field() + ". Format should be like " + memoryPattern + "\n" - } else { - stringerror = stringerror + err.String() + "\n" - } - } - return false, errors.New(stringerror) - } -} diff --git a/schema/gojsonschema.go b/schema/gojsonschema.go new file mode 100644 index 0000000000..70ed0271a0 --- /dev/null +++ b/schema/gojsonschema.go @@ -0,0 +1,140 @@ +package DeploymentTemplateValidate + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "regexp" + + "github.com/devtron-labs/devtron/pkg/pipeline" + util2 "github.com/devtron-labs/devtron/util" + "github.com/xeipuuv/gojsonschema" +) + +type ( + UnitChecker struct{} + MemoryChecker struct{} +) + +var ( + UChecker, _ = regexp.Compile("^([0-9.]+)m$") + NoUChecker, _ = regexp.Compile("^([0-9.]+)$") + MiChecker, _ = regexp.Compile("^[0-9]+Mi$") + GiChecker, _ = regexp.Compile("^[0-9]+Gi$") + TiChecker, _ = regexp.Compile("^[0-9]+Ti$") + PiChecker, _ = regexp.Compile("^[0-9]+Pi$") + KiChecker, _ = regexp.Compile("^[0-9]+Ki$") +) + +func (f UnitChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return true + } + + if UChecker.MatchString(asString) { + return true + } else if NoUChecker.MatchString(asString) { + return true + } else { + return false + } +} + +func (f MemoryChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return true + } + + // fmt.Println("hello", asString) + if MiChecker.MatchString(asString) { + return true + } else if GiChecker.MatchString(asString) { + return true + } else if TiChecker.MatchString(asString) { + return true + } else if PiChecker.MatchString(asString) { + return true + } else if KiChecker.MatchString(asString) { + return true + } else { + return false + } +} + +const memoryPattern = `"100Mi" or "1Gi" or "1Ti"` +const cpuPattern = `"50m" or "0.05"` +const cpu = "cpu" +const memory = "memory" + +func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafile string) (bool, error) { + jsonFile, _ := os.Open(fmt.Sprintf("schema/%s.json", schemafile)) + byteValue, _ := ioutil.ReadAll(jsonFile) + var schemajson map[string]interface{} + json.Unmarshal([]byte(byteValue), &schemajson) + schemaLoader := gojsonschema.NewGoLoader(schemajson) + documentLoader := gojsonschema.NewGoLoader(templatejson) + buff, err := json.Marshal(templatejson) + if err != nil { + log.Fatal(err) + return false, err + } + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + log.Fatal(err) + return false, err + } + if result.Valid() { + var dat map[string]interface{} + + if err := json.Unmarshal(buff, &dat); err != nil { + log.Fatal(err) + return false, err + } + //limits and requests are mandatory fields in schema + for _, i := range []string{"valuesOverride", "defaultAppOverride"} { + autoscaleEnabled := dat[i].(map[string]interface{})["autoscaling"].(map[string]interface{}) + if autoscaleEnabled["enabled"].(bool) { + limit := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + request := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) + memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) + cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) + memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) + + envoproxy_limit := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + envoproxy_request := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) + envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) + envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) + envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) + if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { + return false, errors.New("requests is greater than limits") + } + + } + } + fmt.Println("ok") + return true, nil + } else { + var stringerror string + fmt.Printf("The document is not valid. see errors :\n") + for _, err := range result.Errors() { + fmt.Println(err.Details()["format"]) + if err.Details()["format"] == cpu { + stringerror = stringerror + "Error in " + err.Field() + ". Format should be like " + cpuPattern + "\n" + } else if err.Details()["format"] == memory { + stringerror = stringerror + "Error in " + err.Field() + ". Format should be like " + memoryPattern + "\n" + } else { + stringerror = stringerror + err.String() + "\n" + } + } + return false, errors.New(stringerror) + } +} From 48de654c8551edfccf2ac7110f44de387eea6000 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Tue, 12 Oct 2021 14:59:54 +0530 Subject: [PATCH 21/26] validator --- api/restHandler/PipelineConfigRestHandler.go | 4 ++-- .../validator/DeploymentTemplateValidator.go | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename schema/gojsonschema.go => internal/validator/DeploymentTemplateValidator.go (100%) diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index bdeba9999f..a981943c57 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -35,6 +35,7 @@ import ( "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig" "github.com/devtron-labs/devtron/internal/sql/repository/security" "github.com/devtron-labs/devtron/internal/util" + validator2 "github.com/devtron-labs/devtron/internal/validator" "github.com/devtron-labs/devtron/pkg/appClone" "github.com/devtron-labs/devtron/pkg/appWorkflow" "github.com/devtron-labs/devtron/pkg/bean" @@ -43,7 +44,6 @@ import ( security2 "github.com/devtron-labs/devtron/pkg/security" "github.com/devtron-labs/devtron/pkg/team" "github.com/devtron-labs/devtron/pkg/user" - schema2 "github.com/devtron-labs/devtron/schema" util2 "github.com/devtron-labs/devtron/util" "github.com/devtron-labs/devtron/util/rbac" "github.com/go-pg/pg" @@ -1244,7 +1244,7 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr writeJsonResp(w, err, nil, http.StatusBadRequest) } ChartVersion := dat.RefChartTemplate - validate, error := schema2.DeploymentTemplateValidate(dat, ChartVersion) + validate, error := validator2.DeploymentTemplateValidate(dat, ChartVersion) if validate { err = handler.validator.Struct(templateRequest) if err != nil { diff --git a/schema/gojsonschema.go b/internal/validator/DeploymentTemplateValidator.go similarity index 100% rename from schema/gojsonschema.go rename to internal/validator/DeploymentTemplateValidator.go From 37f2ac2056a86bc528ac076bda05ca09a6d66df5 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 13 Oct 2021 13:07:53 +0530 Subject: [PATCH 22/26] deployment template --- api/restHandler/PipelineConfigRestHandler.go | 2 +- .../validator/DeploymentTemplateValidator.go | 58 +- schema/reference-chart_3-11-0.json | 902 +++++++++--------- schema/reference-chart_3-12-0.json | 484 ++++++++++ 4 files changed, 960 insertions(+), 486 deletions(-) create mode 100644 schema/reference-chart_3-12-0.json diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index a981943c57..ac38c5aa47 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -1244,7 +1244,7 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr writeJsonResp(w, err, nil, http.StatusBadRequest) } ChartVersion := dat.RefChartTemplate - validate, error := validator2.DeploymentTemplateValidate(dat, ChartVersion) + validate, error := validator2.DeploymentTemplateValidate(dat.ValuesOverride, ChartVersion) if validate { err = handler.validator.Struct(templateRequest) if err != nil { diff --git a/internal/validator/DeploymentTemplateValidator.go b/internal/validator/DeploymentTemplateValidator.go index 70ed0271a0..378fc2fb55 100644 --- a/internal/validator/DeploymentTemplateValidator.go +++ b/internal/validator/DeploymentTemplateValidator.go @@ -9,13 +9,12 @@ import ( "os" "regexp" - "github.com/devtron-labs/devtron/pkg/pipeline" util2 "github.com/devtron-labs/devtron/util" "github.com/xeipuuv/gojsonschema" ) type ( - UnitChecker struct{} + CpuChecker struct{} MemoryChecker struct{} ) @@ -29,7 +28,7 @@ var ( KiChecker, _ = regexp.Compile("^[0-9]+Ki$") ) -func (f UnitChecker) IsFormat(input interface{}) bool { +func (f CpuChecker) IsFormat(input interface{}) bool { asString, ok := input.(string) if !ok { return true @@ -50,7 +49,6 @@ func (f MemoryChecker) IsFormat(input interface{}) bool { return true } - // fmt.Println("hello", asString) if MiChecker.MatchString(asString) { return true } else if GiChecker.MatchString(asString) { @@ -71,7 +69,10 @@ const cpuPattern = `"50m" or "0.05"` const cpu = "cpu" const memory = "memory" -func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafile string) (bool, error) { +func DeploymentTemplateValidate(templatejson interface{}, schemafile string) (bool, error) { + gojsonschema.FormatCheckers.Add("cpu", CpuChecker{}) + gojsonschema.FormatCheckers.Add("memory", MemoryChecker{}) + jsonFile, _ := os.Open(fmt.Sprintf("schema/%s.json", schemafile)) byteValue, _ := ioutil.ReadAll(jsonFile) var schemajson map[string]interface{} @@ -83,6 +84,7 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil log.Fatal(err) return false, err } + fmt.Println(string(buff)) result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { log.Fatal(err) @@ -96,30 +98,30 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil return false, err } //limits and requests are mandatory fields in schema - for _, i := range []string{"valuesOverride", "defaultAppOverride"} { - autoscaleEnabled := dat[i].(map[string]interface{})["autoscaling"].(map[string]interface{}) - if autoscaleEnabled["enabled"].(bool) { - limit := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - request := dat[i].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - - cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) - memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) - cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) - memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) - - envoproxy_limit := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - envoproxy_request := dat[i].(map[string]interface{})["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - - envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) - envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) - envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) - envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) - if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { - return false, errors.New("requests is greater than limits") - } + autoscaleEnabled := dat["autoscaling"].(map[string]interface{}) + if autoscaleEnabled["enabled"].(bool) { + limit := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + request := dat["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) + memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) + cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) + memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) + + envoproxy_limit := dat["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + envoproxy_request := dat["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) + envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) + envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) + envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) + if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { + return false, errors.New("requests is greater than limits") } + } + fmt.Println("ok") return true, nil } else { @@ -128,9 +130,9 @@ func DeploymentTemplateValidate(templatejson pipeline.TemplateRequest, schemafil for _, err := range result.Errors() { fmt.Println(err.Details()["format"]) if err.Details()["format"] == cpu { - stringerror = stringerror + "Error in " + err.Field() + ". Format should be like " + cpuPattern + "\n" + stringerror = stringerror + err.Field() + ": Format should be like " + cpuPattern + "\n" } else if err.Details()["format"] == memory { - stringerror = stringerror + "Error in " + err.Field() + ". Format should be like " + memoryPattern + "\n" + stringerror = stringerror + err.Field() + ": Format should be like " + memoryPattern + "\n" } else { stringerror = stringerror + err.String() + "\n" } diff --git a/schema/reference-chart_3-11-0.json b/schema/reference-chart_3-11-0.json index ca86e325f5..050f962b02 100644 --- a/schema/reference-chart_3-11-0.json +++ b/schema/reference-chart_3-11-0.json @@ -2,499 +2,487 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { - "id": { - "type" : "integer" + "ContainerPort": { + "type": "array", + "items": { + "type": "object", + "properties": { + "envoyPort": { + "type": "integer", + "enum": [ + 8799, + 8800 + ] + }, + "idleTimeout": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "servicePort": { + "type": "integer" + }, + "supportStreaming": { + "type": "boolean" + }, + "useHTTP2": { + "type": "boolean" + } }, - "appId": { - "type" : "integer" + "required": [ + "port" + ] + } + }, + "EnvVariables": { + "type": "array" + }, + "GracePeriod": { + "type": "integer" + }, + "LivenessProbe": { + "type": "object", + "properties": { + "Path": { + "type": "string" }, - "refChartTemplate": { - "type" : "string" + "command": { + "type": "array" }, - "refChartTemplateVersion": { - "type" : "string" + "failureThreshold": { + "type": "integer" }, - "valuesOverride": { - "type" : "object", + "httpHeader": { + "type": "object", "properties": { - "ContainerPort": { - "type" : "array", - "items": { - "type":"object", - "properties": { - "envoyPort": { - "type" : "integer", - "enum": [8799,8800] - }, - "idleTimeout": { - "type" : "string" - }, - "name": { - "type" : "string" - }, - "port": { - "type" : "integer" - }, - "servicePort": { - "type" : "integer" - }, - "supportStreaming": { - "type" : "boolean" - }, - "useHTTP2": { - "type" : "boolean" - } - }, - "required":["port"] - } - - }, - "EnvVariables": { - "type" : "array" - }, - "GracePeriod": { - "type" : "integer" - }, - "LivenessProbe": { - "type" : "object", - "properties": { - "Path": { - "type" : "string" - }, - "command": { - "type" : "array" - }, - "failureThreshold": { - "type" : "integer" - }, - "httpHeader": { - "type" : "object", - "properties": { - "name": { - "type" : "string" - }, - "value": { - "type" : "string" - } - } - }, - "initialDelaySeconds": { - "type" : "integer" - }, - "periodSeconds": { - "type" : "integer" - }, - "port": { - "type" : "integer" - }, - "scheme": { - "type" : "string" - }, - "successThreshold": { - "type" : "integer" - }, - "tcp": { - "type" : "boolean" - }, - "timeoutSeconds": { - "type" : "integer" - } - } - }, - "MaxSurge": { - "type" : "integer" - }, - "MaxUnavailable": { - "type" : "integer" - }, - "MinReadySeconds": { - "type" : "integer" - }, - "ReadinessProbe": { - "type" : "object", - "properties": { - "Path": { - "type" : "string" - }, - "command": { - "type" : "array" - }, - "failureThreshold": { - "type" : "integer" - }, - "httpHeader": { - "type" : "object", - "properties": { - "name": { - "type" : "string" - }, - "value": { - "type" : "string" - } - } - }, - "initialDelaySeconds": { - "type" : "integer" - }, - "periodSeconds": { - "type" : "integer" - }, - "port": { - "type" : "integer" - }, - "scheme": { - "type" : "string" - }, - "successThreshold": { - "type" : "integer" - }, - "tcp": { - "type" : "boolean" - }, - "timeoutSeconds": { - "type" : "integer" - } - } - }, - "Spec": { - "type" : "object", - "properties": { - "Affinity": { - "type" : "object", - "properties": { - "Key": { - "type":"null" - }, - "Values": { - "type" : "string" - }, - "key": { - "type" : "string" - } - } - } - } - }, - "args": { - "type" : "object", - "properties": { - "enabled": { - "type" : "boolean" - }, - "value": { - "type" : "array" - } - } - }, - "autoscaling": { - "type" : "object", - "properties": { - "MaxReplicas": { - "type" : "integer" - }, - "MinReplicas": { - "type" : "integer" - }, - "TargetCPUUtilizationPercentage": { - "type" : "integer" - }, - "TargetMemoryUtilizationPercentage": { - "type" : "integer" - }, - "enabled": { - "type" : "boolean" - }, - "extraMetrics": { - "type" : "array" - } - } - }, - "command": { - "type" : "object", - "properties": { - "enabled": { - "type" : "boolean" - }, - "value": { - "type" : "array" - } - } - }, - "containers": { - "type" : "array" - }, - "dbMigrationConfig": { - "type" : "object", - "properties": { - "enabled": { - "type" : "boolean" - } - } - }, - "envoyproxy": { - "type" : "object", - "properties": { - "configMapName": { - "type" : "string" - }, - "image": { - "type" : "string" - }, - "resources": { - "type" : "object", - "properties": { - "limits": { - "description": "Maximum Resource that container can utilize", - "type" : "object", - "properties": { - "cpu": { - "type" : "string", - "format": "cpu" - - }, - "memory": { - "type" : "string", - "format": "memory" - } - }, - "required":["cpu","memory"] - }, - "requests": { - "type" : "object", - "properties": { - "cpu": { - "type" : "string", - "format": "cpu" - }, - "memory": { - "type" : "string", - "format": "memory" - - } - } - - } - }, - "required":["limits"] - } - } + "name": { + "type": "string" }, - "image": { - "type" : "object", - "properties": { - "pullPolicy": { - "type" : "string" - } + "value": { + "type": "string" } + } + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "successThreshold": { + "type": "integer" + }, + "tcp": { + "type": "boolean" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "MaxSurge": { + "type": "integer" + }, + "MaxUnavailable": { + "type": "integer" + }, + "MinReadySeconds": { + "type": "integer" + }, + "ReadinessProbe": { + "type": "object", + "properties": { + "Path": { + "type": "string" + }, + "command": { + "type": "array" + }, + "failureThreshold": { + "type": "integer" + }, + "httpHeader": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "imagePullSecrets": { - "type" : "array" - }, - "ingress": { - "type" : "object", - "properties": { - "annotations": { - "type" : "object", - "patternproperties": { - "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { - "type" : "string" - }, - "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { - "type" : "string" - }, - "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { - "type" : "string" - } - } - }, - "enabled": { - "type" : "boolean" - }, - "hosts": { - "type" : "array", - "properties": { - "host": { - "type" : "string" - }, - "paths": { - "type" : "array" - } - } - }, - "tls": { - "type" : "array" - } + "value": { + "type": "string" } + } + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "successThreshold": { + "type": "integer" + }, + "tcp": { + "type": "boolean" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "Spec": { + "type": "object", + "properties": { + "Affinity": { + "type": "object", + "properties": { + "Key": { + "type": "null" }, - "ingressInternal": { - "type" : "object", - "properties": { - "annotations": { - "type" : "object", - "patternproperties": { - "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { - "type" : "string" - }, - "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { - "type" : "string" - }, - "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { - "type" : "string" - } - } - }, - "enabled": { - "type" : "boolean" - }, - "hosts": { - "type" : "array", - "properties": { - "host": { - "type" : "string" - }, - "paths": { - "type" : "array" - } - } - }, - "tls": { - "type" : "array" - } - } - }, - "initContainers": { - "type" : "array" - }, - "pauseForSecondsBeforeSwitchActive": { - "type" : "integer" - }, - "podAnnotations": { - "type" : "object" - }, - "podLabels": { - "type" : "object" + "Values": { + "type": "string" }, - "prometheus": { - "type" : "object", - "properties": { - "release": { - "type" : "string" - } + "key": { + "type": "string" } - }, - "rawYaml": { - "type" : "array" - }, - "replicaCount": { - "type" : "integer" - }, - "resources": { - "type" : "object", - "properties": { - "limits": { + } + } + } + }, + "args": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "value": { + "type": "array" + } + } + }, + "autoscaling": { + "type": "object", + "properties": { + "MaxReplicas": { + "type": "integer" + }, + "MinReplicas": { + "type": "integer" + }, + "TargetCPUUtilizationPercentage": { + "type": "integer" + }, + "TargetMemoryUtilizationPercentage": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "extraMetrics": { + "type": "array" + } + } + }, + "command": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "value": { + "type": "array" + } + } + }, + "containers": { + "type": "array" + }, + "dbMigrationConfig": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "envoyproxy": { + "type": "object", + "properties": { + "configMapName": { + "type": "string" + }, + "image": { + "type": "string" + }, + "resources": { + "type": "object", + "properties": { + "limits": { "description": "Maximum Resource that container can utilize", - "type" : "object", + "type": "object", "properties": { "cpu": { - "type" : "string", - "format": "cpu" + "type": "string", + "format": "cpu" }, "memory": { - "type" : "string", - "format": "memory" + "type": "string", + "format": "memory" } }, - "required":["cpu","memory"] - }, - "requests": { - "description": "Maximum Resource that container can utilize", - "type" : "object", + "required": [ + "cpu", + "memory" + ] + }, + "requests": { + "type": "object", "properties": { "cpu": { - "type" : "string", - "format": "cpu" + "type": "string", + "format": "cpu" }, "memory": { - "type" : "string", - "format": "memory" - + "type": "string", + "format": "memory" } } - } - }, - "required":["limits"] - }, - "secret": { - "type" : "object", - "properties": { - "data": { - "type" : "object" - }, - "enabled": { - "type" : "boolean" - } } - }, - "server": { - "type" : "object", - "properties": { - "deployment": { - "type" : "object", - "properties": { - "image": { - "type" : "string" - }, - "image_tag": { - "type" : "string" - } - } - } + }, + "required": [ + "limits" + ] + } + } + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + } + } + }, + "imagePullSecrets": { + "type": "array" + }, + "ingress": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "patternproperties": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { + "type": "string" } + } + }, + "enabled": { + "type": "boolean" + }, + "hosts": { + "type": "array", + "properties": { + "host": { + "type": "string" }, - "service": { - "type" : "object", - "properties": { - "annotations": { - "type" : "object" - }, - "type": { - "type" : "string" - } + "paths": { + "type": "array" } - }, - "servicemonitor": { - "type" : "object", - "properties": { - "additionalLabels": { - "type" : "object" - } + } + }, + "tls": { + "type": "array" + } + } + }, + "ingressInternal": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "patternproperties": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { + "type": "string" } + } + }, + "enabled": { + "type": "boolean" + }, + "hosts": { + "type": "array", + "properties": { + "host": { + "type": "string" }, - "tolerations": { - "type" : "array" - }, - "volumeMounts": { - "type" : "array" - }, - "volumes": { - "type" : "array" + "paths": { + "type": "array" + } + } + }, + "tls": { + "type": "array" + } + } + }, + "initContainers": { + "type": "array" + }, + "pauseForSecondsBeforeSwitchActive": { + "type": "integer" + }, + "podAnnotations": { + "type": "object" + }, + "podLabels": { + "type": "object" + }, + "prometheus": { + "type": "object", + "properties": { + "release": { + "type": "string" + } + } + }, + "rawYaml": { + "type": "array" + }, + "replicaCount": { + "type": "integer" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "description": "Maximum Resource that container can utilize", + "type": "object", + "properties": { + "cpu": { + "type": "string", + "format": "cpu" }, - "waitForSecondsBeforeScalingDown": { - "type" : "integer" + "memory": { + "type": "string", + "format": "memory" } - }, - "required":["replicaCount","ContainerPort","ingress","ingressInternal"] + "required": [ + "cpu", + "memory" + ] }, - - "chartRefId": { - "type" : "integer" + "requests": { + "description": "Maximum Resource that container can utilize", + "type": "object", + "properties": { + "cpu": { + "type": "string", + "format": "cpu" + }, + "memory": { + "type": "string", + "format": "memory" + } + } + } + }, + "required": [ + "limits" + ] + }, + "secret": { + "type": "object", + "properties": { + "data": { + "type": "object" }, - "latest": { - "type" : "boolean" + "enabled": { + "type": "boolean" + } + } + }, + "server": { + "type": "object", + "properties": { + "deployment": { + "type": "object", + "properties": { + "image": { + "type": "string" + }, + "image_tag": { + "type": "string" + } + } + } + } + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": "object" }, - "isAppMetricsEnabled": { - "type" : "boolean" + "type": { + "type": "string" } } - } \ No newline at end of file + }, + "servicemonitor": { + "type": "object", + "properties": { + "additionalLabels": { + "type": "object" + } + } + }, + "tolerations": { + "type": "array" + }, + "volumeMounts": { + "type": "array" + }, + "volumes": { + "type": "array" + }, + "waitForSecondsBeforeScalingDown": { + "type": "integer" + } + }, + "required": [ + "replicaCount", + "ContainerPort", + "ingress", + "ingressInternal" + ] + +} \ No newline at end of file diff --git a/schema/reference-chart_3-12-0.json b/schema/reference-chart_3-12-0.json new file mode 100644 index 0000000000..90e5a085df --- /dev/null +++ b/schema/reference-chart_3-12-0.json @@ -0,0 +1,484 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ContainerPort": { + "type": "array", + "items": { + "type": "object", + "properties": { + "envoyPort": { + "type": "integer", + "enum": [ + 8799, + 8800 + ] + }, + "idleTimeout": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "servicePort": { + "type": "integer" + }, + "supportStreaming": { + "type": "boolean" + }, + "useHTTP2": { + "type": "boolean" + } + }, + "required": [ + "port" + ] + } + }, + "EnvVariables": { + "type": "array", + "items": {} + }, + "GracePeriod": { + "type": "integer" + }, + "LivenessProbe": { + "type": "object", + "properties": { + "Path": { + "type": "string" + }, + "command": { + "type": "array", + "items": {} + }, + "failureThreshold": { + "type": "integer" + }, + "httpHeader": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "successThreshold": { + "type": "integer" + }, + "tcp": { + "type": "boolean" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "MaxSurge": { + "type": "integer" + }, + "MaxUnavailable": { + "type": "integer" + }, + "MinReadySeconds": { + "type": "integer" + }, + "ReadinessProbe": { + "type": "object", + "properties": { + "Path": { + "type": "string" + }, + "command": { + "type": "array", + "items": {} + }, + "failureThreshold": { + "type": "integer" + }, + "httpHeader": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "successThreshold": { + "type": "integer" + }, + "tcp": { + "type": "boolean" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "Spec": { + "type": "object", + "properties": { + "Affinity": { + "type": "object", + "properties": { + "Key": { + "type": "null" + }, + "Values": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + } + }, + "args": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "value": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ] + } + } + }, + "autoscaling": { + "type": "object", + "properties": { + "MaxReplicas": { + "type": "integer" + }, + "MinReplicas": { + "type": "integer" + }, + "TargetCPUUtilizationPercentage": { + "type": "integer" + }, + "TargetMemoryUtilizationPercentage": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "extraMetrics": { + "type": "array", + "items": {} + } + } + }, + "command": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "value": { + "type": "array", + "items": {} + } + } + }, + "containers": { + "type": "array", + "items": {} + }, + "dbMigrationConfig": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "envoyproxy": { + "type": "object", + "properties": { + "configMapName": { + "type": "string" + }, + "image": { + "type": "string" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + }, + "required": [ + "cpu", + "memory" + ] + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + }, + "required": [ + "limits" + ] + } + } + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + } + } + }, + "ingress": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "patternproperties": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%+*-=:;?/><,.": { + "type": "string" + } + } + }, + "enabled": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "path": { + "type": "string" + }, + "tls": { + "type": "array", + "items": {} + } + } + }, + "ingressInternal": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "patternproperties": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { + "type": "string" + } + } + }, + "enabled": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "path": { + "type": "string" + }, + "tls": { + "type": "array", + "items": {} + } + } + }, + "initContainers": { + "type": "array", + "items": {} + }, + "pauseForSecondsBeforeSwitchActive": { + "type": "integer" + }, + "prometheus": { + "type": "object", + "properties": { + "release": { + "type": "string" + } + } + }, + "rawYaml": { + "type": "array", + "items": {} + }, + "replicaCount": { + "type": "integer" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + }, + "required": [ + "cpu", + "memory" + ] + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + }, + "required": [ + "limits" + ] + }, + "secret": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "enabled": { + "type": "boolean" + } + } + }, + "server": { + "type": "object", + "properties": { + "deployment": { + "type": "object", + "properties": { + "image": { + "type": "string" + }, + "image_tag": { + "type": "string" + } + } + } + } + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "type": { + "type": "string" + } + } + }, + "servicemonitor": { + "type": "object", + "properties": { + "additionalLabels": { + "type": "object" + } + } + }, + "tolerations": { + "type": "array", + "items": {} + }, + "volumeMounts": { + "type": "array", + "items": {} + }, + "volumes": { + "type": "array", + "items": {} + }, + "waitForSecondsBeforeScalingDown": { + "type": "integer" + } + }, + "required": [ + "replicaCount", + "ContainerPort", + "ingress", + "ingressInternal" + ] + } \ No newline at end of file From ad7c904c1ba9b1c164827c7f9be18105f8afff21 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Wed, 13 Oct 2021 15:45:37 +0530 Subject: [PATCH 23/26] deployment-template --- .../validator/DeploymentTemplateValidator.go | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/internal/validator/DeploymentTemplateValidator.go b/internal/validator/DeploymentTemplateValidator.go index 378fc2fb55..ca89b34ed7 100644 --- a/internal/validator/DeploymentTemplateValidator.go +++ b/internal/validator/DeploymentTemplateValidator.go @@ -99,29 +99,34 @@ func DeploymentTemplateValidate(templatejson interface{}, schemafile string) (bo } //limits and requests are mandatory fields in schema - autoscaleEnabled := dat["autoscaling"].(map[string]interface{}) - if autoscaleEnabled["enabled"].(bool) { - limit := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - request := dat["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - - cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) - memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) - cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) - memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) - - envoproxy_limit := dat["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) - envoproxy_request := dat["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) - - envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) - envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) - envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) - envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) - if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { - return false, errors.New("requests is greater than limits") - } + autoscaleEnabled := dat["autoscaling"] + if autoscaleEnabled == nil { + fmt.Println(autoscaleEnabled) + }else if autoscaleEnabled.(map[string]interface{})["enabled"] == nil { + fmt.Println("hello") + }else{ + if autoscaleEnabled.(map[string]interface{})["enabled"].(bool) { + limit := dat["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + request := dat["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + cpu_limit, _ := util2.CpuToNumber(limit["cpu"].(string)) + memory_limit, _ := util2.MemoryToNumber(limit["memory"].(string)) + cpu_request, _ := util2.CpuToNumber(request["cpu"].(string)) + memory_request, _ := util2.MemoryToNumber(request["memory"].(string)) + + envoproxy_limit := dat["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["limits"].(map[string]interface{}) + envoproxy_request := dat["envoyproxy"].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{}) + + envoproxy_cpu_limit, _ := util2.CpuToNumber(envoproxy_limit["cpu"].(string)) + envoproxy_memory_limit, _ := util2.MemoryToNumber(envoproxy_limit["memory"].(string)) + envoproxy_cpu_request, _ := util2.CpuToNumber(envoproxy_request["cpu"].(string)) + envoproxy_memory_request, _ := util2.MemoryToNumber(envoproxy_request["memory"].(string)) + if (envoproxy_cpu_limit < envoproxy_cpu_request) || (envoproxy_memory_limit < envoproxy_memory_request) || (cpu_limit < cpu_request) || (memory_limit < memory_request) { + return false, errors.New("requests is greater than limits") + } + } } - fmt.Println("ok") return true, nil } else { From 5c50a2e9b1ad39398952cd074655c60062fb3fdc Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Thu, 14 Oct 2021 13:16:39 +0530 Subject: [PATCH 24/26] deployment template bug fix --- api/restHandler/PipelineConfigRestHandler.go | 20 +- .../validator/DeploymentTemplateValidator.go | 4 - schema/reference-chart_3-10-0.json | 487 ++++++++++++++++++ 3 files changed, 500 insertions(+), 11 deletions(-) create mode 100644 schema/reference-chart_3-10-0.json diff --git a/api/restHandler/PipelineConfigRestHandler.go b/api/restHandler/PipelineConfigRestHandler.go index ac38c5aa47..d3c16c83cc 100644 --- a/api/restHandler/PipelineConfigRestHandler.go +++ b/api/restHandler/PipelineConfigRestHandler.go @@ -25,6 +25,7 @@ import ( "fmt" "io" "net/http" + "os" "strconv" "strings" @@ -1244,8 +1245,16 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr writeJsonResp(w, err, nil, http.StatusBadRequest) } ChartVersion := dat.RefChartTemplate - validate, error := validator2.DeploymentTemplateValidate(dat.ValuesOverride, ChartVersion) - if validate { + if _, err := os.Stat(fmt.Sprintf("schema/%s.json",ChartVersion)); err == nil { + validate, error := validator2.DeploymentTemplateValidate(dat.ValuesOverride, ChartVersion) + if !validate { + fmt.Println("Values are incorrect", error) + writeJsonResp(w, error, nil, http.StatusBadRequest) + return + } + + + } err = handler.validator.Struct(templateRequest) if err != nil { handler.Logger.Errorw("validation err, UpdateAppOverride", "err", err, "payload", templateRequest) @@ -1274,11 +1283,8 @@ func (handler PipelineConfigRestHandlerImpl) UpdateAppOverride(w http.ResponseWr return } writeJsonResp(w, err, createResp, http.StatusOK) - } else { - fmt.Println("Values are incorrect", error) - writeJsonResp(w, error, nil, http.StatusBadRequest) - return - } + + } func (handler PipelineConfigRestHandlerImpl) FetchArtifactForRollback(w http.ResponseWriter, r *http.Request) { diff --git a/internal/validator/DeploymentTemplateValidator.go b/internal/validator/DeploymentTemplateValidator.go index ca89b34ed7..63adfb5d92 100644 --- a/internal/validator/DeploymentTemplateValidator.go +++ b/internal/validator/DeploymentTemplateValidator.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io/ioutil" - "log" "os" "regexp" @@ -81,20 +80,17 @@ func DeploymentTemplateValidate(templatejson interface{}, schemafile string) (bo documentLoader := gojsonschema.NewGoLoader(templatejson) buff, err := json.Marshal(templatejson) if err != nil { - log.Fatal(err) return false, err } fmt.Println(string(buff)) result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { - log.Fatal(err) return false, err } if result.Valid() { var dat map[string]interface{} if err := json.Unmarshal(buff, &dat); err != nil { - log.Fatal(err) return false, err } //limits and requests are mandatory fields in schema diff --git a/schema/reference-chart_3-10-0.json b/schema/reference-chart_3-10-0.json new file mode 100644 index 0000000000..442259829d --- /dev/null +++ b/schema/reference-chart_3-10-0.json @@ -0,0 +1,487 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ContainerPort": { + "type": "array", + "items": { + "type": "object", + "properties": { + "envoyPort": { + "type": "integer", + "enum": [ + 8799, + 8800 + ] + }, + "idleTimeout": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "servicePort": { + "type": "integer" + }, + "supportStreaming": { + "type": "boolean" + }, + "useHTTP2": { + "type": "boolean" + } + }, + "required": [ + "port" + ] + } + }, + "EnvVariables": { + "type": "array", + "items": {} + }, + "GracePeriod": { + "type": "integer" + }, + "LivenessProbe": { + "type": "object", + "properties": { + "Path": { + "type": "string" + }, + "command": { + "type": "array", + "items": {} + }, + "failureThreshold": { + "type": "integer" + }, + "httpHeader": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "successThreshold": { + "type": "integer" + }, + "tcp": { + "type": "boolean" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "MaxSurge": { + "type": "integer" + }, + "MaxUnavailable": { + "type": "integer" + }, + "MinReadySeconds": { + "type": "integer" + }, + "ReadinessProbe": { + "type": "object", + "properties": { + "Path": { + "type": "string" + }, + "command": { + "type": "array", + "items": {} + }, + "failureThreshold": { + "type": "integer" + }, + "httpHeader": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "successThreshold": { + "type": "integer" + }, + "tcp": { + "type": "boolean" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "Spec": { + "type": "object", + "properties": { + "Affinity": { + "type": "object", + "properties": { + "Key": { + "type": "null" + }, + "Values": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + } + }, + "args": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "value": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ] + } + } + }, + "autoscaling": { + "type": "object", + "properties": { + "MaxReplicas": { + "type": "integer" + }, + "MinReplicas": { + "type": "integer" + }, + "TargetCPUUtilizationPercentage": { + "type": "integer" + }, + "TargetMemoryUtilizationPercentage": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "extraMetrics": { + "type": "array", + "items": {} + } + } + }, + "command": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "value": { + "type": "array", + "items": {} + } + } + }, + "containers": { + "type": "array", + "items": {} + }, + "dbMigrationConfig": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "envoyproxy": { + "type": "object", + "properties": { + "configMapName": { + "type": "string" + }, + "image": { + "type": "string" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + }, + "required": [ + "cpu", + "memory" + ] + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + }, + "required": [ + "limits" + ] + } + } + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + } + } + }, + "ingress": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "patternproperties": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%+*-=:;?/><,.": { + "type": "string" + } + } + }, + "enabled": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "path": { + "type": "string" + }, + "tls": { + "type": "array", + "items": {} + } + } + }, + "ingressInternal": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "patternproperties": { + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#%&*+-=:;?/.,><": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%*+-=:;?/><,.": { + "type": "string" + }, + "[a-zA-Z_]+[0-9.]+!@#&%+*-=:;?/><,.": { + "type": "string" + } + } + }, + "enabled": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "path": { + "type": "string" + }, + "tls": { + "type": "array", + "items": {} + } + } + }, + "initContainers": { + "type": "array", + "items": {} + }, + "pauseForSecondsBeforeSwitchActive": { + "type": "integer" + }, + "prometheus": { + "type": "object", + "properties": { + "release": { + "type": "string" + } + } + }, + "rawYaml": { + "type": "array", + "items": {} + }, + "replicaCount": { + "type": "integer" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + }, + "required": [ + "cpu", + "memory" + ] + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + }, + "required": [ + "limits" + ] + }, + "secret": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "enabled": { + "type": "boolean" + } + } + }, + "server": { + "type": "object", + "properties": { + "deployment": { + "type": "object", + "properties": { + "image": { + "type": "string" + }, + "image_tag": { + "type": "string" + } + } + } + } + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "type": { + "type": "string" + } + } + }, + "servicemonitor": { + "type": "object", + "properties": { + "additionalLabels": { + "type": "object" + } + } + }, + "tolerations": { + "type": "array", + "items": {} + }, + "volumeMounts": { + "type": "array", + "items": {} + }, + "volumes": { + "type": "array", + "items": {} + }, + "waitForSecondsBeforeScalingDown": { + "type": "integer" + } + }, + "required": [ + "replicaCount", + "ContainerPort", + "ingress", + "ingressInternal" + ] + } \ No newline at end of file From 5b00214259c42db1c442d94b6ccb1549b12a14ad Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Thu, 14 Oct 2021 13:20:42 +0530 Subject: [PATCH 25/26] deployment template validator log --- internal/validator/DeploymentTemplateValidator.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/validator/DeploymentTemplateValidator.go b/internal/validator/DeploymentTemplateValidator.go index 63adfb5d92..a4349a56f9 100644 --- a/internal/validator/DeploymentTemplateValidator.go +++ b/internal/validator/DeploymentTemplateValidator.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io/ioutil" + "log" "os" "regexp" @@ -85,12 +86,14 @@ func DeploymentTemplateValidate(templatejson interface{}, schemafile string) (bo fmt.Println(string(buff)) result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { + log.Println(err) return false, err } if result.Valid() { var dat map[string]interface{} if err := json.Unmarshal(buff, &dat); err != nil { + log.Println(err) return false, err } //limits and requests are mandatory fields in schema From e35113ea3832bbbb94a715b4ee78004167d6d462 Mon Sep 17 00:00:00 2001 From: Aviral Srivastava Date: Thu, 14 Oct 2021 14:11:17 +0530 Subject: [PATCH 26/26] bug fix --- internal/validator/DeploymentTemplateValidator.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/validator/DeploymentTemplateValidator.go b/internal/validator/DeploymentTemplateValidator.go index a4349a56f9..2db8aca5ec 100644 --- a/internal/validator/DeploymentTemplateValidator.go +++ b/internal/validator/DeploymentTemplateValidator.go @@ -4,8 +4,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/devtron-labs/devtron/internal/util" "io/ioutil" - "log" "os" "regexp" @@ -70,6 +70,7 @@ const cpu = "cpu" const memory = "memory" func DeploymentTemplateValidate(templatejson interface{}, schemafile string) (bool, error) { + sugaredLogger := util.NewSugardLogger() gojsonschema.FormatCheckers.Add("cpu", CpuChecker{}) gojsonschema.FormatCheckers.Add("memory", MemoryChecker{}) @@ -86,14 +87,14 @@ func DeploymentTemplateValidate(templatejson interface{}, schemafile string) (bo fmt.Println(string(buff)) result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { - log.Println(err) + sugaredLogger.Error(err) return false, err } if result.Valid() { var dat map[string]interface{} if err := json.Unmarshal(buff, &dat); err != nil { - log.Println(err) + sugaredLogger.Error(err) return false, err } //limits and requests are mandatory fields in schema