From 7179c12f3917cbfb511a825173bdf63d4ee5d4bc Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 21:24:31 +0300 Subject: [PATCH 01/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- .gitignore | 2 +- go.mod | 24 + go.sum | 235 ++++++++- pkg/yaml/validation/error.go | 120 +++++ pkg/yaml/validation/extensions.go | 134 +++++ pkg/yaml/validation/schema.go | 36 ++ pkg/yaml/validation/schema_loader.go | 74 +++ pkg/yaml/validation/transformer/README.md | 1 + .../transformer/additional-properties.go | 76 +++ pkg/yaml/validation/transformer/transform.go | 28 + pkg/yaml/validation/validator.go | 298 +++++++++++ pkg/yaml/validation/validator_test.go | 493 ++++++++++++++++++ 12 files changed, 1519 insertions(+), 2 deletions(-) create mode 100644 pkg/yaml/validation/error.go create mode 100644 pkg/yaml/validation/extensions.go create mode 100644 pkg/yaml/validation/schema.go create mode 100644 pkg/yaml/validation/schema_loader.go create mode 100644 pkg/yaml/validation/transformer/README.md create mode 100644 pkg/yaml/validation/transformer/additional-properties.go create mode 100644 pkg/yaml/validation/transformer/transform.go create mode 100644 pkg/yaml/validation/validator.go create mode 100644 pkg/yaml/validation/validator_test.go diff --git a/.gitignore b/.gitignore index c620764..d71232c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,4 @@ go.work.sum # .vscode/ bin/ -validation/ +/validation diff --git a/go.mod b/go.mod index 97c44d4..f28ebb4 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.25.3 require ( github.com/deckhouse/deckhouse/pkg/log v0.1.1-0.20251230144142-2bad7c3d1edf + github.com/go-openapi/spec v0.19.8 github.com/gookit/color v1.5.2 github.com/name212/govalue v1.0.2 github.com/stretchr/testify v1.9.0 @@ -13,15 +14,38 @@ require ( require ( github.com/DataDog/gostackparse v0.7.0 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.1 // indirect + github.com/go-openapi/analysis v0.19.10 // indirect + github.com/go-openapi/errors v0.19.7 // indirect + github.com/go-openapi/jsonpointer v0.19.3 // indirect + github.com/go-openapi/jsonreference v0.19.3 // indirect + github.com/go-openapi/loads v0.19.5 // indirect + github.com/go-openapi/runtime v0.19.16 // indirect + github.com/go-openapi/strfmt v0.19.5 // indirect + github.com/go-openapi/swag v0.19.9 // indirect + github.com/go-openapi/validate v0.19.12 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/mailru/easyjson v0.7.1 // indirect + github.com/mitchellh/mapstructure v1.3.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + go.mongodb.org/mongo-driver v1.3.4 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) + +replace gopkg.in/alecthomas/kingpin.v2 => github.com/alecthomas/kingpin v1.3.8-0.20200323085623-b6657d9477a6 diff --git a/go.sum b/go.sum index ece97f5..4654004 100644 --- a/go.sum +++ b/go.sum @@ -1,46 +1,279 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 h1:HrMVYtly2IVqg9EBooHsakQ256ueojP7QuG32K71X/U= github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774/go.mod h1:5wi5YYOpfuAKwL5XLFYopbgIl/v7NZxaJpa/4X6yFKE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckhouse/deckhouse/pkg/log v0.1.1-0.20251230144142-2bad7c3d1edf h1:4HrDzRZcLpREJ+2cSGNmxHVQlxXRcH2r5TGmTcoTZiU= github.com/deckhouse/deckhouse/pkg/log v0.1.1-0.20251230144142-2bad7c3d1edf/go.mod h1:pbAxTSDcPmwyl3wwKDcEB3qdxHnRxqTV+J0K+sha8bw= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gmuz9fdE= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.7 h1:Lcq+o0mSwCLKACMxZhreVHigB9ebghJ/lrmeaqASbjo= +github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/runtime v0.19.16 h1:tQMAY5s5BfmmCC31+ufDCsGrr8iO1A8UIdYfDo5ADvs= +github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.8 h1:qAdZLh1r6QF/hI/gTq+TJTvsQUodZsM7KLqkAJdiJNg= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= +github.com/go-openapi/validate v0.19.12 h1:mPLM/bfbd00PGOCJlU0yJL7IulkZ+q9VjPv7U11RMQQ= +github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/name212/govalue v1.0.2 h1:NLpLfZatHyJYMohyUP8+qXtP8OriHQToxZv+DIXNrno= github.com/name212/govalue v1.0.2/go.mod h1:3mLA4mFb82esucQHCOIAnUjN7e7AZnRYEfxeaHLKjho= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/werf/logboek v0.5.5 h1:RmtTejHJOyw0fub4pIfKsb7OTzD90ZOUyuBAXqYqJpU= github.com/werf/logboek v0.5.5/go.mod h1:Gez5J4bxekyr6MxTmIJyId1F61rpO+0/V4vjCIEIZmk= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE= +go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/pkg/yaml/validation/error.go b/pkg/yaml/validation/error.go new file mode 100644 index 0000000..4a11896 --- /dev/null +++ b/pkg/yaml/validation/error.go @@ -0,0 +1,120 @@ +// Copyright 2026 Flant JSC +// +// 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 validation + +import ( + "fmt" + "strings" +) + +type ErrorKind int + +const ( + ErrKindChangesValidationFailed ErrorKind = iota + 1 + ErrKindValidationFailed + ErrKindInvalidYAML +) + +func (k ErrorKind) String() string { + switch k { + case ErrKindChangesValidationFailed: + return "ChangesValidationFailed" + case ErrKindValidationFailed: + return "ValidationFailed" + case ErrKindInvalidYAML: + return "InvalidYAML" + default: + return "unknown" + } +} + +type ValidationError struct { + Kind ErrorKind + Errors []Error +} + +func (v *ValidationError) Append(kind ErrorKind, e Error) { + if v.Kind < kind { + v.Kind = kind + } + v.Errors = append(v.Errors, e) +} + +func (v *ValidationError) Error() string { + if v == nil { + return "" + } + errs := make([]string, 0, len(v.Errors)) + for _, e := range v.Errors { + b := strings.Builder{} + if e.Index != nil { + b.WriteString(fmt.Sprintf("[%d]", *e.Index)) + } + + if e.Group != "" { + b.WriteString(fmt.Sprintf("%s/%s, Kind=%s", e.Group, e.Version, e.Kind)) + } + if e.Name != "" { + b.WriteString(fmt.Sprintf(" %q", e.Name)) + } + if b.Len() != 0 { + b.WriteString(": ") + } + b.WriteString(strings.Join(e.Messages, "; ")) + + errs = append(errs, b.String()) + } + + return fmt.Sprintf("%s: %s", v.Kind, strings.Join(errs, "\n")) +} + +func (v *ValidationError) ErrorOrNil() error { + if v == nil { + return nil + } + if len(v.Errors) == 0 { + return nil + } + + return v +} + +type Error struct { + Index *int + Group string + Version string + Kind string + Name string + Messages []string +} + +type namedIndex struct { + Kind string `json:"kind"` + Version string `json:"apiVersion"` + Metadata struct { + Name string `json:"name"` + } `json:"metadata"` +} + +func (i *namedIndex) IsValid() bool { + return i.Kind != "" && i.Version != "" +} + +func (i *namedIndex) String() string { + if i.Metadata.Name != "" { + return fmt.Sprintf("%s, %s", i.Kind, i.Version) + } + return fmt.Sprintf("%s, %s, metadata.name: %q", i.Kind, i.Version, i.Metadata.Name) +} diff --git a/pkg/yaml/validation/extensions.go b/pkg/yaml/validation/extensions.go new file mode 100644 index 0000000..a6db174 --- /dev/null +++ b/pkg/yaml/validation/extensions.go @@ -0,0 +1,134 @@ +// Copyright 2026 Flant JSC +// +// 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 validation + +import ( + "encoding/json" + "fmt" + + "github.com/go-openapi/spec" + "sigs.k8s.io/yaml" +) + +const ( + xRulesExtension = "x-rules" +) + +type ( + ExtensionsValidatorHandler func(oldValue json.RawMessage) error +) + +type ExtensionsValidator struct { + name string + validators map[string]ExtensionsValidatorHandler +} + +func NewExtensionsValidator(extensionName string, validators map[string]ExtensionsValidatorHandler) *ExtensionsValidator { + if len(validators) == 0 { + validators = make(map[string]ExtensionsValidatorHandler) + } + + return &ExtensionsValidator{ + name: extensionName, + validators: validators, + } +} + +func NewXRulesExtensionsValidator(validators map[string]ExtensionsValidatorHandler) *ExtensionsValidator { + return NewExtensionsValidator(xRulesExtension, validators) +} + +func (v *ExtensionsValidator) ExtensionName() string { + return v.name +} + +func (v *ExtensionsValidator) Validate(data json.RawMessage, schema spec.Schema) error { + if schema.Properties == nil { + return nil + } + + var properties map[string]json.RawMessage + + err := yaml.Unmarshal(data, &properties) + if err != nil { + return err + } + + err = v.validateData(data, schema) + if err != nil { + return err + } + + for field, fieldSchema := range schema.Properties { + err = v.validateData(properties[field], fieldSchema) + if err != nil { + return fmt.Errorf("%s: %w", field, err) + } + + err = v.Validate(properties[field], fieldSchema) + if err != nil { + return fmt.Errorf("%s: %w", field, err) + } + } + + return nil +} + +func (v *ExtensionsValidator) validateData(data json.RawMessage, schema spec.Schema) error { + if xRules, ok := schema.Extensions.GetStringSlice(v.name); ok { + for _, rule := range xRules { + xValidator, ok := v.validators[rule] + if !ok { + continue + } + + err := xValidator(data) + if err != nil { + return err + } + } + } + + if schema.SchemaProps.Items != nil && schema.SchemaProps.Items.Schema != nil { + if xRules, ok := schema.SchemaProps.Items.Schema.Extensions.GetStringSlice(v.name); ok { + for _, rule := range xRules { + xValidator, ok := v.validators[rule] + if !ok { + continue + } + + // avoid validation exception by validation empty data + if len(data) == 0 { + return nil + } + + var items []json.RawMessage + err := json.Unmarshal(data, &items) + if err != nil { + return err + } + + for _, item := range items { + err = xValidator(item) + if err != nil { + return err + } + } + } + } + } + + return nil +} diff --git a/pkg/yaml/validation/schema.go b/pkg/yaml/validation/schema.go new file mode 100644 index 0000000..e78146a --- /dev/null +++ b/pkg/yaml/validation/schema.go @@ -0,0 +1,36 @@ +// Copyright 2026 Flant JSC +// +// 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 validation + +import ( + "fmt" + + "github.com/go-openapi/spec" +) + +type SchemaIndex struct { + Kind string `json:"kind"` + Version string `json:"apiVersion"` +} + +func (i *SchemaIndex) IsValid() bool { + return i.Kind != "" && i.Version != "" +} + +func (i *SchemaIndex) String() string { + return fmt.Sprintf("%s, %s", i.Kind, i.Version) +} + +var s spec.Schema diff --git a/pkg/yaml/validation/schema_loader.go b/pkg/yaml/validation/schema_loader.go new file mode 100644 index 0000000..71986e8 --- /dev/null +++ b/pkg/yaml/validation/schema_loader.go @@ -0,0 +1,74 @@ +package validation + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/deckhouse/lib-dhctl/pkg/yaml/validation/transformer" + + "github.com/go-openapi/spec" + "sigs.k8s.io/yaml" +) + +type OpenAPISchema struct { + Kind string `json:"kind"` + Versions []OpenAPISchemaVersion `json:"apiVersions"` +} + +type OpenAPISchemaVersion struct { + Version string `json:"apiVersion"` + Schema interface{} `json:"openAPISpec"` +} + +type SchemaWithIndex struct { + Schema *spec.Schema + Index SchemaIndex +} + +func LoadSchemas(reader io.Reader) ([]*SchemaWithIndex, error) { + fileContent, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + + openAPISchema := new(OpenAPISchema) + if err := yaml.UnmarshalStrict(fileContent, openAPISchema); err != nil { + return nil, fmt.Errorf("Failed unmarshal openapi schema: %v", err) + } + + res := make([]*SchemaWithIndex, 0) + + for _, parsedSchema := range openAPISchema.Versions { + schema := new(spec.Schema) + + d, err := json.Marshal(parsedSchema.Schema) + if err != nil { + return nil, fmt.Errorf("expand the schema: %v", err) + } + + if err := json.Unmarshal(d, schema); err != nil { + return nil, fmt.Errorf("json marshal schema: %v", err) + } + + err = spec.ExpandSchema(schema, schema, nil) + if err != nil { + return nil, fmt.Errorf("expand the schema: %v", err) + } + + schema = transformer.TransformSchema( + schema, + &transformer.AdditionalPropertiesTransformer{}, + ) + + res = append(res, &SchemaWithIndex{ + Schema: schema, + Index: SchemaIndex{ + Kind: openAPISchema.Kind, + Version: parsedSchema.Version, + }, + }) + } + + return res, nil +} diff --git a/pkg/yaml/validation/transformer/README.md b/pkg/yaml/validation/transformer/README.md new file mode 100644 index 0000000..e28de22 --- /dev/null +++ b/pkg/yaml/validation/transformer/README.md @@ -0,0 +1 @@ +Copy code from https://github.com/flant/addon-operator/tree/main/pkg/values/validation/schema \ No newline at end of file diff --git a/pkg/yaml/validation/transformer/additional-properties.go b/pkg/yaml/validation/transformer/additional-properties.go new file mode 100644 index 0000000..3467f9a --- /dev/null +++ b/pkg/yaml/validation/transformer/additional-properties.go @@ -0,0 +1,76 @@ +// Copyright 2026 Flant JSC +// +// 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 transformer + +import "github.com/go-openapi/spec" + +type AdditionalPropertiesTransformer struct { + disallowFull bool +} + +func NewAdditionalPropertiesTransformer() *AdditionalPropertiesTransformer { + return &AdditionalPropertiesTransformer{} +} + +func NewAdditionalPropertiesTransformerDisallowFull() *AdditionalPropertiesTransformer { + return &AdditionalPropertiesTransformer{ + disallowFull: true, + } +} + +func (t *AdditionalPropertiesTransformer) shouldDisallowAdditionalProperties(s *spec.Schema) bool { + if s.AdditionalProperties == nil { + return true + } + + return t.disallowFull +} + +func disallowAdditionalProperties(s *spec.Schema) { + s.AdditionalProperties = &spec.SchemaOrBool{ + Allows: false, + } +} + +// Transform sets undefined AdditionalProperties to false recursively. +func (t *AdditionalPropertiesTransformer) Transform(s *spec.Schema) *spec.Schema { + if s == nil { + return nil + } + + if t.shouldDisallowAdditionalProperties(s) { + disallowAdditionalProperties(s) + } + + for k, prop := range s.Properties { + if t.shouldDisallowAdditionalProperties(&prop) { + ts := prop + disallowAdditionalProperties(&ts) + s.Properties[k] = *t.Transform(&ts) + } + } + + if s.Items != nil { + if s.Items.Schema != nil { + s.Items.Schema = t.Transform(s.Items.Schema) + } + for i, item := range s.Items.Schemas { + ts := item + s.Items.Schemas[i] = *t.Transform(&ts) + } + } + + return s +} diff --git a/pkg/yaml/validation/transformer/transform.go b/pkg/yaml/validation/transformer/transform.go new file mode 100644 index 0000000..16bf989 --- /dev/null +++ b/pkg/yaml/validation/transformer/transform.go @@ -0,0 +1,28 @@ +// Copyright 2026 Flant JSC +// +// 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 transformer + +import "github.com/go-openapi/spec" + +type SchemaTransformer interface { + Transform(s *spec.Schema) *spec.Schema +} + +func TransformSchema(s *spec.Schema, transformers ...SchemaTransformer) *spec.Schema { + for _, transformer := range transformers { + s = transformer.Transform(s) + } + return s +} diff --git a/pkg/yaml/validation/validator.go b/pkg/yaml/validation/validator.go new file mode 100644 index 0000000..2e2bc7d --- /dev/null +++ b/pkg/yaml/validation/validator.go @@ -0,0 +1,298 @@ +// Copyright 2026 Flant JSC +// +// 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 validation + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/deckhouse/lib-dhctl/pkg/log" + "github.com/deckhouse/lib-dhctl/pkg/yaml/validation/transformer" + + "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" + "github.com/go-openapi/validate/post" + "github.com/hashicorp/go-multierror" + "github.com/name212/govalue" + "sigs.k8s.io/yaml" +) + +var ( + ErrSchemaNotFound = fmt.Errorf("schema not found") +) + +type validateOptions struct { + omitDocInError bool + strictUnmarshal bool + noPrettyError bool +} + +type ValidateOption func(o *validateOptions) + +func ValidateWithOmitDocInError(v bool) ValidateOption { + return func(o *validateOptions) { + o.omitDocInError = v + } +} + +func ValidateWithStrictUnmarshal(v bool) ValidateOption { + return func(o *validateOptions) { + o.strictUnmarshal = v + } +} + +func ValidateWithNoPrettyError(v bool) ValidateOption { + return func(o *validateOptions) { + o.noPrettyError = v + } +} + +type PreValidator interface { + // Validate + // currentSchema can be nil + // if validator does not provide our own schema please return currentSchema + Validate(doc []byte, currentSchema *spec.Schema, logger log.Logger) (*spec.Schema, error) +} + +type Validator struct { + schemas map[SchemaIndex]*spec.Schema + preValidators map[SchemaIndex]PreValidator + loggerProvider log.LoggerProvider + versionFallbacks map[string]string + transformers map[SchemaIndex][]transformer.SchemaTransformer + defaultTransformers []transformer.SchemaTransformer + extensionsValidators []*ExtensionsValidator +} + +func NewValidator(schemas map[SchemaIndex]*spec.Schema) *Validator { + return NewValidatorWithLogger(schemas, log.SilentLoggerProvider()) +} + +func NewValidatorWithLogger(schemas map[SchemaIndex]*spec.Schema, loggerProvider log.LoggerProvider) *Validator { + if len(schemas) == 0 { + schemas = make(map[SchemaIndex]*spec.Schema) + } + return &Validator{ + schemas: schemas, + loggerProvider: loggerProvider, + preValidators: make(map[SchemaIndex]PreValidator), + versionFallbacks: map[string]string{ + "deckhouse.io/v1alpha1": "deckhouse.io/v1", + }, + defaultTransformers: make([]transformer.SchemaTransformer, 0), + transformers: make(map[SchemaIndex][]transformer.SchemaTransformer), + extensionsValidators: make([]*ExtensionsValidator, 0), + } +} + +func (v *Validator) AddSchema(index SchemaIndex, schema *spec.Schema) *Validator { + v.schemas[index] = schema + return v +} + +func (v *Validator) LoadSchemas(reader io.Reader) error { + schemas, err := LoadSchemas(reader) + if err != nil { + return err + } + + for _, sc := range schemas { + v.AddSchema(sc.Index, sc.Schema) + } + + return nil +} + +func (v *Validator) AddPreValidator(index SchemaIndex, validator PreValidator) *Validator { + v.preValidators[index] = validator + return v +} + +func (v *Validator) AddExtensionsValidators(validators ...*ExtensionsValidator) *Validator { + v.extensionsValidators = append(v.extensionsValidators, validators...) + return v +} + +func (v *Validator) AddVersionFallback(failVersion, fallback string) *Validator { + v.versionFallbacks[failVersion] = fallback + return v +} + +func (v *Validator) SetLogger(loggerProvider log.LoggerProvider) *Validator { + v.loggerProvider = loggerProvider + + return v +} + +func (v *Validator) AddTransformers(index SchemaIndex, t ...transformer.SchemaTransformer) *Validator { + res := make([]transformer.SchemaTransformer, 0, len(v.transformers)) + v.transformers[index] = append(res, t...) + return v +} + +func (v *Validator) SetDefaultTransformers(transformers ...transformer.SchemaTransformer) *Validator { + res := make([]transformer.SchemaTransformer, 0, len(v.transformers)) + v.defaultTransformers = append(res, transformers...) + + return v +} + +func (v *Validator) Get(index *SchemaIndex) *spec.Schema { + return v.schemas[*index] +} + +func (v *Validator) Validate(doc *[]byte, opts ...ValidateOption) (*SchemaIndex, error) { + var index SchemaIndex + + err := yaml.Unmarshal(*doc, &index) + if err != nil { + return nil, fmt.Errorf("Schema index unmarshal failed: %w", err) + } + + err = v.ValidateWithIndex(&index, doc, opts...) + return &index, err +} + +// ValidateWithIndex +// validate one document with schema +// if schema not fount then return ErrSchemaNotFound +func (v *Validator) ValidateWithIndex(index *SchemaIndex, doc *[]byte, opts ...ValidateOption) error { + options := &validateOptions{} + for _, opt := range opts { + opt(options) + } + + if !index.IsValid() { + return fmt.Errorf( + "document must contain \"kind\" and \"apiVersion\" fields:\n\tapiVersion: %s\n\tkind: %s\n\n%s", + index.Version, index.Kind, string(*doc), + ) + } + + logger := log.SafeProvideLogger(v.loggerProvider) + + docForValidate := *doc + + schema := v.getSchemaWithFallback(index, logger) + + preValidator, ok := v.preValidators[*index] + if ok && !govalue.IsNil(preValidator) { + var err error + schema, err = preValidator.Validate(docForValidate, schema, logger) + if err != nil { + return err + } + } + + if schema == nil { + logger.DebugF("No schema for index %s. Skip it", index.String()) + // we need return error because on top level we want filter documents without index and move into resources + return ErrSchemaNotFound + } + + schema = v.addTransformersForSchema(index, schema) + + isValid, err := v.openAPIValidate(&docForValidate, schema, options) + if !isValid { + if options.omitDocInError || options.noPrettyError { + return fmt.Errorf("%q document validation failed: %w", index.String(), err) + } + return fmt.Errorf("Document validation failed:\n---\n%s\n\n%w", string(*doc), err) + } + + *doc = docForValidate + + return nil +} + +func (v *Validator) addTransformersForSchema(index *SchemaIndex, schema *spec.Schema) *spec.Schema { + transformers := v.transformers[*index] + if len(transformers) == 0 { + transformers = v.defaultTransformers + } + + if len(transformers) == 0 { + return schema + } + + for _, t := range transformers { + if govalue.IsNil(t) { + continue + } + + schema = t.Transform(schema) + } + + return schema +} + +func (v *Validator) getSchemaWithFallback(index *SchemaIndex, logger log.Logger) *spec.Schema { + schema := v.Get(index) + if schema != nil { + return schema + } + + fallback, ok := v.versionFallbacks[index.Version] + if !ok || fallback == "" { + logger.DebugF("No fallback schema for version %s", index.Version) + return nil + } + + index.Version = fallback + + return v.Get(index) +} + +func (v *Validator) openAPIValidate(dataObj *[]byte, schema *spec.Schema, options *validateOptions) (isValid bool, multiErr error) { + validator := validate.NewSchemaValidator(schema, nil, "", strfmt.Default) + + var blank map[string]interface{} + + dataBytes := *dataObj + + if options.strictUnmarshal { + err := yaml.UnmarshalStrict(dataBytes, &blank) + if err != nil { + return false, fmt.Errorf("openAPIValidate json unmarshal strict: %v", err) + } + } else { + err := yaml.Unmarshal(dataBytes, &blank) + if err != nil { + return false, fmt.Errorf("openAPIValidate json unmarshal: %v", err) + } + } + + result := validator.Validate(blank) + if !result.IsValid() { + var allErrs *multierror.Error + allErrs = multierror.Append(allErrs, result.Errors...) + + return false, allErrs.ErrorOrNil() + } + + for _, extensionsValidator := range v.extensionsValidators { + if err := extensionsValidator.Validate(dataBytes, *schema); err != nil { + return false, err + } + } + + // Add default values from openAPISpec + post.ApplyDefaults(result) + *dataObj, _ = json.Marshal(result.Data()) + + return true, nil +} diff --git a/pkg/yaml/validation/validator_test.go b/pkg/yaml/validation/validator_test.go new file mode 100644 index 0000000..7c89ca4 --- /dev/null +++ b/pkg/yaml/validation/validator_test.go @@ -0,0 +1,493 @@ +// Copyright 2026 Flant JSC +// +// 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 validation + +import ( + "fmt" + "strings" + "testing" + + "github.com/deckhouse/lib-dhctl/pkg/log" + "github.com/deckhouse/lib-dhctl/pkg/yaml/validation/transformer" + "github.com/go-openapi/spec" + "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" +) + +const ( + testSchemaTestKind = ` +kind: TestKind +apiVersions: +- apiVersion: deckhouse.io/v1 + openAPISpec: + type: object + additionalProperties: false + anyOf: + - required: [apiVersion, kind, sshUser, sshAgentPrivateKeys] + - required: [apiVersion, kind, sshUser, sudoPassword] + x-rules: [sshPrivateKey] + properties: + kind: + type: string + apiVersion: + type: string + sshUser: + type: string + description: SSH username. + sudoPassword: + description: | + A sudo password for the user. + type: string + sshPort: + default: 22 + type: integer + description: SSH port. + sshAgentPrivateKeys: + type: array + minItems: 1 + items: + type: object + additionalProperties: false + required: [key] + x-rules: [sshPrivateKey] + properties: + key: + type: string + description: Private SSH key. + passphrase: + type: string + description: Password for SSH key. +` + testSchemaAnotherTestKind = ` +kind: AnotherTestKind +apiVersions: +- apiVersion: test + openAPISpec: + type: object + additionalProperties: false + properties: + kind: + type: string + apiVersion: + type: string + key: + type: string + value: + type: object + additionalProperties: true + default: {"valueEnum": "AWS", "valueBool": true} + properties: + valueEnum: + type: string + enum: + - "OpenStack" + - "AWS" + valueBool: + type: boolean +` +) + +var ( + indexTestKind = SchemaIndex{ + Kind: "TestKind", + Version: "deckhouse.io/v1", + } + + indexAnotherTestKind = SchemaIndex{ + Kind: "AnotherTestKind", + Version: "test", + } +) + +func TestValidator(t *testing.T) { + t.Run("One schema", func(t *testing.T) { + logger := testGetLogger() + + getValidatorTestKind := func(t *testing.T) *Validator { + validator := NewValidator(nil).SetLogger(logger) + err := validator.LoadSchemas(strings.NewReader(testSchemaTestKind)) + require.NoError(t, err, "failed to load schema") + return validator + } + + getValidatorAnotherTestKind := func(t *testing.T) *Validator { + validator := NewValidator(nil).SetLogger(logger) + err := validator.LoadSchemas(strings.NewReader(testSchemaAnotherTestKind)) + require.NoError(t, err, "failed to load schema") + return validator + } + + t.Run("happy case", func(t *testing.T) { + validatorTestKind := getValidatorTestKind(t) + + docPassword := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 2200 +` + bytesValidate := []byte(docPassword) + index, err := validatorTestKind.Validate(&bytesValidate, ValidateWithNoPrettyError(true)) + require.NoError(t, err) + asserTestKindIndex(t, *index) + + expectedTestKind := &testKind{ + SchemaIndex: indexTestKind, + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 2200, + } + + asserTestKind(t, bytesValidate, expectedTestKind) + + asserValidateTestKind(t, validatorTestKind, docPassword, false, expectedTestKind) + + docKey := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sshPort: 2200 +sshAgentPrivateKeys: +- key: "mykey" +` + asserValidateTestKind(t, validatorTestKind, docKey, false, &testKind{ + SSHUser: "ubuntu", + SSHPort: 2200, + SSHAgentPrivateKeys: []testPrivateKey{ + {Key: "mykey"}, + }, + }) + }) + + t.Run("set defaults", func(t *testing.T) { + doc := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +` + + asserValidateTestKind(t, getValidatorTestKind(t), doc, false, &testKind{ + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 22, + }) + + anotherTestKindDoc := ` +apiVersion: test +kind: AnotherTestKind +key: "mykey" +` + asserValidateAnotherTestKind( + t, + getValidatorAnotherTestKind(t), + anotherTestKindDoc, + false, + &testAnotherKind{ + Key: "mykey", + Value: testAnotherKindValue{ + ValueEnum: "AWS", + ValueBool: true, + }, + }) + }) + + t.Run("version fallback", func(t *testing.T) { + validatorTestKind := getValidatorTestKind(t). + AddVersionFallback("test", indexTestKind.Version) + // copy + index := indexTestKind + index.Version = "test" + + doc := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +` + + bytesValidateWithIndex := []byte(doc) + err := validatorTestKind.ValidateWithIndex(&index, &bytesValidateWithIndex) + require.NoError(t, err) + asserTestKindIndex(t, index) + }) + + t.Run("prevalidator", func(t *testing.T) { + t.Run("doc valid", func(t *testing.T) { + doc := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 22456 +` + validator := getValidatorTestKind(t) + validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, "")) + + asserValidateTestKind(t, validator, doc, false, &testKind{ + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 22456, + }) + }) + + t.Run("doc invalid", func(t *testing.T) { + doc := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 23 +` + validator := getValidatorTestKind(t) + validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, "")) + + asserValidateTestKind(t, validator, doc, true, nil) + }) + + t.Run("our schema valid", func(t *testing.T) { + doc := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 22456 +` + validator := NewValidator(nil) + validator.SetLogger(logger) + + validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, testSchemaTestKind)) + + asserValidateTestKind(t, validator, doc, false, &testKind{ + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 22456, + }) + }) + + t.Run("our schema invalid", func(t *testing.T) { + doc := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 22456 +` + validator := NewValidator(nil) + validator.SetLogger(logger) + + validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, testSchemaAnotherTestKind)) + + asserValidateTestKind(t, validator, doc, true, nil) + }) + }) + + t.Run("add transformers", func(t *testing.T) { + t.Run("for index", func(t *testing.T) { + validator := getValidatorAnotherTestKind(t) + validator.AddTransformers( + indexAnotherTestKind, + transformer.NewAdditionalPropertiesTransformerDisallowFull(), + ) + + assertValidationWithTransformers(t, validator, true) + }) + + t.Run("default", func(t *testing.T) { + validator := getValidatorAnotherTestKind(t) + validator.SetDefaultTransformers( + transformer.NewAdditionalPropertiesTransformerDisallowFull(), + ) + + assertValidationWithTransformers(t, validator, true) + }) + + t.Run("additional properties enabled is set in schema", func(t *testing.T) { + validator := getValidatorAnotherTestKind(t) + validator.SetDefaultTransformers( + transformer.NewAdditionalPropertiesTransformer(), + ) + + assertValidationWithTransformers(t, validator, false) + }) + }) + }) +} + +func testGetLogger() log.LoggerProvider { + return log.SimpleLoggerProvider( + log.NewInMemoryLoggerWithParent( + log.NewPrettyLogger(log.LoggerOptions{IsDebug: true}), + ), + ) +} + +type testPrivateKey struct { + Key string `json:"key"` + Passphrase string `json:"passphrase"` +} + +type testKind struct { + SchemaIndex + SSHUser string `yaml:"sshUser"` + SudoPassword string `yaml:"sudoPassword"` + SSHPort int `yaml:"sshPort"` + SSHAgentPrivateKeys []testPrivateKey `yaml:"sshAgentPrivateKeys"` +} + +type testAnotherKindValue struct { + ValueEnum string `yaml:"valueEnum"` + ValueBool bool `yaml:"valueBool"` +} + +type testAnotherKind struct { + SchemaIndex + + Key string `json:"key"` + Value testAnotherKindValue `json:"value"` +} + +type testKindPreValidator struct { + mySchema *spec.Schema +} + +func newTestKindPreValidator(t *testing.T, schema string) *testKindPreValidator { + r := &testKindPreValidator{} + if schema != "" { + ss, err := LoadSchemas(strings.NewReader(schema)) + require.NoError(t, err) + require.Len(t, ss, 1) + r.mySchema = ss[0].Schema + } + + return r +} + +func (p *testKindPreValidator) Validate(doc []byte, currentSchema *spec.Schema, _ log.Logger) (*spec.Schema, error) { + v := testKind{} + err := yaml.Unmarshal(doc, &v) + if err != nil { + return nil, err + } + + if v.SSHPort >= 22000 && v.SSHPort < 30000 { + if currentSchema == nil { + return p.mySchema, nil + } + return currentSchema, nil + } + + return nil, fmt.Errorf("invalid SSH port: %d", v.SSHPort) +} + +func asserTestKind(t *testing.T, data []byte, expected *testKind) { + result := testKind{} + + expected.SchemaIndex = indexTestKind + + err := yaml.Unmarshal(data, &result) + require.NoError(t, err, "failed to unmarshal test kind") + + require.Equal(t, expected.SchemaIndex, result.SchemaIndex, "invalid schema index") + require.Equal(t, expected.SSHUser, result.SSHUser, "invalid SSH user") + require.Equal(t, expected.SSHPort, result.SSHPort, "invalid SSH port") + require.Equal(t, expected.SSHAgentPrivateKeys, result.SSHAgentPrivateKeys, "invalid SSH agent keys") + require.Equal(t, expected.SudoPassword, result.SudoPassword, "invalid sudo password") +} + +func doValidate(t *testing.T, validator *Validator, expectedIndex SchemaIndex, doc string, shouldError bool, opts ...ValidateOption) []byte { + index := expectedIndex + + if len(opts) == 0 { + opts = []ValidateOption{ValidateWithNoPrettyError(true)} + } + + bytesValidateWithIndex := []byte(doc) + err := validator.ValidateWithIndex(&index, &bytesValidateWithIndex, opts...) + if shouldError { + require.Error(t, err, "should validation error for test another kind") + return nil + } + require.NoError(t, err, "should not validation error for test another kind") + + require.True(t, index.IsValid(), "invalid index") + require.Equal(t, expectedIndex, index, "invalid index value") + + return bytesValidateWithIndex +} + +func asserValidateAnotherTestKind(t *testing.T, validator *Validator, doc string, shouldError bool, expected *testAnotherKind, opts ...ValidateOption) []byte { + bytes := doValidate(t, validator, indexAnotherTestKind, doc, shouldError, opts...) + if shouldError { + return nil + } + + result := testAnotherKind{} + + expected.SchemaIndex = indexAnotherTestKind + + err := yaml.Unmarshal(bytes, &result) + require.NoError(t, err, "failed to unmarshal test another kind") + + require.Equal(t, expected.SchemaIndex, result.SchemaIndex, "invalid schema index") + require.Equal(t, expected.Key, result.Key, "invalid key value") + require.Equal(t, expected.Value, result.Value, "invalid value value") + + return bytes +} + +func asserTestKindIndex(t *testing.T, index SchemaIndex) { + require.True(t, index.IsValid(), "invalid index") + require.Equal(t, indexTestKind, index, "invalid index value") +} + +func asserValidateTestKind(t *testing.T, validator *Validator, doc string, shouldError bool, expected *testKind, opts ...ValidateOption) { + bytes := doValidate(t, validator, indexTestKind, doc, shouldError, opts...) + if shouldError { + return + } + asserTestKind(t, bytes, expected) +} + +func assertValidationWithTransformers(t *testing.T, validator *Validator, shouldError bool) { + doc := ` +apiVersion: test +kind: AnotherTestKind +key: "mykey" +value: + additionalPropertyInvalidWithTransformer: "invalid" + valueEnum: "OpenStack" + valueBool: true +` + bytes := asserValidateAnotherTestKind(t, validator, doc, shouldError, &testAnotherKind{ + Key: "mykey", + Value: testAnotherKindValue{ + ValueBool: true, + ValueEnum: "OpenStack", + }, + }) + + if shouldError { + return + } + + obj := map[string]any{} + err := yaml.Unmarshal(bytes, &obj) + require.NoError(t, err, "failed to unmarshal test another kind to map") + require.Contains(t, obj, "value") + value := obj["value"].(map[string]any) + require.Equal(t, value["additionalPropertyInvalidWithTransformer"], "invalid", "additional field should present") +} From 4873cd88b569daead1546b184446032bedd1e4d0 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 23:01:37 +0300 Subject: [PATCH 02/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- pkg/yaml/validation/validator.go | 10 +- pkg/yaml/validation/validator_test.go | 161 +++++++++++++++++++++++++- 2 files changed, 164 insertions(+), 7 deletions(-) diff --git a/pkg/yaml/validation/validator.go b/pkg/yaml/validation/validator.go index 2e2bc7d..49587b6 100644 --- a/pkg/yaml/validation/validator.go +++ b/pkg/yaml/validation/validator.go @@ -171,11 +171,6 @@ func (v *Validator) Validate(doc *[]byte, opts ...ValidateOption) (*SchemaIndex, // validate one document with schema // if schema not fount then return ErrSchemaNotFound func (v *Validator) ValidateWithIndex(index *SchemaIndex, doc *[]byte, opts ...ValidateOption) error { - options := &validateOptions{} - for _, opt := range opts { - opt(options) - } - if !index.IsValid() { return fmt.Errorf( "document must contain \"kind\" and \"apiVersion\" fields:\n\tapiVersion: %s\n\tkind: %s\n\n%s", @@ -183,6 +178,11 @@ func (v *Validator) ValidateWithIndex(index *SchemaIndex, doc *[]byte, opts ...V ) } + options := &validateOptions{} + for _, opt := range opts { + opt(options) + } + logger := log.SafeProvideLogger(v.loggerProvider) docForValidate := *doc diff --git a/pkg/yaml/validation/validator_test.go b/pkg/yaml/validation/validator_test.go index 7c89ca4..22656ea 100644 --- a/pkg/yaml/validation/validator_test.go +++ b/pkg/yaml/validation/validator_test.go @@ -15,6 +15,7 @@ package validation import ( + "encoding/json" "fmt" "strings" "testing" @@ -37,7 +38,6 @@ apiVersions: anyOf: - required: [apiVersion, kind, sshUser, sshAgentPrivateKeys] - required: [apiVersion, kind, sshUser, sudoPassword] - x-rules: [sshPrivateKey] properties: kind: type: string @@ -61,7 +61,7 @@ apiVersions: type: object additionalProperties: false required: [key] - x-rules: [sshPrivateKey] + x-rules: [passphrase] properties: key: type: string @@ -324,6 +324,107 @@ sshPort: 22456 assertValidationWithTransformers(t, validator, false) }) }) + + t.Run("with extensions", func(t *testing.T) { + tests := []struct { + name string + keyPassword string + shouldError bool + }{ + { + name: "invalid value", + keyPassword: `["a", "b"]`, + shouldError: true, + }, + + { + name: "value does not valid password string", + keyPassword: `"not secret"`, + shouldError: true, + }, + + { + name: "valid password string", + keyPassword: `"!not@secret."`, + shouldError: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assertPassphraseExtensions( + t, + getValidatorTestKind(t), + test.keyPassword, + test.shouldError, + ) + }) + } + }) + + t.Run("error case", func(t *testing.T) { + tests := []struct { + name string + doc string + errSubstring string + opts []ValidateOption + }{ + { + name: "invalid yaml", + doc: `{invalid`, + errSubstring: "error converting YAML to JSON: yaml: line 1", + }, + { + name: "no schema fields", + doc: ` +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 22456 +`, + errSubstring: `document must contain "kind" and "apiVersion"`, + }, + { + name: "no schema found", + doc: ` +apiVersion: some +kind: MyKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 22456 +`, + errSubstring: ErrSchemaNotFound.Error(), + }, + { + name: "not valid by schema", + doc: ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sshPort: "port" +sshAgentPrivateKeys: {"a": "b"} +`, + errSubstring: "Document validation failed:\n---", + }, + { + name: "not valid by schema no pretty error", + doc: ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sshPort: "port" +sshAgentPrivateKeys: {"a": "b"} +`, + errSubstring: `"TestKind, deckhouse.io/v1" document validation failed:`, + opts: []ValidateOption{ValidateWithNoPrettyError(true)}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + asserNoValidateTestKind(t, getValidatorTestKind(t), test.doc, test.errSubstring, test.opts...) + }) + } + }) }) } @@ -415,10 +516,12 @@ func doValidate(t *testing.T, validator *Validator, expectedIndex SchemaIndex, d opts = []ValidateOption{ValidateWithNoPrettyError(true)} } + bytesForError := []byte(doc) bytesValidateWithIndex := []byte(doc) err := validator.ValidateWithIndex(&index, &bytesValidateWithIndex, opts...) if shouldError { require.Error(t, err, "should validation error for test another kind") + require.Equal(t, bytesForError, bytesValidateWithIndex, "should not change input") return nil } require.NoError(t, err, "should not validation error for test another kind") @@ -462,6 +565,17 @@ func asserValidateTestKind(t *testing.T, validator *Validator, doc string, shoul asserTestKind(t, bytes, expected) } +func asserNoValidateTestKind(t *testing.T, validator *Validator, doc string, errorSubstring string, opts ...ValidateOption) { + bytesForError := []byte(doc) + bytesValidateWithIndex := []byte(doc) + + _, err := validator.Validate(&bytesValidateWithIndex, opts...) + + require.Error(t, err, "should not validate") + require.Equal(t, bytesForError, bytesValidateWithIndex, "should not change input") + require.Contains(t, err.Error(), errorSubstring) +} + func assertValidationWithTransformers(t *testing.T, validator *Validator, shouldError bool) { doc := ` apiVersion: test @@ -491,3 +605,46 @@ value: value := obj["value"].(map[string]any) require.Equal(t, value["additionalPropertyInvalidWithTransformer"], "invalid", "additional field should present") } + +func assertPassphraseExtensions(t *testing.T, validator *Validator, keyPassword string, shouldError bool) { + validators := map[string]ExtensionsValidatorHandler{ + "passphrase": func(oldValue json.RawMessage) error { + key := testPrivateKey{} + err := json.Unmarshal(oldValue, &key) + if err != nil { + return err + } + + shouldPresent := ".!@" + + if !strings.ContainsAny(key.Passphrase, shouldPresent) { + return fmt.Errorf("invalid passphrase: should contain %s", shouldPresent) + } + + return nil + }, + } + + validator.AddExtensionsValidators(NewXRulesExtensionsValidator(validators)) + + doc := fmt.Sprintf(` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sshPort: 2200 +sshAgentPrivateKeys: +- key: "mykey" + passphrase: %s +`, keyPassword) + + asserValidateTestKind(t, validator, doc, shouldError, &testKind{ + SSHUser: "ubuntu", + SSHPort: 2200, + SSHAgentPrivateKeys: []testPrivateKey{ + { + Key: "mykey", + Passphrase: strings.Trim(keyPassword, `"`), + }, + }, + }) +} From 5d4cb508597df0b67509b514b8e541047fb8e614 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 23:14:55 +0300 Subject: [PATCH 03/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- pkg/yaml/validation/validator_test.go | 643 ++++++++++++++------------ 1 file changed, 359 insertions(+), 284 deletions(-) diff --git a/pkg/yaml/validation/validator_test.go b/pkg/yaml/validation/validator_test.go index 22656ea..998c581 100644 --- a/pkg/yaml/validation/validator_test.go +++ b/pkg/yaml/validation/validator_test.go @@ -20,142 +20,58 @@ import ( "strings" "testing" - "github.com/deckhouse/lib-dhctl/pkg/log" - "github.com/deckhouse/lib-dhctl/pkg/yaml/validation/transformer" "github.com/go-openapi/spec" "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" -) -const ( - testSchemaTestKind = ` -kind: TestKind -apiVersions: -- apiVersion: deckhouse.io/v1 - openAPISpec: - type: object - additionalProperties: false - anyOf: - - required: [apiVersion, kind, sshUser, sshAgentPrivateKeys] - - required: [apiVersion, kind, sshUser, sudoPassword] - properties: - kind: - type: string - apiVersion: - type: string - sshUser: - type: string - description: SSH username. - sudoPassword: - description: | - A sudo password for the user. - type: string - sshPort: - default: 22 - type: integer - description: SSH port. - sshAgentPrivateKeys: - type: array - minItems: 1 - items: - type: object - additionalProperties: false - required: [key] - x-rules: [passphrase] - properties: - key: - type: string - description: Private SSH key. - passphrase: - type: string - description: Password for SSH key. -` - testSchemaAnotherTestKind = ` -kind: AnotherTestKind -apiVersions: -- apiVersion: test - openAPISpec: - type: object - additionalProperties: false - properties: - kind: - type: string - apiVersion: - type: string - key: - type: string - value: - type: object - additionalProperties: true - default: {"valueEnum": "AWS", "valueBool": true} - properties: - valueEnum: - type: string - enum: - - "OpenStack" - - "AWS" - valueBool: - type: boolean -` + "github.com/deckhouse/lib-dhctl/pkg/log" + "github.com/deckhouse/lib-dhctl/pkg/yaml/validation/transformer" ) -var ( - indexTestKind = SchemaIndex{ - Kind: "TestKind", - Version: "deckhouse.io/v1", - } +func TestOneSchemaValidator(t *testing.T) { + logger := testGetLogger() - indexAnotherTestKind = SchemaIndex{ - Kind: "AnotherTestKind", - Version: "test", + getValidatorTestKind := func(t *testing.T) *Validator { + validator := NewValidator(nil).SetLogger(logger) + err := validator.LoadSchemas(strings.NewReader(testSchemaTestKind)) + require.NoError(t, err, "failed to load schema") + return validator } -) - -func TestValidator(t *testing.T) { - t.Run("One schema", func(t *testing.T) { - logger := testGetLogger() - - getValidatorTestKind := func(t *testing.T) *Validator { - validator := NewValidator(nil).SetLogger(logger) - err := validator.LoadSchemas(strings.NewReader(testSchemaTestKind)) - require.NoError(t, err, "failed to load schema") - return validator - } - getValidatorAnotherTestKind := func(t *testing.T) *Validator { - validator := NewValidator(nil).SetLogger(logger) - err := validator.LoadSchemas(strings.NewReader(testSchemaAnotherTestKind)) - require.NoError(t, err, "failed to load schema") - return validator - } + getValidatorAnotherTestKind := func(t *testing.T) *Validator { + validator := NewValidator(nil).SetLogger(logger) + err := validator.LoadSchemas(strings.NewReader(testSchemaAnotherTestKind)) + require.NoError(t, err, "failed to load schema") + return validator + } - t.Run("happy case", func(t *testing.T) { - validatorTestKind := getValidatorTestKind(t) + t.Run("happy case", func(t *testing.T) { + validatorTestKind := getValidatorTestKind(t) - docPassword := ` + docPassword := ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sudoPassword: "no secret" sshPort: 2200 ` - bytesValidate := []byte(docPassword) - index, err := validatorTestKind.Validate(&bytesValidate, ValidateWithNoPrettyError(true)) - require.NoError(t, err) - asserTestKindIndex(t, *index) + bytesValidate := []byte(docPassword) + index, err := validatorTestKind.Validate(&bytesValidate, ValidateWithNoPrettyError(true)) + require.NoError(t, err) + asserTestKindIndex(t, *index) - expectedTestKind := &testKind{ - SchemaIndex: indexTestKind, - SSHUser: "ubuntu", - SudoPassword: "no secret", - SSHPort: 2200, - } + expectedTestKind := &testKind{ + SchemaIndex: indexTestKind, + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 2200, + } - asserTestKind(t, bytesValidate, expectedTestKind) + asserTestKind(t, bytesValidate, expectedTestKind) - asserValidateTestKind(t, validatorTestKind, docPassword, false, expectedTestKind) + asserValidateTestKind(t, validatorTestKind, docPassword, false, expectedTestKind) - docKey := ` + docKey := ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu @@ -163,271 +79,430 @@ sshPort: 2200 sshAgentPrivateKeys: - key: "mykey" ` - asserValidateTestKind(t, validatorTestKind, docKey, false, &testKind{ - SSHUser: "ubuntu", - SSHPort: 2200, - SSHAgentPrivateKeys: []testPrivateKey{ - {Key: "mykey"}, - }, - }) + asserValidateTestKind(t, validatorTestKind, docKey, false, &testKind{ + SSHUser: "ubuntu", + SSHPort: 2200, + SSHAgentPrivateKeys: []testPrivateKey{ + {Key: "mykey"}, + }, }) + }) - t.Run("set defaults", func(t *testing.T) { - doc := ` + t.Run("set defaults", func(t *testing.T) { + doc := ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sudoPassword: "no secret" ` - asserValidateTestKind(t, getValidatorTestKind(t), doc, false, &testKind{ - SSHUser: "ubuntu", - SudoPassword: "no secret", - SSHPort: 22, - }) + asserValidateTestKind(t, getValidatorTestKind(t), doc, false, &testKind{ + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 22, + }) - anotherTestKindDoc := ` + anotherTestKindDoc := ` apiVersion: test kind: AnotherTestKind key: "mykey" ` - asserValidateAnotherTestKind( - t, - getValidatorAnotherTestKind(t), - anotherTestKindDoc, - false, - &testAnotherKind{ - Key: "mykey", - Value: testAnotherKindValue{ - ValueEnum: "AWS", - ValueBool: true, - }, - }) - }) + asserValidateAnotherTestKind( + t, + getValidatorAnotherTestKind(t), + anotherTestKindDoc, + false, + &testAnotherKind{ + Key: "mykey", + Value: testAnotherKindValue{ + ValueEnum: "AWS", + ValueBool: true, + }, + }) + }) - t.Run("version fallback", func(t *testing.T) { - validatorTestKind := getValidatorTestKind(t). - AddVersionFallback("test", indexTestKind.Version) - // copy - index := indexTestKind - index.Version = "test" + t.Run("version fallback", func(t *testing.T) { + validatorTestKind := getValidatorTestKind(t). + AddVersionFallback("test", indexTestKind.Version) + // copy + index := indexTestKind + index.Version = "test" - doc := ` + doc := ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sudoPassword: "no secret" ` - bytesValidateWithIndex := []byte(doc) - err := validatorTestKind.ValidateWithIndex(&index, &bytesValidateWithIndex) - require.NoError(t, err) - asserTestKindIndex(t, index) - }) + bytesValidateWithIndex := []byte(doc) + err := validatorTestKind.ValidateWithIndex(&index, &bytesValidateWithIndex) + require.NoError(t, err) + asserTestKindIndex(t, index) + }) - t.Run("prevalidator", func(t *testing.T) { - t.Run("doc valid", func(t *testing.T) { - doc := ` + t.Run("prevalidator", func(t *testing.T) { + t.Run("doc valid", func(t *testing.T) { + doc := ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sudoPassword: "no secret" sshPort: 22456 ` - validator := getValidatorTestKind(t) - validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, "")) - - asserValidateTestKind(t, validator, doc, false, &testKind{ - SSHUser: "ubuntu", - SudoPassword: "no secret", - SSHPort: 22456, - }) + validator := getValidatorTestKind(t) + validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, "")) + + asserValidateTestKind(t, validator, doc, false, &testKind{ + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 22456, }) + }) - t.Run("doc invalid", func(t *testing.T) { - doc := ` + t.Run("doc invalid", func(t *testing.T) { + doc := ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sudoPassword: "no secret" sshPort: 23 ` - validator := getValidatorTestKind(t) - validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, "")) + validator := getValidatorTestKind(t) + validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, "")) - asserValidateTestKind(t, validator, doc, true, nil) - }) + asserValidateTestKind(t, validator, doc, true, nil) + }) - t.Run("our schema valid", func(t *testing.T) { - doc := ` + t.Run("our schema valid", func(t *testing.T) { + doc := ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sudoPassword: "no secret" sshPort: 22456 ` - validator := NewValidator(nil) - validator.SetLogger(logger) + validator := NewValidator(nil) + validator.SetLogger(logger) - validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, testSchemaTestKind)) + validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, testSchemaTestKind)) - asserValidateTestKind(t, validator, doc, false, &testKind{ - SSHUser: "ubuntu", - SudoPassword: "no secret", - SSHPort: 22456, - }) + asserValidateTestKind(t, validator, doc, false, &testKind{ + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 22456, }) + }) - t.Run("our schema invalid", func(t *testing.T) { - doc := ` + t.Run("our schema invalid", func(t *testing.T) { + doc := ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sudoPassword: "no secret" sshPort: 22456 ` - validator := NewValidator(nil) - validator.SetLogger(logger) + validator := NewValidator(nil) + validator.SetLogger(logger) - validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, testSchemaAnotherTestKind)) + validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, testSchemaAnotherTestKind)) - asserValidateTestKind(t, validator, doc, true, nil) - }) + asserValidateTestKind(t, validator, doc, true, nil) }) + }) - t.Run("add transformers", func(t *testing.T) { - t.Run("for index", func(t *testing.T) { - validator := getValidatorAnotherTestKind(t) - validator.AddTransformers( - indexAnotherTestKind, - transformer.NewAdditionalPropertiesTransformerDisallowFull(), - ) + t.Run("add transformers", func(t *testing.T) { + t.Run("for index", func(t *testing.T) { + validator := getValidatorAnotherTestKind(t) + validator.AddTransformers( + indexAnotherTestKind, + transformer.NewAdditionalPropertiesTransformerDisallowFull(), + ) - assertValidationWithTransformers(t, validator, true) - }) + assertValidationWithTransformers(t, validator, true) + }) - t.Run("default", func(t *testing.T) { - validator := getValidatorAnotherTestKind(t) - validator.SetDefaultTransformers( - transformer.NewAdditionalPropertiesTransformerDisallowFull(), - ) + t.Run("default", func(t *testing.T) { + validator := getValidatorAnotherTestKind(t) + validator.SetDefaultTransformers( + transformer.NewAdditionalPropertiesTransformerDisallowFull(), + ) - assertValidationWithTransformers(t, validator, true) - }) + assertValidationWithTransformers(t, validator, true) + }) - t.Run("additional properties enabled is set in schema", func(t *testing.T) { - validator := getValidatorAnotherTestKind(t) - validator.SetDefaultTransformers( - transformer.NewAdditionalPropertiesTransformer(), - ) + t.Run("additional properties enabled is set in schema", func(t *testing.T) { + validator := getValidatorAnotherTestKind(t) + validator.SetDefaultTransformers( + transformer.NewAdditionalPropertiesTransformer(), + ) - assertValidationWithTransformers(t, validator, false) - }) + assertValidationWithTransformers(t, validator, false) }) + }) - t.Run("with extensions", func(t *testing.T) { - tests := []struct { - name string - keyPassword string - shouldError bool - }{ - { - name: "invalid value", - keyPassword: `["a", "b"]`, - shouldError: true, - }, + t.Run("with extensions", func(t *testing.T) { + tests := []struct { + name string + keyPassword string + shouldError bool + }{ + { + name: "invalid value", + keyPassword: `["a", "b"]`, + shouldError: true, + }, - { - name: "value does not valid password string", - keyPassword: `"not secret"`, - shouldError: true, - }, + { + name: "value does not valid password string", + keyPassword: `"not secret"`, + shouldError: true, + }, - { - name: "valid password string", - keyPassword: `"!not@secret."`, - shouldError: false, - }, - } + { + name: "valid password string", + keyPassword: `"!not@secret."`, + shouldError: false, + }, + } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assertPassphraseExtensions( - t, - getValidatorTestKind(t), - test.keyPassword, - test.shouldError, - ) - }) - } - }) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assertPassphraseExtensions( + t, + getValidatorTestKind(t), + test.keyPassword, + test.shouldError, + ) + }) + } + }) - t.Run("error case", func(t *testing.T) { - tests := []struct { - name string - doc string - errSubstring string - opts []ValidateOption - }{ - { - name: "invalid yaml", - doc: `{invalid`, - errSubstring: "error converting YAML to JSON: yaml: line 1", - }, - { - name: "no schema fields", - doc: ` + t.Run("error case", func(t *testing.T) { + tests := []struct { + name string + doc string + errSubstring string + opts []ValidateOption + }{ + { + name: "invalid yaml", + doc: `{invalid`, + errSubstring: "error converting YAML to JSON: yaml: line 1", + }, + { + name: "no schema fields", + doc: ` sshUser: ubuntu sudoPassword: "no secret" sshPort: 22456 `, - errSubstring: `document must contain "kind" and "apiVersion"`, - }, - { - name: "no schema found", - doc: ` + errSubstring: `document must contain "kind" and "apiVersion"`, + }, + { + name: "no schema found", + doc: ` apiVersion: some kind: MyKind sshUser: ubuntu sudoPassword: "no secret" sshPort: 22456 `, - errSubstring: ErrSchemaNotFound.Error(), - }, - { - name: "not valid by schema", - doc: ` + errSubstring: ErrSchemaNotFound.Error(), + }, + { + name: "not valid by schema", + doc: ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sshPort: "port" sshAgentPrivateKeys: {"a": "b"} `, - errSubstring: "Document validation failed:\n---", - }, - { - name: "not valid by schema no pretty error", - doc: ` + errSubstring: "Document validation failed:\n---", + }, + { + name: "not valid by schema no pretty error", + doc: ` apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sshPort: "port" sshAgentPrivateKeys: {"a": "b"} `, - errSubstring: `"TestKind, deckhouse.io/v1" document validation failed:`, - opts: []ValidateOption{ValidateWithNoPrettyError(true)}, - }, - } + errSubstring: `"TestKind, deckhouse.io/v1" document validation failed:`, + opts: []ValidateOption{ValidateWithNoPrettyError(true)}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + asserNoValidateTestKind(t, getValidatorTestKind(t), test.doc, test.errSubstring, test.opts...) + }) + } + }) +} + +func TestMultipleSchemasValidator(t *testing.T) { + logger := testGetLogger() + + testKindSchemas, err := LoadSchemas(strings.NewReader(testSchemaTestKind)) + require.NoError(t, err) + + initMap := make(map[SchemaIndex]*spec.Schema) + for _, schema := range testKindSchemas { + initMap[schema.Index] = schema.Schema + } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - asserNoValidateTestKind(t, getValidatorTestKind(t), test.doc, test.errSubstring, test.opts...) - }) + validator := NewValidator(initMap).SetLogger(logger) + + testAnotherKindSchemas, err := LoadSchemas(strings.NewReader(testSchemaAnotherTestKind)) + require.NoError(t, err) + for _, schema := range testAnotherKindSchemas { + validator.AddSchema(schema.Index, schema.Schema) + } + + tests := []struct { + name string + doc string + shouldError bool + }{ + { + name: "test kind validate", + doc: ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 2200 +`, + shouldError: false, + }, + + { + name: "another test kind validate", + doc: ` +apiVersion: test +kind: AnotherTestKind +key: "mykey" +value: + valueEnum: "OpenStack" + valueBool: false +`, + shouldError: false, + }, + + { + name: "not present schema", + doc: ` +apiVersion: my +kind: MyKind +key: "key" +value: 1 +`, + shouldError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + bytes := []byte(test.doc) + _, err := validator.Validate(&bytes) + + assertError := require.NoError + if test.shouldError { + assertError = require.Error } + + assertError(t, err) }) - }) + } } +const ( + testSchemaTestKind = ` +kind: TestKind +apiVersions: +- apiVersion: deckhouse.io/v1 + openAPISpec: + type: object + additionalProperties: false + anyOf: + - required: [apiVersion, kind, sshUser, sshAgentPrivateKeys] + - required: [apiVersion, kind, sshUser, sudoPassword] + properties: + kind: + type: string + apiVersion: + type: string + sshUser: + type: string + description: SSH username. + sudoPassword: + description: | + A sudo password for the user. + type: string + sshPort: + default: 22 + type: integer + description: SSH port. + sshAgentPrivateKeys: + type: array + minItems: 1 + items: + type: object + additionalProperties: false + required: [key] + x-rules: [passphrase] + properties: + key: + type: string + description: Private SSH key. + passphrase: + type: string + description: Password for SSH key. +` + testSchemaAnotherTestKind = ` +kind: AnotherTestKind +apiVersions: +- apiVersion: test + openAPISpec: + type: object + additionalProperties: false + properties: + kind: + type: string + apiVersion: + type: string + key: + type: string + value: + type: object + additionalProperties: true + default: {"valueEnum": "AWS", "valueBool": true} + properties: + valueEnum: + type: string + enum: + - "OpenStack" + - "AWS" + valueBool: + type: boolean +` +) + +var ( + indexTestKind = SchemaIndex{ + Kind: "TestKind", + Version: "deckhouse.io/v1", + } + + indexAnotherTestKind = SchemaIndex{ + Kind: "AnotherTestKind", + Version: "test", + } +) + func testGetLogger() log.LoggerProvider { return log.SimpleLoggerProvider( log.NewInMemoryLoggerWithParent( From bd463240b90037b252c8e5585e0d89674b1aca76 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 23:36:37 +0300 Subject: [PATCH 04/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- go.mod | 4 +-- go.sum | 18 +++++++++++ pkg/yaml/split.go | 39 +++++++++++++++++++++++ pkg/yaml/unmarshal.go | 36 +++++++++++++++++++++ pkg/yaml/unmarshal_test.go | 65 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 pkg/yaml/split.go create mode 100644 pkg/yaml/unmarshal.go create mode 100644 pkg/yaml/unmarshal_test.go diff --git a/go.mod b/go.mod index f28ebb4..d612e96 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/mitchellh/mapstructure v1.3.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect - go.mongodb.org/mongo-driver v1.3.4 // indirect + go.mongodb.org/mongo-driver v1.5.1 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/net v0.47.0 // indirect @@ -47,5 +47,3 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) - -replace gopkg.in/alecthomas/kingpin.v2 => github.com/alecthomas/kingpin v1.3.8-0.20200323085623-b6657d9477a6 diff --git a/go.sum b/go.sum index 4654004..764fc87 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,7 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 h1:HrMVYtly2IVqg9EBooHsakQ256ueojP7QuG32K71X/U= github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774/go.mod h1:5wi5YYOpfuAKwL5XLFYopbgIl/v7NZxaJpa/4X6yFKE= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -95,6 +96,7 @@ github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7 github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= github.com/go-openapi/validate v0.19.12 h1:mPLM/bfbd00PGOCJlU0yJL7IulkZ+q9VjPv7U11RMQQ= github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= @@ -124,6 +126,7 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY9 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= @@ -133,6 +136,8 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= @@ -165,8 +170,10 @@ github.com/name212/govalue v1.0.2/go.mod h1:3mLA4mFb82esucQHCOIAnUjN7e7AZnRYEfxe github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -195,15 +202,21 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/werf/logboek v0.5.5 h1:RmtTejHJOyw0fub4pIfKsb7OTzD90ZOUyuBAXqYqJpU= github.com/werf/logboek v0.5.5/go.mod h1:Gez5J4bxekyr6MxTmIJyId1F61rpO+0/V4vjCIEIZmk= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE= go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI= +go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -213,6 +226,7 @@ golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -221,6 +235,7 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= @@ -228,6 +243,7 @@ golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -245,6 +261,7 @@ golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -256,6 +273,7 @@ golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/yaml/split.go b/pkg/yaml/split.go new file mode 100644 index 0000000..a1d8b22 --- /dev/null +++ b/pkg/yaml/split.go @@ -0,0 +1,39 @@ +// Copyright 2026 Flant JSC +// +// 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 yaml + +import ( + "io" + "regexp" + "strings" +) + +var yamlSplitRegexp = regexp.MustCompile(`(?:^|\s*\n)---\s*`) + +func SplitYAML(s string) []string { + return yamlSplitRegexp.Split(strings.TrimSpace(s), -1) +} + +func SplitYAMLBytes(s []byte) []string { + return SplitYAML(string(s)) +} + +func SplitYAMLReader(reader io.Reader) ([]string, error) { + content, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + return SplitYAML(string(content)), nil +} diff --git a/pkg/yaml/unmarshal.go b/pkg/yaml/unmarshal.go new file mode 100644 index 0000000..906b2d8 --- /dev/null +++ b/pkg/yaml/unmarshal.go @@ -0,0 +1,36 @@ +// Copyright 2026 Flant JSC +// +// 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 yaml + +import ( + "fmt" + "reflect" + + "gopkg.in/yaml.v3" +) + +func Unmarshal[T any](data []byte) (T, error) { + var result T + err := yaml.Unmarshal(data, &result) + if err != nil { + return result, fmt.Errorf("failed to unmarshal to %s: %w", reflect.TypeFor[T]().String(), err) + } + + return result, nil +} + +func UnmarshalString[T any](data string) (T, error) { + return Unmarshal[T]([]byte(data)) +} diff --git a/pkg/yaml/unmarshal_test.go b/pkg/yaml/unmarshal_test.go new file mode 100644 index 0000000..1706771 --- /dev/null +++ b/pkg/yaml/unmarshal_test.go @@ -0,0 +1,65 @@ +// Copyright 2026 Flant JSC +// +// 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 yaml + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type testType struct { + StringValue string `yaml:"string"` + IntValue int `yaml:"int"` + Sub testSubType `yaml:"sub"` +} + +type testSubType struct { + SliceValue []string `yaml:"slice"` +} + +func TestUnmarshal(t *testing.T) { + doc := ` +string: str +int: 42 +sub: + slice: + - "first" + - "second" + - "third" +` + + assertResult := func(t *testing.T, result testType) { + require.Equal(t, "str", result.StringValue) + require.Equal(t, 42, result.IntValue) + require.Len(t, result.Sub.SliceValue, 3) + require.Equal(t, "first", result.Sub.SliceValue[0]) + require.Equal(t, "second", result.Sub.SliceValue[1]) + require.Equal(t, "third", result.Sub.SliceValue[2]) + } + + t.Run("val", func(t *testing.T) { + res, err := UnmarshalString[testType](doc) + require.NoError(t, err) + assertResult(t, res) + }) + + t.Run("pointer", func(t *testing.T) { + res, err := UnmarshalString[*testType](doc) + require.NoError(t, err) + require.NotNil(t, res) + assertResult(t, *res) + }) +} From bc4a88a730ab0e76fe204c426b725df2a9ebb157 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 23:42:01 +0300 Subject: [PATCH 05/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- pkg/log/ln_logger_wrapper.go | 4 +++- pkg/log/ln_logger_wrapper_test.go | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/log/ln_logger_wrapper.go b/pkg/log/ln_logger_wrapper.go index fc55d3a..8f2af41 100644 --- a/pkg/log/ln_logger_wrapper.go +++ b/pkg/log/ln_logger_wrapper.go @@ -14,6 +14,8 @@ package log +import "strings" + // formatWithNewLineLogger // we often use *F function, but for pretty log we use "\n" in end of string // this interface and wrapper help us for get rit of this @@ -42,5 +44,5 @@ func (w *formatWithNewLineLoggerWrapper) WarnF(format string, a ...any) { } func addLnToFormat(format string) string { - return format + "\n" + return strings.TrimRight(format, "\n") + "\n" } diff --git a/pkg/log/ln_logger_wrapper_test.go b/pkg/log/ln_logger_wrapper_test.go index 0e469a7..59e0100 100644 --- a/pkg/log/ln_logger_wrapper_test.go +++ b/pkg/log/ln_logger_wrapper_test.go @@ -25,13 +25,15 @@ import ( func TestLnLoggerWrapper(t *testing.T) { logger := NewInMemoryLoggerWithParent(NewSimpleLogger(LoggerOptions{IsDebug: true})) - assertAddNewLine := func(t *testing.T, msg string) { + assertAddNewLine := func(t *testing.T, msg string) string { matches, err := logger.AllMatches(&Match{ Prefix: []string{fmt.Sprintf("%s\n", msg)}, }) require.NoError(t, err) require.Len(t, matches, 1, msg) + + return matches[0] } wrapper := newFormatWithNewLineLoggerWrapper(logger) @@ -54,6 +56,11 @@ func TestLnLoggerWrapper(t *testing.T) { wrapper.InfoF("VariablesInfo %s %v", "msg", errors.New("error")) assertAddNewLine(t, "VariablesInfo msg error") + // cut new line from format + wrapper.InfoF("Format with new line %d\n", 42) + match := assertAddNewLine(t, "Format with new line 42") + require.Equal(t, "Format with new line 42\n", match) + wrapper.DebugF("VariablesDebug %v %s", 42, "msg") assertAddNewLine(t, "VariablesDebug 42 msg") } From 40a5d3241e22745dd71690180d50b4822b8505df Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 23:43:03 +0300 Subject: [PATCH 06/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- pkg/yaml/validation/schema.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/yaml/validation/schema.go b/pkg/yaml/validation/schema.go index e78146a..d0f6699 100644 --- a/pkg/yaml/validation/schema.go +++ b/pkg/yaml/validation/schema.go @@ -16,8 +16,6 @@ package validation import ( "fmt" - - "github.com/go-openapi/spec" ) type SchemaIndex struct { @@ -32,5 +30,3 @@ func (i *SchemaIndex) IsValid() bool { func (i *SchemaIndex) String() string { return fmt.Sprintf("%s, %s", i.Kind, i.Version) } - -var s spec.Schema From 96dffa9153e78151747280ddfa39450560ec6cf1 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 23:45:34 +0300 Subject: [PATCH 07/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- go.mod | 13 ++++++++----- go.sum | 58 ++++++++++------------------------------------------------ 2 files changed, 18 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index d612e96..3655b97 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,16 @@ go 1.25.3 require ( github.com/deckhouse/deckhouse/pkg/log v0.1.1-0.20251230144142-2bad7c3d1edf github.com/go-openapi/spec v0.19.8 + github.com/go-openapi/strfmt v0.19.5 + github.com/go-openapi/validate v0.19.12 github.com/gookit/color v1.5.2 + github.com/hashicorp/go-multierror v1.1.1 github.com/name212/govalue v1.0.2 github.com/stretchr/testify v1.9.0 github.com/werf/logboek v0.5.5 + gopkg.in/yaml.v3 v3.0.1 k8s.io/klog/v2 v2.130.1 + sigs.k8s.io/yaml v1.6.0 ) require ( @@ -26,12 +31,9 @@ require ( github.com/go-openapi/jsonreference v0.19.3 // indirect github.com/go-openapi/loads v0.19.5 // indirect github.com/go-openapi/runtime v0.19.16 // indirect - github.com/go-openapi/strfmt v0.19.5 // indirect github.com/go-openapi/swag v0.19.9 // indirect - github.com/go-openapi/validate v0.19.12 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/mailru/easyjson v0.7.1 // indirect github.com/mitchellh/mapstructure v1.3.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -44,6 +46,7 @@ require ( golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - sigs.k8s.io/yaml v1.6.0 // indirect ) + +// Remove 'in body' from errors, fix for Go 1.16 (https://github.com/go-openapi/validate/pull/138). +replace github.com/go-openapi/validate => github.com/flant/go-openapi-validate v0.19.12-flant.0 diff --git a/go.sum b/go.sum index 764fc87..9b8a71f 100644 --- a/go.sum +++ b/go.sum @@ -22,80 +22,48 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckhouse/deckhouse/pkg/log v0.1.1-0.20251230144142-2bad7c3d1edf h1:4HrDzRZcLpREJ+2cSGNmxHVQlxXRcH2r5TGmTcoTZiU= github.com/deckhouse/deckhouse/pkg/log v0.1.1-0.20251230144142-2bad7c3d1edf/go.mod h1:pbAxTSDcPmwyl3wwKDcEB3qdxHnRxqTV+J0K+sha8bw= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/flant/go-openapi-validate v0.19.12-flant.0 h1:xk6kvc9fHKMgUdB6J7kbpbLR5vJOUzKAK8p3nrD7mDk= +github.com/flant/go-openapi-validate v0.19.12-flant.0/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gmuz9fdE= github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.7 h1:Lcq+o0mSwCLKACMxZhreVHigB9ebghJ/lrmeaqASbjo= github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls= github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= github.com/go-openapi/runtime v0.19.16 h1:tQMAY5s5BfmmCC31+ufDCsGrr8iO1A8UIdYfDo5ADvs= github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/spec v0.19.8 h1:qAdZLh1r6QF/hI/gTq+TJTvsQUodZsM7KLqkAJdiJNg= github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= -github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= -github.com/go-openapi/validate v0.19.12 h1:mPLM/bfbd00PGOCJlU0yJL7IulkZ+q9VjPv7U11RMQQ= -github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -127,7 +95,10 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= @@ -145,17 +116,14 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= @@ -167,6 +135,7 @@ github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/name212/govalue v1.0.2 h1:NLpLfZatHyJYMohyUP8+qXtP8OriHQToxZv+DIXNrno= github.com/name212/govalue v1.0.2/go.mod h1:3mLA4mFb82esucQHCOIAnUjN7e7AZnRYEfxeaHLKjho= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= @@ -191,13 +160,13 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/werf/logboek v0.5.5 h1:RmtTejHJOyw0fub4pIfKsb7OTzD90ZOUyuBAXqYqJpU= @@ -211,27 +180,24 @@ github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHg github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE= go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI= go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -246,7 +212,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -272,17 +237,14 @@ golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= From b2f5f418bfdd1763b850a96148533cb7675b322a Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 23:47:59 +0300 Subject: [PATCH 08/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- pkg/yaml/validation/schema_loader.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/yaml/validation/schema_loader.go b/pkg/yaml/validation/schema_loader.go index 71986e8..e5b7867 100644 --- a/pkg/yaml/validation/schema_loader.go +++ b/pkg/yaml/validation/schema_loader.go @@ -1,3 +1,17 @@ +// Copyright 2026 Flant JSC +// +// 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 validation import ( From 8f10fb43b3a1a4f38358c59725ab156ae3cb8a23 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Thu, 15 Jan 2026 23:50:52 +0300 Subject: [PATCH 09/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- pkg/log/ln_logger_wrapper.go | 4 +--- pkg/log/ln_logger_wrapper_test.go | 5 ----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/pkg/log/ln_logger_wrapper.go b/pkg/log/ln_logger_wrapper.go index 8f2af41..fc55d3a 100644 --- a/pkg/log/ln_logger_wrapper.go +++ b/pkg/log/ln_logger_wrapper.go @@ -14,8 +14,6 @@ package log -import "strings" - // formatWithNewLineLogger // we often use *F function, but for pretty log we use "\n" in end of string // this interface and wrapper help us for get rit of this @@ -44,5 +42,5 @@ func (w *formatWithNewLineLoggerWrapper) WarnF(format string, a ...any) { } func addLnToFormat(format string) string { - return strings.TrimRight(format, "\n") + "\n" + return format + "\n" } diff --git a/pkg/log/ln_logger_wrapper_test.go b/pkg/log/ln_logger_wrapper_test.go index 59e0100..006c1d5 100644 --- a/pkg/log/ln_logger_wrapper_test.go +++ b/pkg/log/ln_logger_wrapper_test.go @@ -56,11 +56,6 @@ func TestLnLoggerWrapper(t *testing.T) { wrapper.InfoF("VariablesInfo %s %v", "msg", errors.New("error")) assertAddNewLine(t, "VariablesInfo msg error") - // cut new line from format - wrapper.InfoF("Format with new line %d\n", 42) - match := assertAddNewLine(t, "Format with new line 42") - require.Equal(t, "Format with new line 42\n", match) - wrapper.DebugF("VariablesDebug %v %s", 42, "msg") assertAddNewLine(t, "VariablesDebug 42 msg") } From f680b917eb790cf9209675ee7cb5c65bc565d80e Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Fri, 16 Jan 2026 20:09:41 +0300 Subject: [PATCH 10/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- pkg/log/ln_logger_wrapper_test.go | 4 +- pkg/yaml/validation/error.go | 12 ++- pkg/yaml/validation/extensions.go | 16 ++-- pkg/yaml/validation/{schema.go => index.go} | 0 ...properties.go => additional_properties.go} | 0 pkg/yaml/validation/validator.go | 85 ++++++++++-------- pkg/yaml/validation/validator_test.go | 89 ++++++++++++------- 7 files changed, 125 insertions(+), 81 deletions(-) rename pkg/yaml/validation/{schema.go => index.go} (100%) rename pkg/yaml/validation/transformer/{additional-properties.go => additional_properties.go} (100%) diff --git a/pkg/log/ln_logger_wrapper_test.go b/pkg/log/ln_logger_wrapper_test.go index 006c1d5..0e469a7 100644 --- a/pkg/log/ln_logger_wrapper_test.go +++ b/pkg/log/ln_logger_wrapper_test.go @@ -25,15 +25,13 @@ import ( func TestLnLoggerWrapper(t *testing.T) { logger := NewInMemoryLoggerWithParent(NewSimpleLogger(LoggerOptions{IsDebug: true})) - assertAddNewLine := func(t *testing.T, msg string) string { + assertAddNewLine := func(t *testing.T, msg string) { matches, err := logger.AllMatches(&Match{ Prefix: []string{fmt.Sprintf("%s\n", msg)}, }) require.NoError(t, err) require.Len(t, matches, 1, msg) - - return matches[0] } wrapper := newFormatWithNewLineLoggerWrapper(logger) diff --git a/pkg/yaml/validation/error.go b/pkg/yaml/validation/error.go index 4a11896..7270c47 100644 --- a/pkg/yaml/validation/error.go +++ b/pkg/yaml/validation/error.go @@ -25,16 +25,26 @@ const ( ErrKindChangesValidationFailed ErrorKind = iota + 1 ErrKindValidationFailed ErrKindInvalidYAML + ErrDocumentValidationFailed + ErrSchemaNotFound ) +func (k ErrorKind) Error() string { + return k.String() +} + func (k ErrorKind) String() string { switch k { case ErrKindChangesValidationFailed: return "ChangesValidationFailed" case ErrKindValidationFailed: - return "ValidationFailed" + return "DocumentKindValidationFailed" case ErrKindInvalidYAML: return "InvalidYAML" + case ErrDocumentValidationFailed: + return "DocumentValidationFailed" + case ErrSchemaNotFound: + return "SchemaNotFound" default: return "unknown" } diff --git a/pkg/yaml/validation/extensions.go b/pkg/yaml/validation/extensions.go index a6db174..8cbd4c3 100644 --- a/pkg/yaml/validation/extensions.go +++ b/pkg/yaml/validation/extensions.go @@ -87,14 +87,14 @@ func (v *ExtensionsValidator) Validate(data json.RawMessage, schema spec.Schema) } func (v *ExtensionsValidator) validateData(data json.RawMessage, schema spec.Schema) error { - if xRules, ok := schema.Extensions.GetStringSlice(v.name); ok { - for _, rule := range xRules { - xValidator, ok := v.validators[rule] + if rules, ok := schema.Extensions.GetStringSlice(v.name); ok { + for _, rule := range rules { + validator, ok := v.validators[rule] if !ok { continue } - err := xValidator(data) + err := validator(data) if err != nil { return err } @@ -102,9 +102,9 @@ func (v *ExtensionsValidator) validateData(data json.RawMessage, schema spec.Sch } if schema.SchemaProps.Items != nil && schema.SchemaProps.Items.Schema != nil { - if xRules, ok := schema.SchemaProps.Items.Schema.Extensions.GetStringSlice(v.name); ok { - for _, rule := range xRules { - xValidator, ok := v.validators[rule] + if rules, ok := schema.SchemaProps.Items.Schema.Extensions.GetStringSlice(v.name); ok { + for _, rule := range rules { + validator, ok := v.validators[rule] if !ok { continue } @@ -121,7 +121,7 @@ func (v *ExtensionsValidator) validateData(data json.RawMessage, schema spec.Sch } for _, item := range items { - err = xValidator(item) + err = validator(item) if err != nil { return err } diff --git a/pkg/yaml/validation/schema.go b/pkg/yaml/validation/index.go similarity index 100% rename from pkg/yaml/validation/schema.go rename to pkg/yaml/validation/index.go diff --git a/pkg/yaml/validation/transformer/additional-properties.go b/pkg/yaml/validation/transformer/additional_properties.go similarity index 100% rename from pkg/yaml/validation/transformer/additional-properties.go rename to pkg/yaml/validation/transformer/additional_properties.go diff --git a/pkg/yaml/validation/validator.go b/pkg/yaml/validation/validator.go index 49587b6..fa55f3c 100644 --- a/pkg/yaml/validation/validator.go +++ b/pkg/yaml/validation/validator.go @@ -31,10 +31,6 @@ import ( "sigs.k8s.io/yaml" ) -var ( - ErrSchemaNotFound = fmt.Errorf("schema not found") -) - type validateOptions struct { omitDocInError bool strictUnmarshal bool @@ -63,9 +59,9 @@ func ValidateWithNoPrettyError(v bool) ValidateOption { type PreValidator interface { // Validate - // currentSchema can be nil - // if validator does not provide our own schema please return currentSchema - Validate(doc []byte, currentSchema *spec.Schema, logger log.Logger) (*spec.Schema, error) + // if validator does not provide our own schema please return nil + // will use schema getting for index + Validate(doc []byte, logger log.Logger) (*spec.Schema, error) } type Validator struct { @@ -160,7 +156,7 @@ func (v *Validator) Validate(doc *[]byte, opts ...ValidateOption) (*SchemaIndex, err := yaml.Unmarshal(*doc, &index) if err != nil { - return nil, fmt.Errorf("Schema index unmarshal failed: %w", err) + return nil, fmt.Errorf("%w %w: schema index unmarshal failed: %w", ErrKindValidationFailed, ErrKindInvalidYAML, err) } err = v.ValidateWithIndex(&index, doc, opts...) @@ -173,8 +169,8 @@ func (v *Validator) Validate(doc *[]byte, opts ...ValidateOption) (*SchemaIndex, func (v *Validator) ValidateWithIndex(index *SchemaIndex, doc *[]byte, opts ...ValidateOption) error { if !index.IsValid() { return fmt.Errorf( - "document must contain \"kind\" and \"apiVersion\" fields:\n\tapiVersion: %s\n\tkind: %s\n\n%s", - index.Version, index.Kind, string(*doc), + "%w: document must contain \"kind\" and \"apiVersion\" fields:\n\tapiVersion: %s\n\tkind: %s\n\n%s", + ErrKindValidationFailed, index.Version, index.Kind, string(*doc), ) } @@ -183,23 +179,18 @@ func (v *Validator) ValidateWithIndex(index *SchemaIndex, doc *[]byte, opts ...V opt(options) } - logger := log.SafeProvideLogger(v.loggerProvider) - docForValidate := *doc - schema := v.getSchemaWithFallback(index, logger) + schema := v.getSchemaWithFallback(index) - preValidator, ok := v.preValidators[*index] - if ok && !govalue.IsNil(preValidator) { - var err error - schema, err = preValidator.Validate(docForValidate, schema, logger) - if err != nil { - return err - } + var err error + schema, err = v.runPreValidation(index, schema, docForValidate) + if err != nil { + return fmt.Errorf("%w: %w", ErrDocumentValidationFailed, err) } if schema == nil { - logger.DebugF("No schema for index %s. Skip it", index.String()) + v.logger().DebugF("No schema for index %s. Skip it", index.String()) // we need return error because on top level we want filter documents without index and move into resources return ErrSchemaNotFound } @@ -209,7 +200,7 @@ func (v *Validator) ValidateWithIndex(index *SchemaIndex, doc *[]byte, opts ...V isValid, err := v.openAPIValidate(&docForValidate, schema, options) if !isValid { if options.omitDocInError || options.noPrettyError { - return fmt.Errorf("%q document validation failed: %w", index.String(), err) + return fmt.Errorf("%q: %w", index.String(), err) } return fmt.Errorf("Document validation failed:\n---\n%s\n\n%w", string(*doc), err) } @@ -219,6 +210,22 @@ func (v *Validator) ValidateWithIndex(index *SchemaIndex, doc *[]byte, opts ...V return nil } +func (v *Validator) runPreValidation(index *SchemaIndex, schema *spec.Schema, doc []byte) (*spec.Schema, error) { + preValidator, ok := v.preValidators[*index] + if ok && !govalue.IsNil(preValidator) { + schemaFromValidator, err := preValidator.Validate(doc, v.logger()) + if err != nil { + return nil, err + } + + if !govalue.IsNil(schemaFromValidator) { + return schemaFromValidator, nil + } + } + + return schema, nil +} + func (v *Validator) addTransformersForSchema(index *SchemaIndex, schema *spec.Schema) *spec.Schema { transformers := v.transformers[*index] if len(transformers) == 0 { @@ -240,7 +247,7 @@ func (v *Validator) addTransformersForSchema(index *SchemaIndex, schema *spec.Sc return schema } -func (v *Validator) getSchemaWithFallback(index *SchemaIndex, logger log.Logger) *spec.Schema { +func (v *Validator) getSchemaWithFallback(index *SchemaIndex) *spec.Schema { schema := v.Get(index) if schema != nil { return schema @@ -248,7 +255,7 @@ func (v *Validator) getSchemaWithFallback(index *SchemaIndex, logger log.Logger) fallback, ok := v.versionFallbacks[index.Version] if !ok || fallback == "" { - logger.DebugF("No fallback schema for version %s", index.Version) + v.logger().DebugF("No fallback schema for version %s", index.Version) return nil } @@ -264,29 +271,31 @@ func (v *Validator) openAPIValidate(dataObj *[]byte, schema *spec.Schema, option dataBytes := *dataObj + unmarshal := yaml.Unmarshal + msg := "json unmarshal" if options.strictUnmarshal { - err := yaml.UnmarshalStrict(dataBytes, &blank) - if err != nil { - return false, fmt.Errorf("openAPIValidate json unmarshal strict: %v", err) - } - } else { - err := yaml.Unmarshal(dataBytes, &blank) - if err != nil { - return false, fmt.Errorf("openAPIValidate json unmarshal: %v", err) - } + unmarshal = yaml.UnmarshalStrict + msg = "json unmarshal strict" + } + + if err := unmarshal(dataBytes, &blank); err != nil { + return false, fmt.Errorf("%w: %s: %w", ErrKindInvalidYAML, msg, err) } result := validator.Validate(blank) if !result.IsValid() { var allErrs *multierror.Error allErrs = multierror.Append(allErrs, result.Errors...) - - return false, allErrs.ErrorOrNil() + var resErr error = ErrDocumentValidationFailed + if err := allErrs.ErrorOrNil(); err != nil { + resErr = fmt.Errorf("%w: %w", resErr, err) + } + return false, resErr } for _, extensionsValidator := range v.extensionsValidators { if err := extensionsValidator.Validate(dataBytes, *schema); err != nil { - return false, err + return false, fmt.Errorf("%w: %w", ErrDocumentValidationFailed, err) } } @@ -296,3 +305,7 @@ func (v *Validator) openAPIValidate(dataObj *[]byte, schema *spec.Schema, option return true, nil } + +func (v *Validator) logger() log.Logger { + return log.SafeProvideLogger(v.loggerProvider) +} diff --git a/pkg/yaml/validation/validator_test.go b/pkg/yaml/validation/validator_test.go index 998c581..c1384e7 100644 --- a/pkg/yaml/validation/validator_test.go +++ b/pkg/yaml/validation/validator_test.go @@ -69,7 +69,7 @@ sshPort: 2200 asserTestKind(t, bytesValidate, expectedTestKind) - asserValidateTestKind(t, validatorTestKind, docPassword, false, expectedTestKind) + asserValidateTestKind(t, validatorTestKind, docPassword, nil, expectedTestKind) docKey := ` apiVersion: deckhouse.io/v1 @@ -79,7 +79,7 @@ sshPort: 2200 sshAgentPrivateKeys: - key: "mykey" ` - asserValidateTestKind(t, validatorTestKind, docKey, false, &testKind{ + asserValidateTestKind(t, validatorTestKind, docKey, nil, &testKind{ SSHUser: "ubuntu", SSHPort: 2200, SSHAgentPrivateKeys: []testPrivateKey{ @@ -96,7 +96,7 @@ sshUser: ubuntu sudoPassword: "no secret" ` - asserValidateTestKind(t, getValidatorTestKind(t), doc, false, &testKind{ + asserValidateTestKind(t, getValidatorTestKind(t), doc, nil, &testKind{ SSHUser: "ubuntu", SudoPassword: "no secret", SSHPort: 22, @@ -111,7 +111,7 @@ key: "mykey" t, getValidatorAnotherTestKind(t), anotherTestKindDoc, - false, + nil, &testAnotherKind{ Key: "mykey", Value: testAnotherKindValue{ @@ -153,7 +153,7 @@ sshPort: 22456 validator := getValidatorTestKind(t) validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, "")) - asserValidateTestKind(t, validator, doc, false, &testKind{ + asserValidateTestKind(t, validator, doc, nil, &testKind{ SSHUser: "ubuntu", SudoPassword: "no secret", SSHPort: 22456, @@ -171,7 +171,7 @@ sshPort: 23 validator := getValidatorTestKind(t) validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, "")) - asserValidateTestKind(t, validator, doc, true, nil) + asserValidateTestKind(t, validator, doc, ErrDocumentValidationFailed, nil) }) t.Run("our schema valid", func(t *testing.T) { @@ -187,7 +187,7 @@ sshPort: 22456 validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, testSchemaTestKind)) - asserValidateTestKind(t, validator, doc, false, &testKind{ + asserValidateTestKind(t, validator, doc, nil, &testKind{ SSHUser: "ubuntu", SudoPassword: "no secret", SSHPort: 22456, @@ -207,7 +207,7 @@ sshPort: 22456 validator.AddPreValidator(indexTestKind, newTestKindPreValidator(t, testSchemaAnotherTestKind)) - asserValidateTestKind(t, validator, doc, true, nil) + asserValidateTestKind(t, validator, doc, ErrDocumentValidationFailed, nil) }) }) @@ -283,12 +283,14 @@ sshPort: 22456 name string doc string errSubstring string + errKind error opts []ValidateOption }{ { name: "invalid yaml", doc: `{invalid`, errSubstring: "error converting YAML to JSON: yaml: line 1", + errKind: ErrKindInvalidYAML, }, { name: "no schema fields", @@ -297,6 +299,7 @@ sshUser: ubuntu sudoPassword: "no secret" sshPort: 22456 `, + errKind: ErrKindValidationFailed, errSubstring: `document must contain "kind" and "apiVersion"`, }, { @@ -308,6 +311,7 @@ sshUser: ubuntu sudoPassword: "no secret" sshPort: 22456 `, + errKind: ErrSchemaNotFound, errSubstring: ErrSchemaNotFound.Error(), }, { @@ -319,6 +323,7 @@ sshUser: ubuntu sshPort: "port" sshAgentPrivateKeys: {"a": "b"} `, + errKind: ErrDocumentValidationFailed, errSubstring: "Document validation failed:\n---", }, { @@ -330,14 +335,22 @@ sshUser: ubuntu sshPort: "port" sshAgentPrivateKeys: {"a": "b"} `, - errSubstring: `"TestKind, deckhouse.io/v1" document validation failed:`, + errKind: ErrDocumentValidationFailed, + errSubstring: fmt.Sprintf(`"TestKind, deckhouse.io/v1": %s:`, ErrDocumentValidationFailed.Error()), opts: []ValidateOption{ValidateWithNoPrettyError(true)}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - asserNoValidateTestKind(t, getValidatorTestKind(t), test.doc, test.errSubstring, test.opts...) + asserNoValidateTestKind( + t, + getValidatorTestKind(t), + test.doc, + test.errKind, + test.errSubstring, + test.opts..., + ) }) } }) @@ -365,7 +378,7 @@ func TestMultipleSchemasValidator(t *testing.T) { tests := []struct { name string doc string - shouldError bool + shouldError error }{ { name: "test kind validate", @@ -376,7 +389,7 @@ sshUser: ubuntu sudoPassword: "no secret" sshPort: 2200 `, - shouldError: false, + shouldError: nil, }, { @@ -389,7 +402,7 @@ value: valueEnum: "OpenStack" valueBool: false `, - shouldError: false, + shouldError: nil, }, { @@ -400,7 +413,7 @@ kind: MyKind key: "key" value: 1 `, - shouldError: true, + shouldError: ErrSchemaNotFound, }, } @@ -409,12 +422,13 @@ value: 1 bytes := []byte(test.doc) _, err := validator.Validate(&bytes) - assertError := require.NoError - if test.shouldError { - assertError = require.Error + if test.shouldError == nil { + require.NoError(t, err, "should valid") + return } - assertError(t, err) + require.Error(t, err, "should not valid") + require.ErrorIs(t, err, test.shouldError, "should return valid error") }) } } @@ -552,7 +566,7 @@ func newTestKindPreValidator(t *testing.T, schema string) *testKindPreValidator return r } -func (p *testKindPreValidator) Validate(doc []byte, currentSchema *spec.Schema, _ log.Logger) (*spec.Schema, error) { +func (p *testKindPreValidator) Validate(doc []byte, _ log.Logger) (*spec.Schema, error) { v := testKind{} err := yaml.Unmarshal(doc, &v) if err != nil { @@ -560,10 +574,7 @@ func (p *testKindPreValidator) Validate(doc []byte, currentSchema *spec.Schema, } if v.SSHPort >= 22000 && v.SSHPort < 30000 { - if currentSchema == nil { - return p.mySchema, nil - } - return currentSchema, nil + return p.mySchema, nil } return nil, fmt.Errorf("invalid SSH port: %d", v.SSHPort) @@ -584,7 +595,7 @@ func asserTestKind(t *testing.T, data []byte, expected *testKind) { require.Equal(t, expected.SudoPassword, result.SudoPassword, "invalid sudo password") } -func doValidate(t *testing.T, validator *Validator, expectedIndex SchemaIndex, doc string, shouldError bool, opts ...ValidateOption) []byte { +func doValidate(t *testing.T, validator *Validator, expectedIndex SchemaIndex, doc string, shouldError error, opts ...ValidateOption) []byte { index := expectedIndex if len(opts) == 0 { @@ -594,8 +605,9 @@ func doValidate(t *testing.T, validator *Validator, expectedIndex SchemaIndex, d bytesForError := []byte(doc) bytesValidateWithIndex := []byte(doc) err := validator.ValidateWithIndex(&index, &bytesValidateWithIndex, opts...) - if shouldError { + if shouldError != nil { require.Error(t, err, "should validation error for test another kind") + require.ErrorIs(t, err, shouldError, "should returns valid error") require.Equal(t, bytesForError, bytesValidateWithIndex, "should not change input") return nil } @@ -607,9 +619,9 @@ func doValidate(t *testing.T, validator *Validator, expectedIndex SchemaIndex, d return bytesValidateWithIndex } -func asserValidateAnotherTestKind(t *testing.T, validator *Validator, doc string, shouldError bool, expected *testAnotherKind, opts ...ValidateOption) []byte { +func asserValidateAnotherTestKind(t *testing.T, validator *Validator, doc string, shouldError error, expected *testAnotherKind, opts ...ValidateOption) []byte { bytes := doValidate(t, validator, indexAnotherTestKind, doc, shouldError, opts...) - if shouldError { + if shouldError != nil { return nil } @@ -632,23 +644,24 @@ func asserTestKindIndex(t *testing.T, index SchemaIndex) { require.Equal(t, indexTestKind, index, "invalid index value") } -func asserValidateTestKind(t *testing.T, validator *Validator, doc string, shouldError bool, expected *testKind, opts ...ValidateOption) { +func asserValidateTestKind(t *testing.T, validator *Validator, doc string, shouldError error, expected *testKind, opts ...ValidateOption) { bytes := doValidate(t, validator, indexTestKind, doc, shouldError, opts...) - if shouldError { + if shouldError != nil { return } asserTestKind(t, bytes, expected) } -func asserNoValidateTestKind(t *testing.T, validator *Validator, doc string, errorSubstring string, opts ...ValidateOption) { +func asserNoValidateTestKind(t *testing.T, validator *Validator, doc string, errorKind error, errorSubstring string, opts ...ValidateOption) { bytesForError := []byte(doc) bytesValidateWithIndex := []byte(doc) _, err := validator.Validate(&bytesValidateWithIndex, opts...) require.Error(t, err, "should not validate") - require.Equal(t, bytesForError, bytesValidateWithIndex, "should not change input") + require.ErrorIs(t, err, errorKind, "should returns valid error") require.Contains(t, err.Error(), errorSubstring) + require.Equal(t, bytesForError, bytesValidateWithIndex, "should not change input") } func assertValidationWithTransformers(t *testing.T, validator *Validator, shouldError bool) { @@ -661,7 +674,12 @@ value: valueEnum: "OpenStack" valueBool: true ` - bytes := asserValidateAnotherTestKind(t, validator, doc, shouldError, &testAnotherKind{ + var expectedErr error + if shouldError { + expectedErr = ErrDocumentValidationFailed + } + + bytes := asserValidateAnotherTestKind(t, validator, doc, expectedErr, &testAnotherKind{ Key: "mykey", Value: testAnotherKindValue{ ValueBool: true, @@ -712,7 +730,12 @@ sshAgentPrivateKeys: passphrase: %s `, keyPassword) - asserValidateTestKind(t, validator, doc, shouldError, &testKind{ + var expectedErr error + if shouldError { + expectedErr = ErrDocumentValidationFailed + } + + asserValidateTestKind(t, validator, doc, expectedErr, &testKind{ SSHUser: "ubuntu", SSHPort: 2200, SSHAgentPrivateKeys: []testPrivateKey{ From 2514d784399a73d7ad3ffdf2624feb161d45f2e8 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Fri, 16 Jan 2026 20:16:00 +0300 Subject: [PATCH 11/11] Add yaml validator Signed-off-by: Nikolay Mitrofanov --- pkg/yaml/validation/validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/yaml/validation/validator.go b/pkg/yaml/validation/validator.go index fa55f3c..facc9c9 100644 --- a/pkg/yaml/validation/validator.go +++ b/pkg/yaml/validation/validator.go @@ -264,7 +264,7 @@ func (v *Validator) getSchemaWithFallback(index *SchemaIndex) *spec.Schema { return v.Get(index) } -func (v *Validator) openAPIValidate(dataObj *[]byte, schema *spec.Schema, options *validateOptions) (isValid bool, multiErr error) { +func (v *Validator) openAPIValidate(dataObj *[]byte, schema *spec.Schema, options *validateOptions) (bool, error) { validator := validate.NewSchemaValidator(schema, nil, "", strfmt.Default) var blank map[string]interface{}