mirror of
https://github.com/prometheus/alertmanager
synced 2025-02-28 08:40:59 +00:00
The current Alertmanager API v1 is undocumented and written by hand. This patch introduces a new Alertmanager API - v2. The API is fully generated via an OpenAPI 2.0 [1] specification (see `api/v2/openapi.yaml`) with the exception of the http handlers itself. Pros: - Generated server code - Ability to generate clients in all major languages (Go, Java, JS, Python, Ruby, Haskell, *elm* [3] ...) - Strict contract (OpenAPI spec) between server and clients. - Instant feedback on frontend-breaking changes, due to strictly typed frontend language elm. - Generated documentation (See Alertmanager online Swagger UI [4]) Cons: - Dependency on open api ecosystem including go-swagger [2] In addition this patch includes the following changes. - README.md: Add API section - test: Duplicate acceptance test to API v1 & API v2 version The Alertmanager acceptance test framework has a decent test coverage on the Alertmanager API. Introducing the Alertmanager API v2 does not go hand in hand with deprecating API v1. They should live alongside each other for a couple of minor Alertmanager versions. Instead of porting the acceptance test framework to use the new API v2, this patch duplicates the acceptance tests, one using the API v1, the other API v2. Once API v1 is removed we can simply remove `test/with_api_v1` and bring `test/with_api_v2` to `test/`. [1] https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md [2] https://github.com/go-swagger/go-swagger/ [3] https://github.com/ahultgren/swagger-elm [4] http://petstore.swagger.io/?url=https://raw.githubusercontent.com/mxinden/alertmanager/apiv2/api/v2/openapi.yaml Signed-off-by: Max Leonard Inden <IndenML@gmail.com>
265 lines
8.6 KiB
Go
265 lines
8.6 KiB
Go
// Copyright 2015 go-swagger maintainers
|
|
//
|
|
// 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 validate
|
|
|
|
import (
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/go-openapi/errors"
|
|
"github.com/go-openapi/spec"
|
|
"github.com/go-openapi/strfmt"
|
|
)
|
|
|
|
type objectValidator struct {
|
|
Path string
|
|
In string
|
|
MaxProperties *int64
|
|
MinProperties *int64
|
|
Required []string
|
|
Properties map[string]spec.Schema
|
|
AdditionalProperties *spec.SchemaOrBool
|
|
PatternProperties map[string]spec.Schema
|
|
Root interface{}
|
|
KnownFormats strfmt.Registry
|
|
}
|
|
|
|
func (o *objectValidator) SetPath(path string) {
|
|
o.Path = path
|
|
}
|
|
|
|
func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool {
|
|
// TODO: this should also work for structs
|
|
// there is a problem in the type validator where it will be unhappy about null values
|
|
// so that requires more testing
|
|
r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct)
|
|
debugLog("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind)
|
|
return r
|
|
}
|
|
|
|
func (o *objectValidator) isPropertyName() bool {
|
|
p := strings.Split(o.Path, ".")
|
|
return p[len(p)-1] == "properties" && p[len(p)-2] != "properties"
|
|
}
|
|
|
|
func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]interface{}) {
|
|
if t, typeFound := val["type"]; typeFound {
|
|
if tpe, ok := t.(string); ok && tpe == "array" {
|
|
if _, itemsKeyFound := val["items"]; !itemsKeyFound {
|
|
res.AddErrors(errors.Required("items", o.Path))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]interface{}) {
|
|
if !o.isPropertyName() {
|
|
if _, itemsKeyFound := val["items"]; itemsKeyFound {
|
|
t, typeFound := val["type"]
|
|
if typeFound {
|
|
if tpe, ok := t.(string); !ok || tpe != "array" {
|
|
res.AddErrors(errors.InvalidType(o.Path, o.In, "array", nil))
|
|
}
|
|
} else {
|
|
// there is no type
|
|
res.AddErrors(errors.Required("type", o.Path))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *objectValidator) precheck(res *Result, val map[string]interface{}) {
|
|
o.checkArrayMustHaveItems(res, val)
|
|
o.checkItemsMustBeTypeArray(res, val)
|
|
}
|
|
|
|
func (o *objectValidator) Validate(data interface{}) *Result {
|
|
val := data.(map[string]interface{})
|
|
// TODO: guard against nil data
|
|
numKeys := int64(len(val))
|
|
|
|
if o.MinProperties != nil && numKeys < *o.MinProperties {
|
|
return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties))
|
|
}
|
|
if o.MaxProperties != nil && numKeys > *o.MaxProperties {
|
|
return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties))
|
|
}
|
|
|
|
res := new(Result)
|
|
|
|
o.precheck(res, val)
|
|
|
|
// check validity of field names
|
|
if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows {
|
|
// Case: additionalProperties: false
|
|
for k := range val {
|
|
_, regularProperty := o.Properties[k]
|
|
matched := false
|
|
|
|
for pk := range o.PatternProperties {
|
|
if matches, _ := regexp.MatchString(pk, k); matches {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !regularProperty && k != "$schema" && k != "id" && !matched {
|
|
// Special properties "$schema" and "id" are ignored
|
|
res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k))
|
|
|
|
// BUG(fredbi): This section should move to a part dedicated to spec validation as
|
|
// it will conflict with regular schemas where a property "headers" is defined.
|
|
|
|
//
|
|
// Croaks a more explicit message on top of the standard one
|
|
// on some recognized cases.
|
|
//
|
|
// NOTE: edge cases with invalid type assertion are simply ignored here.
|
|
// NOTE: prefix your messages here by "IMPORTANT!" so there are not filtered
|
|
// by higher level callers (the IMPORTANT! tag will be eventually
|
|
// removed).
|
|
switch k {
|
|
// $ref is forbidden in header
|
|
case "headers":
|
|
if val[k] != nil {
|
|
if headers, mapOk := val[k].(map[string]interface{}); mapOk {
|
|
for headerKey, headerBody := range headers {
|
|
if headerBody != nil {
|
|
if headerSchema, mapOfMapOk := headerBody.(map[string]interface{}); mapOfMapOk {
|
|
if _, found := headerSchema["$ref"]; found {
|
|
var msg string
|
|
if refString, stringOk := headerSchema["$ref"].(string); stringOk {
|
|
msg = strings.Join([]string{", one may not use $ref=\":", refString, "\""}, "")
|
|
}
|
|
res.AddErrors(refNotAllowedInHeaderMsg(o.Path, headerKey, msg))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
case "$ref":
|
|
if val[k] != nil {
|
|
// TODO: check context of that ref: warn about siblings, check against invalid context
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Cases: no additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> }
|
|
for key, value := range val {
|
|
_, regularProperty := o.Properties[key]
|
|
|
|
// Validates property against "patternProperties" if applicable
|
|
// BUG(fredbi): succeededOnce is always false
|
|
|
|
// NOTE: how about regular properties which do not match patternProperties?
|
|
matched, succeededOnce, _ := o.validatePatternProperty(key, value, res)
|
|
|
|
if !(regularProperty || matched || succeededOnce) {
|
|
|
|
// Cases: properties which are not regular properties and have not been matched by the PatternProperties validator
|
|
if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil {
|
|
// AdditionalProperties as Schema
|
|
r := NewSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats).Validate(value)
|
|
res.mergeForField(data.(map[string]interface{}), key, r)
|
|
} else if regularProperty && !(matched || succeededOnce) {
|
|
// TODO: this is dead code since regularProperty=false here
|
|
res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key))
|
|
}
|
|
}
|
|
}
|
|
// Valid cases: additionalProperties: true or undefined
|
|
}
|
|
|
|
createdFromDefaults := map[string]bool{}
|
|
|
|
// Property types:
|
|
// - regular Property
|
|
for pName := range o.Properties {
|
|
pSchema := o.Properties[pName] // one instance per iteration
|
|
rName := pName
|
|
if o.Path != "" {
|
|
rName = o.Path + "." + pName
|
|
}
|
|
|
|
// Recursively validates each property against its schema
|
|
if v, ok := val[pName]; ok {
|
|
r := NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats).Validate(v)
|
|
res.mergeForField(data.(map[string]interface{}), pName, r)
|
|
} else if pSchema.Default != nil {
|
|
// If a default value is defined, creates the property from defaults
|
|
// NOTE: JSON schema does not enforce default values to be valid against schema. Swagger does.
|
|
createdFromDefaults[pName] = true
|
|
res.addPropertySchemata(data.(map[string]interface{}), pName, &pSchema)
|
|
}
|
|
}
|
|
|
|
// Check required properties
|
|
if len(o.Required) > 0 {
|
|
for _, k := range o.Required {
|
|
if _, ok := val[k]; !ok && !createdFromDefaults[k] {
|
|
res.AddErrors(errors.Required(o.Path+"."+k, o.In))
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check patternProperties
|
|
// TODO: it looks like we have done that twice in many cases
|
|
for key, value := range val {
|
|
_, regularProperty := o.Properties[key]
|
|
matched, _ /*succeededOnce*/, patterns := o.validatePatternProperty(key, value, res)
|
|
if !regularProperty && (matched /*|| succeededOnce*/) {
|
|
for _, pName := range patterns {
|
|
if v, ok := o.PatternProperties[pName]; ok {
|
|
r := NewSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats).Validate(value)
|
|
res.mergeForField(data.(map[string]interface{}), key, r)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
// TODO: succeededOnce is not used anywhere
|
|
func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) {
|
|
matched := false
|
|
succeededOnce := false
|
|
var patterns []string
|
|
|
|
for k, schema := range o.PatternProperties {
|
|
if match, _ := regexp.MatchString(k, key); match {
|
|
patterns = append(patterns, k)
|
|
matched = true
|
|
validator := NewSchemaValidator(&schema, o.Root, o.Path+"."+key, o.KnownFormats)
|
|
|
|
res := validator.Validate(value)
|
|
result.Merge(res)
|
|
}
|
|
}
|
|
|
|
// BUG(fredbi): can't get to here. Should remove dead code (commented out).
|
|
|
|
//if succeededOnce {
|
|
// result.Inc()
|
|
//}
|
|
|
|
return matched, succeededOnce, patterns
|
|
}
|