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..3655b97 100644 --- a/go.mod +++ b/go.mod @@ -4,24 +4,49 @@ 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 ( 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/swag v0.19.9 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // 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.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 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.v3 v3.0.1 // indirect + gopkg.in/yaml.v2 v2.3.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 ece97f5..9b8a71f 100644 --- a/go.sum +++ b/go.sum @@ -1,46 +1,259 @@ +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/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= 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.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/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.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.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.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.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.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +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.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.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.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.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +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-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= +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/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= +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/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= +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/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/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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +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 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= +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= +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.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= 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.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +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-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-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-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= 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-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.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= -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/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/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/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/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) + }) +} diff --git a/pkg/yaml/validation/error.go b/pkg/yaml/validation/error.go new file mode 100644 index 0000000..7270c47 --- /dev/null +++ b/pkg/yaml/validation/error.go @@ -0,0 +1,130 @@ +// 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 + ErrDocumentValidationFailed + ErrSchemaNotFound +) + +func (k ErrorKind) Error() string { + return k.String() +} + +func (k ErrorKind) String() string { + switch k { + case ErrKindChangesValidationFailed: + return "ChangesValidationFailed" + case ErrKindValidationFailed: + return "DocumentKindValidationFailed" + case ErrKindInvalidYAML: + return "InvalidYAML" + case ErrDocumentValidationFailed: + return "DocumentValidationFailed" + case ErrSchemaNotFound: + return "SchemaNotFound" + 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..8cbd4c3 --- /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 rules, ok := schema.Extensions.GetStringSlice(v.name); ok { + for _, rule := range rules { + validator, ok := v.validators[rule] + if !ok { + continue + } + + err := validator(data) + if err != nil { + return err + } + } + } + + if schema.SchemaProps.Items != nil && schema.SchemaProps.Items.Schema != nil { + if rules, ok := schema.SchemaProps.Items.Schema.Extensions.GetStringSlice(v.name); ok { + for _, rule := range rules { + validator, 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 = validator(item) + if err != nil { + return err + } + } + } + } + } + + return nil +} diff --git a/pkg/yaml/validation/index.go b/pkg/yaml/validation/index.go new file mode 100644 index 0000000..d0f6699 --- /dev/null +++ b/pkg/yaml/validation/index.go @@ -0,0 +1,32 @@ +// 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" +) + +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) +} diff --git a/pkg/yaml/validation/schema_loader.go b/pkg/yaml/validation/schema_loader.go new file mode 100644 index 0000000..e5b7867 --- /dev/null +++ b/pkg/yaml/validation/schema_loader.go @@ -0,0 +1,88 @@ +// 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/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..facc9c9 --- /dev/null +++ b/pkg/yaml/validation/validator.go @@ -0,0 +1,311 @@ +// 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" +) + +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 + // 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 { + 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("%w %w: schema index unmarshal failed: %w", ErrKindValidationFailed, ErrKindInvalidYAML, 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 { + if !index.IsValid() { + return fmt.Errorf( + "%w: document must contain \"kind\" and \"apiVersion\" fields:\n\tapiVersion: %s\n\tkind: %s\n\n%s", + ErrKindValidationFailed, index.Version, index.Kind, string(*doc), + ) + } + + options := &validateOptions{} + for _, opt := range opts { + opt(options) + } + + docForValidate := *doc + + schema := v.getSchemaWithFallback(index) + + var err error + schema, err = v.runPreValidation(index, schema, docForValidate) + if err != nil { + return fmt.Errorf("%w: %w", ErrDocumentValidationFailed, err) + } + + if schema == nil { + 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 + } + + schema = v.addTransformersForSchema(index, schema) + + isValid, err := v.openAPIValidate(&docForValidate, schema, options) + if !isValid { + if options.omitDocInError || options.noPrettyError { + return fmt.Errorf("%q: %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) 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 { + 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) *spec.Schema { + schema := v.Get(index) + if schema != nil { + return schema + } + + fallback, ok := v.versionFallbacks[index.Version] + if !ok || fallback == "" { + v.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) (bool, error) { + validator := validate.NewSchemaValidator(schema, nil, "", strfmt.Default) + + var blank map[string]interface{} + + dataBytes := *dataObj + + unmarshal := yaml.Unmarshal + msg := "json unmarshal" + if options.strictUnmarshal { + 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...) + 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, fmt.Errorf("%w: %w", ErrDocumentValidationFailed, err) + } + } + + // Add default values from openAPISpec + post.ApplyDefaults(result) + *dataObj, _ = json.Marshal(result.Data()) + + 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 new file mode 100644 index 0000000..c1384e7 --- /dev/null +++ b/pkg/yaml/validation/validator_test.go @@ -0,0 +1,748 @@ +// 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" + "strings" + "testing" + + "github.com/go-openapi/spec" + "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" + + "github.com/deckhouse/lib-dhctl/pkg/log" + "github.com/deckhouse/lib-dhctl/pkg/yaml/validation/transformer" +) + +func TestOneSchemaValidator(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, nil, expectedTestKind) + + docKey := ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sshPort: 2200 +sshAgentPrivateKeys: +- key: "mykey" +` + asserValidateTestKind(t, validatorTestKind, docKey, nil, &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, nil, &testKind{ + SSHUser: "ubuntu", + SudoPassword: "no secret", + SSHPort: 22, + }) + + anotherTestKindDoc := ` +apiVersion: test +kind: AnotherTestKind +key: "mykey" +` + asserValidateAnotherTestKind( + t, + getValidatorAnotherTestKind(t), + anotherTestKindDoc, + nil, + &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, nil, &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, ErrDocumentValidationFailed, 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, nil, &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, ErrDocumentValidationFailed, 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) + }) + }) + + 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 + errKind error + opts []ValidateOption + }{ + { + name: "invalid yaml", + doc: `{invalid`, + errSubstring: "error converting YAML to JSON: yaml: line 1", + errKind: ErrKindInvalidYAML, + }, + { + name: "no schema fields", + doc: ` +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 22456 +`, + errKind: ErrKindValidationFailed, + errSubstring: `document must contain "kind" and "apiVersion"`, + }, + { + name: "no schema found", + doc: ` +apiVersion: some +kind: MyKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 22456 +`, + errKind: ErrSchemaNotFound, + errSubstring: ErrSchemaNotFound.Error(), + }, + { + name: "not valid by schema", + doc: ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sshPort: "port" +sshAgentPrivateKeys: {"a": "b"} +`, + errKind: ErrDocumentValidationFailed, + 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"} +`, + 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.errKind, + 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 + } + + 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 error + }{ + { + name: "test kind validate", + doc: ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sudoPassword: "no secret" +sshPort: 2200 +`, + shouldError: nil, + }, + + { + name: "another test kind validate", + doc: ` +apiVersion: test +kind: AnotherTestKind +key: "mykey" +value: + valueEnum: "OpenStack" + valueBool: false +`, + shouldError: nil, + }, + + { + name: "not present schema", + doc: ` +apiVersion: my +kind: MyKind +key: "key" +value: 1 +`, + shouldError: ErrSchemaNotFound, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + bytes := []byte(test.doc) + _, err := validator.Validate(&bytes) + + if test.shouldError == nil { + require.NoError(t, err, "should valid") + return + } + + require.Error(t, err, "should not valid") + require.ErrorIs(t, err, test.shouldError, "should return valid error") + }) + } +} + +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( + 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, _ 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 { + return p.mySchema, 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 error, opts ...ValidateOption) []byte { + index := expectedIndex + + if len(opts) == 0 { + opts = []ValidateOption{ValidateWithNoPrettyError(true)} + } + + bytesForError := []byte(doc) + bytesValidateWithIndex := []byte(doc) + err := validator.ValidateWithIndex(&index, &bytesValidateWithIndex, opts...) + 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 + } + 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 error, expected *testAnotherKind, opts ...ValidateOption) []byte { + bytes := doValidate(t, validator, indexAnotherTestKind, doc, shouldError, opts...) + if shouldError != nil { + 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 error, expected *testKind, opts ...ValidateOption) { + bytes := doValidate(t, validator, indexTestKind, doc, shouldError, opts...) + if shouldError != nil { + return + } + asserTestKind(t, bytes, expected) +} + +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.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) { + doc := ` +apiVersion: test +kind: AnotherTestKind +key: "mykey" +value: + additionalPropertyInvalidWithTransformer: "invalid" + valueEnum: "OpenStack" + valueBool: true +` + var expectedErr error + if shouldError { + expectedErr = ErrDocumentValidationFailed + } + + bytes := asserValidateAnotherTestKind(t, validator, doc, expectedErr, &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") +} + +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) + + var expectedErr error + if shouldError { + expectedErr = ErrDocumentValidationFailed + } + + asserValidateTestKind(t, validator, doc, expectedErr, &testKind{ + SSHUser: "ubuntu", + SSHPort: 2200, + SSHAgentPrivateKeys: []testPrivateKey{ + { + Key: "mykey", + Passphrase: strings.Trim(keyPassword, `"`), + }, + }, + }) +}