From d2ee78722d4a9353000c1666e817e9c0b51cc951 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Sat, 31 Jan 2026 23:15:25 +0300 Subject: [PATCH 1/5] add kube flags Signed-off-by: Nikolay Mitrofanov --- Makefile | 13 ++ go.mod | 35 ++++-- go.sum | 110 +++++++++++++++-- pkg/{init.go => kube/config.go} | 14 ++- pkg/kube/parse_flags.go | 192 +++++++++++++++++++++++++++++ pkg/kube/parse_flags_test.go | 46 +++++++ pkg/ssh/config/parse_flags.go | 154 +++++++---------------- pkg/ssh/config/parse_flags_test.go | 186 ++++------------------------ pkg/tests/parse_flags.go | 192 +++++++++++++++++++++++++++++ pkg/utils/env/extractor.go | 8 +- pkg/utils/file/reader.go | 58 +++++++++ pkg/utils/flags/base_flags.go | 83 +++++++++++++ pkg/utils/flags/base_parser.go | 65 ++++++++++ tests/go.mod | 19 ++- tests/go.sum | 43 ++++--- 15 files changed, 899 insertions(+), 319 deletions(-) rename pkg/{init.go => kube/config.go} (73%) create mode 100644 pkg/kube/parse_flags.go create mode 100644 pkg/kube/parse_flags_test.go create mode 100644 pkg/tests/parse_flags.go create mode 100644 pkg/utils/file/reader.go create mode 100644 pkg/utils/flags/base_flags.go create mode 100644 pkg/utils/flags/base_parser.go diff --git a/Makefile b/Makefile index 15a602e..cc3e602 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,19 @@ bin/kind: curl-installed bin deps: bin bin/jq bin/golangci-lint bin/gofumpt bin/kind +go-deps/update: go-installed + if [ -z "$(DEP)" ] ; then \ + echo "Please pass dependency name over DEP like make go-deps/update DEP=github.com/some/lib VER=v1.1.1"; \ + exit 1; \ + fi; \ + if [ -z "$(VER)" ] ; then \ + echo "Please pass dependency version over VER like make go-deps/update DEP=github.com/some/lib VER=v1.1.1"; \ + exit 1; \ + fi; \ + go get "$(DEP)@$(VER)"; \ + cd ./tests; \ + go get "$(DEP)@$(VER)" + test: go-installed docker-installed bin/kind ./hack/run_tests.sh $(MAKE) clean/test diff --git a/go.mod b/go.mod index d7ae66a..835a7a6 100644 --- a/go.mod +++ b/go.mod @@ -15,42 +15,57 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 golang.org/x/term v0.39.0 + k8s.io/client-go v0.32.10 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deckhouse/deckhouse/pkg/log v0.1.1-0.20251230144142-2bad7c3d1edf // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // 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/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // 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/swag v0.23.0 // indirect github.com/go-openapi/validate v0.19.12 // indirect github.com/go-stack/stack v1.8.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/gookit/color v1.5.2 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/mailru/easyjson v0.7.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/mapstructure v1.3.2 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/werf/logboek v0.5.5 // indirect + github.com/x448/float16 v0.8.4 // 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.47.0 // indirect golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + golang.org/x/time v0.7.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apimachinery v0.32.10 // indirect k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect ) diff --git a/go.sum b/go.sum index 8412889..c801d09 100644 --- a/go.sum +++ b/go.sum @@ -4,9 +4,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 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= @@ -22,8 +20,9 @@ github.com/bramvdbogaerde/go-scp v1.6.0 h1:lDh0lUuz1dbIhJqlKLwWT7tzIRONCp1Mtx3pg github.com/bramvdbogaerde/go-scp v1.6.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/deckhouse/lib-dhctl v0.13.0 h1:Xh7G30seic4Aa8MdXOH2ecGvO0zsn6PBGBHbJ4YNnZM= @@ -32,10 +31,14 @@ github.com/deckhouse/lib-gossh v0.3.0 h1:FUAlF8+fLnBCII9hXSNx+arZ4PH3H/6fzp5LBln github.com/deckhouse/lib-gossh v0.3.0/go.mod h1:6bT8jf2fkBPEhYBU35+vMBr5YscliTiS+Vr8v06C+70= 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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 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-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/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= @@ -54,13 +57,16 @@ github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpX 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/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 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/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/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 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= @@ -94,8 +100,10 @@ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ 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-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 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= @@ -129,12 +137,22 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe 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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -151,13 +169,22 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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= @@ -167,17 +194,24 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN 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/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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/name212/govalue v1.1.0 h1:kSdUVs21cM5bFp7RW5sWPrwQ0RzC/Xhk3f+A+dUL6TM= github.com/name212/govalue v1.1.0/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= @@ -186,11 +220,14 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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= @@ -203,6 +240,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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= @@ -210,6 +248,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= @@ -217,6 +256,8 @@ 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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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= @@ -225,6 +266,8 @@ github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHM 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= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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= @@ -242,24 +285,33 @@ 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20190620200207-3b0461eec859/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.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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/sync v0.0.0-20201020160332-67f06af15bc9/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= @@ -270,6 +322,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= @@ -277,9 +330,12 @@ golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= @@ -289,12 +345,25 @@ 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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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= @@ -305,7 +374,22 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C 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/api v0.32.10 h1:ocp4turNfa1V40TuBW/LuA17TeXG9g/GI2ebg0KxBNk= +k8s.io/api v0.32.10/go.mod h1:AsMsc4b6TuampYqgMEGSv0HBFpRS4BlKTXAVCAa7oF4= +k8s.io/apimachinery v0.32.10 h1:SAg2kUPLYRcBJQj66oniP1BnXSqw+l1GvJFsJlBmVvQ= +k8s.io/apimachinery v0.32.10/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.10 h1:MFmIjsKtcnn7mStjrJG1ZW2WzLsKKn6ZtL9hHM/W0xU= +k8s.io/client-go v0.32.10/go.mod h1:qJy/Ws3zSwnu/nD75D+/of1uxbwWHxrYT5P3FuobVLI= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 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/init.go b/pkg/kube/config.go similarity index 73% rename from pkg/init.go rename to pkg/kube/config.go index 3a3f440..5a69fea 100644 --- a/pkg/init.go +++ b/pkg/kube/config.go @@ -1,4 +1,4 @@ -// Copyright 2025 Flant JSC +// 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. @@ -12,4 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package pkg +package kube + +import "k8s.io/client-go/rest" + +type Config struct { + KubeConfig string + KubeConfigContext string + KubeConfigInCluster bool + + RestConfig *rest.Config +} diff --git a/pkg/kube/parse_flags.go b/pkg/kube/parse_flags.go new file mode 100644 index 0000000..5d32013 --- /dev/null +++ b/pkg/kube/parse_flags.go @@ -0,0 +1,192 @@ +// 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 kube + +import ( + "fmt" + + flag "github.com/spf13/pflag" + "k8s.io/client-go/tools/clientcmd" + + "github.com/deckhouse/lib-connection/pkg/settings" + "github.com/deckhouse/lib-connection/pkg/utils/env" + "github.com/deckhouse/lib-connection/pkg/utils/file" + baseflags "github.com/deckhouse/lib-connection/pkg/utils/flags" +) + +const ( + ConfigEnv = "KUBE_CONFIG" + ConfigContextEnv = "KUBE_CONFIG_CONTEXT" + ClientFromClusterEnv = "KUBE_CLIENT_FROM_CLUSTER" +) + +const ( + kubeConfigFlag = "kubeconfig" + kubeConfigContextFlag = "kubeconfig-context" + clientFromClusterFlag = "kube-client-from-cluster" +) + +type Flags struct { + Config + + baseFlags *baseflags.BaseFlags +} + +func (f *Flags) IsConflictBetweenFlags() error { + envsExtractor, err := f.baseFlags.ShouldEnvExtractor() + if err != nil { + return err + } + + if f.KubeConfig != "" && f.KubeConfigInCluster { + return fmt.Errorf( + "Cannot use both --%s and --%s or envs %s and %s at the same time", + kubeConfigFlag, + clientFromClusterFlag, + envsExtractor.NameWithPrefix(ConfigEnv), + envsExtractor.NameWithPrefix(ClientFromClusterEnv), + ) + } + + return nil +} + +func (f *Flags) RewriteFromEnvs() error { + envExtractor, err := f.baseFlags.ShouldEnvExtractor() + if err != nil { + return err + } + + err = envExtractor.ExtractAllVars( + env.NewVar(ConfigEnv, &f.KubeConfig), + env.NewVar(ConfigContextEnv, &f.KubeConfigContext), + env.NewVar(ClientFromClusterEnv, &f.KubeConfigInCluster), + ) + + if err != nil { + return err + } + + if f.KubeConfig != "" || f.KubeConfigInCluster { + return nil + } + + envExtractor.StringWithoutPrefix("KUBECONFIG", &f.KubeConfig) + + return nil +} + +type FlagsParser struct { + *baseflags.BaseParser +} + +// NewFlagsParser +// init FlagsParser +// prefix will trim right all _ ang - symbols and spaces left and right from settings.Settings EnvsPrefix +// By default parser add _ after prefix for all env vars +func NewFlagsParser(sett settings.Settings) *FlagsParser { + return &FlagsParser{ + BaseParser: baseflags.NewBaseParser(sett), + } +} + +// InitFlags +// init flag.FlagSet and return struct with flags where flag.FlagSet parsed +// should call before flag.Parse or flag.FlagSet.Parse +// if set is parsed returns error +func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { + if set.Parsed() { + return nil, fmt.Errorf("Flags already parsed") + } + + envsExtractor := p.NewEnvsExtractor() + + flags := &Flags{ + baseFlags: baseflags.NewBaseFlags(set, envsExtractor), + } + + set.StringVar( + &flags.KubeConfig, + kubeConfigFlag, + "", + envsExtractor.AddEnvToUsage( + "Path to kubernetes config file.", + ConfigContextEnv, + ), + ) + + set.StringVar( + &flags.KubeConfig, + kubeConfigContextFlag, + "", + envsExtractor.AddEnvToUsage( + "Context from kubernetes config to connect to Kubernetes API.", + ConfigContextEnv, + ), + ) + + set.BoolVar( + &flags.KubeConfigInCluster, + clientFromClusterFlag, + false, + envsExtractor.AddEnvToUsage( + "Use in-cluster Kubernetes API access.", + ClientFromClusterEnv, + ), + ) + + return flags, nil +} + +// ExtractConfigAfterParse +// extract ConnectionConfig from flags +// should call after InitFlags and flag.Parse or flag.FlagSet.Parse +// if flag.FlagSet in Flags is not parse returns error +func (p *FlagsParser) ExtractConfigAfterParse(flags *Flags) (*Config, error) { + if err := flags.baseFlags.IsInitialized(); err != nil { + return nil, err + } + + if err := flags.RewriteFromEnvs(); err != nil { + return nil, err + } + + if err := flags.IsConflictBetweenFlags(); err != nil { + return nil, err + } + + sett := p.Settings() + logger := sett.Logger() + + kubeConfigFile := flags.KubeConfig + + if kubeConfigFile != "" { + content, err := file.ReadFile(kubeConfigFile, "kube config", logger) + if err != nil { + return nil, err + } + + _, err = clientcmd.Load(content) + if err != nil { + return nil, fmt.Errorf("Cannot parse kube config file '%s': %w", kubeConfigFile, err) + } + } + + return &Config{ + KubeConfig: kubeConfigFile, + KubeConfigContext: flags.KubeConfigContext, + KubeConfigInCluster: flags.KubeConfigInCluster, + }, nil +} diff --git a/pkg/kube/parse_flags_test.go b/pkg/kube/parse_flags_test.go new file mode 100644 index 0000000..88cf146 --- /dev/null +++ b/pkg/kube/parse_flags_test.go @@ -0,0 +1,46 @@ +// 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 kube + +import ( + "testing" + + flag "github.com/spf13/pflag" + + "github.com/deckhouse/lib-connection/pkg/settings" + "github.com/deckhouse/lib-connection/pkg/tests" +) + +func TestParseFlagsHelp(t *testing.T) { + tests.AssertParseFlagsHelp(t, tests.AssertParseFlagsHelpParams{ + ExpectedFlags: 3, + Name: "kube-flags", + Provider: func(sett settings.Settings, envsPrefix string) tests.TestFlagsParser { + parser := NewFlagsParser(sett) + parser.WithEnvsPrefix(envsPrefix) + + return &testHelpParser{parser: parser} + }, + }) +} + +type testHelpParser struct { + parser *FlagsParser +} + +func (p *testHelpParser) InitFlags(flagSet *flag.FlagSet) error { + _, err := p.parser.InitFlags(flagSet) + return err +} diff --git a/pkg/ssh/config/parse_flags.go b/pkg/ssh/config/parse_flags.go index 36280bf..342819d 100644 --- a/pkg/ssh/config/parse_flags.go +++ b/pkg/ssh/config/parse_flags.go @@ -16,7 +16,6 @@ package config import ( "fmt" - "io" "os" "path/filepath" "sort" @@ -32,6 +31,8 @@ import ( "github.com/deckhouse/lib-connection/pkg/ssh/utils/terminal" "github.com/deckhouse/lib-connection/pkg/utils/defaults" "github.com/deckhouse/lib-connection/pkg/utils/env" + "github.com/deckhouse/lib-connection/pkg/utils/file" + baseflags "github.com/deckhouse/lib-connection/pkg/utils/flags" ) const ( @@ -83,8 +84,7 @@ type Flags struct { forceNoPrivateKeys bool - flagSet *flag.FlagSet - envExtractor *env.Extractor + baseFlags *baseflags.BaseFlags } func (f *Flags) IsConflictBetweenFlags() error { @@ -106,7 +106,7 @@ func (f *Flags) IsConflictBetweenFlags() error { func (f *Flags) FillDefaults() error { if len(f.PrivateKeysPaths) == 0 && !f.forceNoPrivateKeys { - home, err := defaults.HomeDir(f.envExtractor) + home, err := defaults.HomeDir(f.baseFlags.EnvExtractor()) if err != nil { return err } @@ -148,13 +148,14 @@ func (f *Flags) FillDefaults() error { } func (f *Flags) RewriteFromEnvs() error { - if govalue.Nil(f.envExtractor) { - return notInitializedError("envExtractor") + envExtractor, err := f.baseFlags.ShouldEnvExtractor() + if err != nil { + return err } privateKeysVal := env.NewVar(AgentPrivateKeysEnv, &f.PrivateKeysPaths) - err := f.envExtractor.ExtractAllVars( + err = envExtractor.ExtractAllVars( env.NewVar(BastionPortEnv, &f.BastionPort), env.NewVar(PortEnv, &f.Port), env.NewVar(ForceNoPrivateKeysEnv, &f.forceNoPrivateKeys), @@ -184,29 +185,6 @@ func (f *Flags) RewriteFromEnvs() error { return nil } -func notInitializedError(field string) error { - return fmt.Errorf( - "Internal error. %s in Flags did not initialize. Call InitFlags first and pass Flags from result of InitFlags", - field, - ) -} - -func (f *Flags) IsInitialized() error { - if govalue.Nil(f.envExtractor) { - return notInitializedError("envExtractor") - } - - if govalue.Nil(f.flagSet) { - return notInitializedError("flagSet") - } - - if !f.flagSet.Parsed() { - return fmt.Errorf("flagsSet is not parsed. Call flag.Parse or flag.FlagSet.Parse before extract config") - } - - return nil -} - func (f *Flags) userExtractor() func() (string, error) { var currentUser *string @@ -215,7 +193,12 @@ func (f *Flags) userExtractor() func() (string, error) { return *currentUser, nil } - userName, err := defaults.CurrentUserName(f.envExtractor) + envExtractor, err := f.baseFlags.ShouldEnvExtractor() + if err != nil { + return "", err + } + + userName, err := defaults.CurrentUserName(envExtractor) if err != nil { return "", err } @@ -232,10 +215,9 @@ type ( ) type FlagsParser struct { - envsPrefix string - ask AskPasswordFunc - sett settings.Settings - envsLookup env.EnvsLookupFunc + *baseflags.BaseParser + + ask AskPasswordFunc // extractPrivateKey // custom extract content and password for private key file @@ -244,39 +226,29 @@ type FlagsParser struct { } // NewFlagsParser -// init FlagsParser with empty envsPrefix -// trim right all _ ang - symbols and spaces left and right from sett.EnvsPrefix +// init FlagsParser +// prefix will trim right all _ ang - symbols and spaces left and right from settings.Settings EnvsPrefix // By default parser add _ after prefix for all env vars func NewFlagsParser(sett settings.Settings) *FlagsParser { askFromTerminal := func(prompt string) ([]byte, error) { return terminal.AskPassword(sett.Logger(), prompt) } - parser := &FlagsParser{ - sett: sett, - } - terminalPrivateKeyPasswordExtractorWithoutDefault := func(path string, logger log.Logger) (string, error) { return terminalPrivateKeyPasswordExtractor(path, make([]byte, 0), logger) } - return parser.WithEnvsPrefix(sett.EnvsPrefix()). - WithAsk(askFromTerminal). - WithEnvsLookup(os.LookupEnv). - WithPrivateKeyPasswordExtractor(terminalPrivateKeyPasswordExtractorWithoutDefault) -} + parser := &FlagsParser{ + BaseParser: baseflags.NewBaseParser(sett), + } -// WithEnvsPrefix -// This method trim right all _ ang - symbols and spaces left and right -// By default parser add _ after prefix for all env vars -func (p *FlagsParser) WithEnvsPrefix(envsPrefix string) *FlagsParser { - p.envsPrefix = env.SimplifyPrefix(envsPrefix) - return p + return parser.WithAsk(askFromTerminal). + WithPrivateKeyPasswordExtractor(terminalPrivateKeyPasswordExtractorWithoutDefault) } func (p *FlagsParser) WithAsk(ask AskPasswordFunc) *FlagsParser { if govalue.Nil(ask) { - p.sett.Logger().WarnF("Ask function is nil. Skip set ask function.") + p.Settings().Logger().WarnF("Ask function is nil. Skip set ask function.") return p } @@ -284,19 +256,9 @@ func (p *FlagsParser) WithAsk(ask AskPasswordFunc) *FlagsParser { return p } -func (p *FlagsParser) WithEnvsLookup(lookup env.EnvsLookupFunc) *FlagsParser { - if govalue.Nil(lookup) { - p.sett.Logger().WarnF("Envs lookup function is nil. Skip set ask function.") - return p - } - - p.envsLookup = lookup - return p -} - func (p *FlagsParser) WithPrivateKeyPasswordExtractor(extractor PrivateKeyExtractorFunc) *FlagsParser { if govalue.Nil(extractor) { - p.sett.Logger().WarnF("Private key password extractor function is nil. Skip set extractor function.") + p.Settings().Logger().WarnF("Private key password extractor function is nil. Skip set extractor function.") return p } @@ -313,18 +275,17 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { return nil, fmt.Errorf("Flags already parsed") } - extractorFromEnv := p.envsExtractor() + envsExtractor := p.NewEnvsExtractor() flags := &Flags{ - flagSet: set, - envExtractor: extractorFromEnv, + baseFlags: baseflags.NewBaseFlags(set, envsExtractor), } set.StringSliceVar( &flags.PrivateKeysPaths, privateKeysFlag, make([]string, 0), - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "Paths to private keys. Those keys will be used to connect to servers and to the bastion. Can be specified multiple times (default: '~/.ssh/id_rsa').", AgentPrivateKeysEnv, ), @@ -334,7 +295,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.BastionHost, "ssh-bastion-host", "", - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "Jumper (bastion) host to connect to servers (will be used both by infrastructure creation utility and ansible). Only IPs or hostnames are supported, name from ssh-config will not work.", BastionHostEnv, ), @@ -344,7 +305,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.BastionPort, "ssh-bastion-port", 0, - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "SSH bastion port.", BastionPortEnv, ), @@ -354,7 +315,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.BastionUser, "ssh-bastion-user", "", - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "User to authenticate under when connecting to bastion (default: $USER).", BastionUserEnv, ), @@ -364,7 +325,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.Hosts, sshHostsFlag, make([]string, 0), - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "SSH destination hosts, can be specified multiple times.", HostsEnv, ), @@ -374,7 +335,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.User, "ssh-user", "", - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "User to authenticate under (default: $USER).", UserEnv, ), @@ -384,7 +345,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.Port, "ssh-port", 0, - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "SSH destination port.", PortEnv, ), @@ -394,7 +355,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.ExtraArgs, "ssh-extra-args", "", - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "Extra args for ssh commands (like -vvv).", ExtraArgsEnv, ), @@ -404,7 +365,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.ConnectionConfigPath, "connection-config", "", - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "SSH connection config file path.", ConnectionConfigEnv, ), @@ -414,7 +375,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.ForceLegacy, legacyModeFlag, false, - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "Force legacy SSH mode.", LegacyModeEnv, ), @@ -424,7 +385,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.ForceModern, modernModeFlag, false, - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "Force modern SSH mode.", ModernModeEnv, ), @@ -434,7 +395,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.AskBastionPass, "ask-bastion-pass", false, - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "Ask for bastion password before the installation process.", AskBastionPasswordEnv, ), @@ -445,7 +406,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { askSudoPasswordFlag, "K", false, - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "Ask for sudo password before the installation process.", AskSudoPasswordEnv, ), @@ -455,7 +416,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { &flags.forceNoPrivateKeys, forceNoPrivateKeysFlag, false, - extractorFromEnv.AddEnvToUsage( + envsExtractor.AddEnvToUsage( "Do not use private keys.", ForceNoPrivateKeysEnv, ), @@ -469,7 +430,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { // should call after InitFlags and flag.Parse or flag.FlagSet.Parse // if flag.FlagSet in Flags is not parse returns error func (p *FlagsParser) ExtractConfigAfterParse(flags *Flags, opts ...ValidateOption) (*ConnectionConfig, error) { - if err := flags.IsInitialized(); err != nil { + if err := flags.baseFlags.IsInitialized(); err != nil { return nil, err } @@ -481,10 +442,11 @@ func (p *FlagsParser) ExtractConfigAfterParse(flags *Flags, opts ...ValidateOpti return nil, err } - logger := p.sett.Logger() + sett := p.Settings() + logger := sett.Logger() if flags.ConnectionConfigPath != "" { - configReader, err := fileReader(flags.ConnectionConfigPath, "connection config") + configReader, err := file.Reader(flags.ConnectionConfigPath, "connection config") if err != nil { return nil, err } @@ -495,7 +457,7 @@ func (p *FlagsParser) ExtractConfigAfterParse(flags *Flags, opts ...ValidateOpti } }() - return ParseConnectionConfig(configReader, p.sett, opts...) + return ParseConnectionConfig(configReader, sett, opts...) } if err := flags.FillDefaults(); err != nil { @@ -601,10 +563,6 @@ func (p *FlagsParser) ParseFlagsAndExtractConfig(arguments []string, set *flag.F return p.ExtractConfigAfterParse(flags, opts...) } -func (p *FlagsParser) envsExtractor() *env.Extractor { - return env.NewExtractor(p.envsPrefix, p.envsLookup) -} - func (p *FlagsParser) readPrivateKeysFromFlags(flags *Flags, logger log.Logger) ([]AgentPrivateKey, error) { res := make([]AgentPrivateKey, 0, len(flags.PrivateKeysPaths)) @@ -665,24 +623,6 @@ func (p *FlagsParser) getPasswordsFromUser(flags *Flags) (*passwordsFromUser, er return res, nil } -func fileReader(path string, fileType string) (io.ReadCloser, error) { - fullPath, err := filepath.Abs(path) - if err != nil { - return nil, fmt.Errorf("Cannot get abs path for %s: %w", path, err) - } - - stat, err := os.Stat(fullPath) - if err != nil { - return nil, fmt.Errorf("Cannot get %s file info for %s: %w", fileType, fullPath, err) - } - - if stat.IsDir() || !stat.Mode().IsRegular() { - return nil, fmt.Errorf("%s path '%s' should be regular file", fileType, fullPath) - } - - return os.Open(fullPath) -} - func terminalPrivateKeyPasswordExtractor(path string, defaultPassword []byte, logger log.Logger) (string, error) { _, password, err := utils.ParseSSHPrivateKeyFile(path, string(defaultPassword), logger) diff --git a/pkg/ssh/config/parse_flags_test.go b/pkg/ssh/config/parse_flags_test.go index e14017e..37e6adf 100644 --- a/pkg/ssh/config/parse_flags_test.go +++ b/pkg/ssh/config/parse_flags_test.go @@ -15,178 +15,23 @@ package config import ( - "bufio" - "bytes" - "errors" "fmt" - "io" "os" "os/exec" "os/user" "path" "path/filepath" - "regexp" "strings" - "sync" "testing" - "time" "github.com/deckhouse/lib-dhctl/pkg/log" flag "github.com/spf13/pflag" "github.com/stretchr/testify/require" + "github.com/deckhouse/lib-connection/pkg/settings" "github.com/deckhouse/lib-connection/pkg/tests" ) -func TestParseFlagsHelp(t *testing.T) { - oldStdErr := os.Stderr - restoreStderr := func() { - os.Stderr = oldStdErr - } - - t.Cleanup(restoreStderr) - - test := tests.ShouldNewTest(t, "flags_help") - - sett := test.Settings() - logger := test.GetLogger() - - // Create a pipe - pr, pw, err := os.Pipe() - require.NoError(t, err, "pipes should created") - - os.Stderr = pw - - var wg sync.WaitGroup - wg.Add(1) - - var buf bytes.Buffer - - closed := false - - closePipes := func() { - if closed { - return - } - - // hack to wait write all - time.Sleep(3 * time.Second) - - if err := pr.Close(); err != nil && !errors.Is(err, os.ErrClosed) { - logger.ErrorF("Error closing read pipe: %v", err) - } - - if err := pw.Close(); err != nil && !errors.Is(err, os.ErrClosed) { - logger.ErrorF("Error closing read pipe: %v", err) - } - - wg.Wait() - - if err := bufio.NewWriter(&buf).Flush(); err != nil { - logger.ErrorF("Error flushing buf: %v", err) - } - - closed = true - } - - t.Cleanup(closePipes) - - hasCopyErr := func(err error) bool { - if err == nil { - return false - } - - if errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) { - return false - } - - return true - } - - go func() { - defer wg.Done() - _, err := io.Copy(&buf, pr) - // Copy all content from the pipe reader to the buffer - if err != nil && hasCopyErr(err) { - logger.ErrorF("Error copying data from stderr: %v", err) - } - }() - - t.Cleanup(func() { - closePipes() - restoreStderr() - if t.Failed() { - out := buf.String() - logger.InfoF("Got usage from Parse:") - logger.InfoF("%s", out) - } - }) - - // no use require because all stdout rewrite to buf - assertNoError := func(t *testing.T, msg string, err error) { - if err != nil { - restoreStderr() - logger.ErrorF("%s: %v", msg, err) - t.FailNow() - } - } - - flagSet := flag.NewFlagSet("ssh-help", flag.ContinueOnError) - - parser := NewFlagsParser(sett).WithEnvsPrefix("MY_PREFIX") - _, err = parser.InitFlags(flagSet) - assertNoError(t, "Flags init failed", err) - - err = flagSet.Parse([]string{"--help"}) - if !errors.Is(err, flag.ErrHelp) { - assertNoError(t, "Flags parse failed. Should return ErrHelp", fmt.Errorf("not help: %w", err)) - } - // stop writing - closePipes() - - const usagePrefix = "Usage of ssh-help:\n" - - out := buf.String() - if !strings.Contains(out, usagePrefix) { - assertNoError(t, "Flags help failed", fmt.Errorf("not contains usage prefix")) - } - - out = strings.TrimPrefix(out, usagePrefix) - out = strings.TrimSuffix(out, "\n") - - expectedFlags := 14 - - lines := strings.Split(out, "\n") - linesCount := len(lines) - - if linesCount != expectedFlags { - assertNoError( - t, - fmt.Sprintf("Flags help failed \n%s\n", out), fmt.Errorf( - "not contains all flags should %d got %d", expectedFlags, linesCount, - )) - } - - envMsgRe := regexp.MustCompile(`\(Can rewrite with MY_PREFIX_[A-Z_]+ env\)`) - - notContainsEnv := make([]string, 0) - for _, line := range lines { - if !envMsgRe.MatchString(line) { - notContainsEnv = append(notContainsEnv, line) - } - } - - if len(notContainsEnv) > 0 { - assertNoError(t, - "Flags help failed", - fmt.Errorf( - "not contains env vars for:\n %v", - strings.Join(notContainsEnv, "\n"), - ), - ) - } -} - func TestParseFlags(t *testing.T) { usr, err := user.Current() require.NoError(t, err, "could not get current user") @@ -1025,8 +870,8 @@ sshBastionPassword: "not_secure_password_bastion" testCase.before(t, &testCase, logger) } - parser := NewFlagsParser(sett). - WithEnvsPrefix(testCase.envsPrefix) + parser := NewFlagsParser(sett) + parser.WithEnvsPrefix(testCase.envsPrefix) if !testCase.defaultAsk { parser.WithAsk(func(promt string) ([]byte, error) { @@ -1265,6 +1110,28 @@ func TestParseFlagsAndExtractConfigNoArgs(t *testing.T) { flagSet.assertAdditionalFlagsParsed(t) } +func TestParseFlagsHelp(t *testing.T) { + tests.AssertParseFlagsHelp(t, tests.AssertParseFlagsHelpParams{ + ExpectedFlags: 14, + Name: "ssh-flags", + Provider: func(sett settings.Settings, envsPrefix string) tests.TestFlagsParser { + parser := NewFlagsParser(sett) + parser.WithEnvsPrefix(envsPrefix) + + return &testHelpParser{parser: parser} + }, + }) +} + +type testHelpParser struct { + parser *FlagsParser +} + +func (p *testHelpParser) InitFlags(flagSet *flag.FlagSet) error { + _, err := p.parser.InitFlags(flagSet) + return err +} + type parseFlagsAndExtractConfigFlagSet struct { additionalParam string flagSet *flag.FlagSet @@ -1382,7 +1249,8 @@ func assertParseAndExtract(t *testing.T, params *parseFlagsAndExtractConfigParam logger.InfoF("Got prefix: %s", prefix) - parser := NewFlagsParser(params.test.Settings()).WithEnvsPrefix(prefix) + parser := NewFlagsParser(params.test.Settings()) + parser.WithEnvsPrefix(prefix) config, err := parser.ParseFlagsAndExtractConfig(params.arguments, flagSet, ParseWithRequiredSSHHost(true)) diff --git a/pkg/tests/parse_flags.go b/pkg/tests/parse_flags.go new file mode 100644 index 0000000..17515c9 --- /dev/null +++ b/pkg/tests/parse_flags.go @@ -0,0 +1,192 @@ +// 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 tests + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "os" + "regexp" + "strings" + "sync" + "testing" + "time" + + flag "github.com/spf13/pflag" + "github.com/stretchr/testify/require" + + "github.com/deckhouse/lib-connection/pkg/settings" +) + +type TestFlagsParser interface { + InitFlags(*flag.FlagSet) error +} + +type TestFlagsParserHelpProvider func(s settings.Settings, envsPrefix string) TestFlagsParser + +type AssertParseFlagsHelpParams struct { + Name string + Provider TestFlagsParserHelpProvider + ExpectedFlags int +} + +func AssertParseFlagsHelp(t *testing.T, params AssertParseFlagsHelpParams) { + oldStdErr := os.Stderr + restoreStderr := func() { + os.Stderr = oldStdErr + } + + t.Cleanup(restoreStderr) + + test := ShouldNewTest(t, params.Name) + + sett := test.Settings() + logger := test.GetLogger() + + // Create a pipe + pr, pw, err := os.Pipe() + require.NoError(t, err, "pipes should created") + + os.Stderr = pw + + var wg sync.WaitGroup + wg.Add(1) + + var buf bytes.Buffer + + closed := false + + closePipes := func() { + if closed { + return + } + + // hack to wait write all + time.Sleep(3 * time.Second) + + if err := pr.Close(); err != nil && !errors.Is(err, os.ErrClosed) { + logger.ErrorF("Error closing read pipe: %v", err) + } + + if err := pw.Close(); err != nil && !errors.Is(err, os.ErrClosed) { + logger.ErrorF("Error closing read pipe: %v", err) + } + + wg.Wait() + + if err := bufio.NewWriter(&buf).Flush(); err != nil { + logger.ErrorF("Error flushing buf: %v", err) + } + + closed = true + } + + t.Cleanup(closePipes) + + hasCopyErr := func(err error) bool { + if err == nil { + return false + } + + if errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) { + return false + } + + return true + } + + go func() { + defer wg.Done() + _, err := io.Copy(&buf, pr) + // Copy all content from the pipe reader to the buffer + if err != nil && hasCopyErr(err) { + logger.ErrorF("Error copying data from stderr: %v", err) + } + }() + + t.Cleanup(func() { + closePipes() + restoreStderr() + if t.Failed() { + out := buf.String() + logger.InfoF("Got usage from Parse:") + logger.InfoF("%s", out) + } + }) + + // no use require because all stdout rewrite to buf + assertNoError := func(t *testing.T, msg string, err error) { + if err != nil { + restoreStderr() + logger.ErrorF("%s: %v", msg, err) + t.FailNow() + } + } + + flagSet := flag.NewFlagSet(params.Name, flag.ContinueOnError) + + err = params.Provider(sett, "MY_PREFIX").InitFlags(flagSet) + assertNoError(t, "Flags init failed", err) + + err = flagSet.Parse([]string{"--help"}) + if !errors.Is(err, flag.ErrHelp) { + assertNoError(t, "Flags parse failed. Should return ErrHelp", fmt.Errorf("not help: %w", err)) + } + // stop writing + closePipes() + + usagePrefix := fmt.Sprintf("Usage of %s:\n", params.Name) + + out := buf.String() + if !strings.Contains(out, usagePrefix) { + assertNoError(t, "Flags help failed", fmt.Errorf("not contains usage prefix")) + } + + out = strings.TrimPrefix(out, usagePrefix) + out = strings.TrimSuffix(out, "\n") + + lines := strings.Split(out, "\n") + linesCount := len(lines) + + if linesCount != params.ExpectedFlags { + assertNoError( + t, + fmt.Sprintf("Flags help failed \n%s\n", out), fmt.Errorf( + "not contains all flags should %d got %d", params.ExpectedFlags, linesCount, + )) + } + + envMsgRe := regexp.MustCompile(`\(Can rewrite with MY_PREFIX_[A-Z_]+ env\)`) + + notContainsEnv := make([]string, 0) + for _, line := range lines { + if !envMsgRe.MatchString(line) { + notContainsEnv = append(notContainsEnv, line) + } + } + + if len(notContainsEnv) > 0 { + assertNoError(t, + "Flags help failed", + fmt.Errorf( + "not contains env vars for:\n %v", + strings.Join(notContainsEnv, "\n"), + ), + ) + } +} diff --git a/pkg/utils/env/extractor.go b/pkg/utils/env/extractor.go index ecc7bfa..d365e37 100644 --- a/pkg/utils/env/extractor.go +++ b/pkg/utils/env/extractor.go @@ -87,7 +87,7 @@ func (e *Extractor) AddEnvToUsage(usage string, envName string) string { return usage } - return fmt.Sprintf("%s (Can rewrite with %s env)", usage, e.nameWithPrefix(envName)) + return fmt.Sprintf("%s (Can rewrite with %s env)", usage, e.NameWithPrefix(envName)) } func (e *Extractor) Int(name string, destination *int) (bool, error) { @@ -98,7 +98,7 @@ func (e *Extractor) Int(name string, destination *int) (bool, error) { value, err := strconv.Atoi(strVar) if err != nil { - return false, fmt.Errorf("Cannot convert '%s' to int for %s: %w", strVar, e.nameWithPrefix(name), err) + return false, fmt.Errorf("Cannot convert '%s' to int for %s: %w", strVar, e.NameWithPrefix(name), err) } *destination = value @@ -177,7 +177,7 @@ func (e *Extractor) Bool(name string, destination *bool) bool { return true } -func (e *Extractor) nameWithPrefix(name string) string { +func (e *Extractor) NameWithPrefix(name string) string { if e.prefix != "" { name = fmt.Sprintf("%s%s%s", e.prefix, e.prefixSeparator, name) } @@ -186,7 +186,7 @@ func (e *Extractor) nameWithPrefix(name string) string { } func (e *Extractor) getVar(name string) (string, bool) { - return e.lookupFunc(e.nameWithPrefix(name)) + return e.lookupFunc(e.NameWithPrefix(name)) } type Var struct { diff --git a/pkg/utils/file/reader.go b/pkg/utils/file/reader.go new file mode 100644 index 0000000..3dd07f7 --- /dev/null +++ b/pkg/utils/file/reader.go @@ -0,0 +1,58 @@ +// 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 file + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/deckhouse/lib-dhctl/pkg/log" +) + +func Reader(path string, fileType string) (io.ReadCloser, error) { + fullPath, err := filepath.Abs(path) + if err != nil { + return nil, fmt.Errorf("Cannot get abs path for %s: %w", path, err) + } + + stat, err := os.Stat(fullPath) + if err != nil { + return nil, fmt.Errorf("Cannot get %s file info for %s: %w", fileType, fullPath, err) + } + + if stat.IsDir() || !stat.Mode().IsRegular() { + return nil, fmt.Errorf("%s path '%s' should be regular file", fileType, fullPath) + } + + return os.Open(fullPath) +} + +func ReadFile(path string, fileType string, logger ...log.Logger) ([]byte, error) { + reader, err := Reader(path, fileType) + if err != nil { + return nil, err + } + + defer func() { + err := reader.Close() + if err != nil && len(logger) > 0 { + logger[0].DebugF("Error closing config file: %v", err) + } + }() + + return io.ReadAll(reader) +} diff --git a/pkg/utils/flags/base_flags.go b/pkg/utils/flags/base_flags.go new file mode 100644 index 0000000..61e7cc2 --- /dev/null +++ b/pkg/utils/flags/base_flags.go @@ -0,0 +1,83 @@ +// 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 flags + +import ( + "fmt" + + "github.com/name212/govalue" + flag "github.com/spf13/pflag" + + "github.com/deckhouse/lib-connection/pkg/utils/env" +) + +type BaseFlags struct { + flagSet *flag.FlagSet + envExtractor *env.Extractor +} + +func NewBaseFlags(flagSet *flag.FlagSet, envExtractor *env.Extractor) *BaseFlags { + return &BaseFlags{ + flagSet: flagSet, + envExtractor: envExtractor, + } +} + +func (f *BaseFlags) IsInitialized() error { + if govalue.Nil(f) { + return notInitializedError("baseFlags") + } + + if govalue.Nil(f.envExtractor) { + return notInitializedEnvExtractorError() + } + + if govalue.Nil(f.flagSet) { + return notInitializedError("flagSet") + } + + if !f.flagSet.Parsed() { + return fmt.Errorf("flagsSet is not parsed. Call flag.Parse or flag.FlagSet.Parse before extract config") + } + + return nil +} + +func (f *BaseFlags) EnvExtractor() *env.Extractor { + return f.envExtractor +} + +func (f *BaseFlags) ShouldEnvExtractor() (*env.Extractor, error) { + if govalue.Nil(f.envExtractor) { + return nil, notInitializedEnvExtractorError() + } + + return f.envExtractor, nil +} + +func (f *BaseFlags) FlagSet() *flag.FlagSet { + return f.flagSet +} + +func notInitializedEnvExtractorError() error { + return notInitializedError("envExtractor") +} + +func notInitializedError(field string) error { + return fmt.Errorf( + "Internal error. %s in Flags did not initialize. Call InitFlags first and pass Flags from result of InitFlags", + field, + ) +} diff --git a/pkg/utils/flags/base_parser.go b/pkg/utils/flags/base_parser.go new file mode 100644 index 0000000..27572a4 --- /dev/null +++ b/pkg/utils/flags/base_parser.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 flags + +import ( + "os" + + "github.com/name212/govalue" + + "github.com/deckhouse/lib-connection/pkg/settings" + "github.com/deckhouse/lib-connection/pkg/utils/env" +) + +type BaseParser struct { + envsPrefix string + sett settings.Settings + envsLookup env.EnvsLookupFunc +} + +func NewBaseParser(sett settings.Settings) *BaseParser { + p := &BaseParser{ + sett: sett, + envsLookup: os.LookupEnv, + } + + p.WithEnvsPrefix(sett.EnvsPrefix()) + + return p +} + +// WithEnvsPrefix +// This method trim right all _ ang - symbols and spaces left and right +// By default parser add _ after prefix for all env vars +func (p *BaseParser) WithEnvsPrefix(envsPrefix string) { + p.envsPrefix = env.SimplifyPrefix(envsPrefix) +} + +func (p *BaseParser) WithEnvsLookup(lookup env.EnvsLookupFunc) { + if govalue.Nil(lookup) { + p.sett.Logger().WarnF("Envs lookup function is nil. Skip set ask function.") + return + } + + p.envsLookup = lookup +} + +func (p *BaseParser) Settings() settings.Settings { + return p.sett +} + +func (p *BaseParser) NewEnvsExtractor() *env.Extractor { + return env.NewExtractor(p.envsPrefix, p.envsLookup) +} diff --git a/tests/go.mod b/tests/go.mod index 94794ec..c4a6a59 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -6,29 +6,29 @@ require github.com/deckhouse/lib-connection v0.0.0-00010101000000-000000000000 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/deckhouse/deckhouse/pkg/log v0.1.1-0.20251230144142-2bad7c3d1edf // indirect github.com/deckhouse/lib-dhctl v0.13.0 // indirect github.com/deckhouse/lib-gossh v0.3.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // 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/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/loads v0.19.5 // indirect github.com/go-openapi/runtime v0.19.16 // indirect github.com/go-openapi/spec v0.19.8 // indirect github.com/go-openapi/strfmt v0.19.5 // indirect - github.com/go-openapi/swag v0.19.9 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.19.12 // indirect github.com/go-stack/stack v1.8.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/gookit/color v1.5.2 // 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/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/mapstructure v1.3.2 // indirect github.com/name212/govalue v1.1.0 // indirect github.com/spf13/pflag v1.0.10 // indirect @@ -41,14 +41,13 @@ require ( golang.org/x/sys v0.40.0 // indirect golang.org/x/term v0.39.0 // indirect golang.org/x/text v0.33.0 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tests/go.sum b/tests/go.sum index 87964bf..9322eae 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -2,9 +2,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 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= @@ -18,8 +16,9 @@ github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774/go.mod h1:5wi5YYOp 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/deckhouse/lib-dhctl v0.13.0 h1:Xh7G30seic4Aa8MdXOH2ecGvO0zsn6PBGBHbJ4YNnZM= @@ -30,8 +29,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD 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-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/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= @@ -50,13 +49,16 @@ github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpX 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/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 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/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/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 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= @@ -90,8 +92,10 @@ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ 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-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 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= @@ -129,8 +133,8 @@ 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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -145,6 +149,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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= @@ -152,6 +158,9 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 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/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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= @@ -161,8 +170,9 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN 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/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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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= @@ -171,7 +181,6 @@ 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.1.0 h1:kSdUVs21cM5bFp7RW5sWPrwQ0RzC/Xhk3f+A+dUL6TM= github.com/name212/govalue v1.1.0/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= @@ -179,11 +188,14 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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= @@ -196,6 +208,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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= @@ -203,6 +216,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= @@ -285,8 +299,9 @@ golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgw 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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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= From 93dc3654befdd5ce382b64508943969868e87af0 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Sun, 1 Feb 2026 00:06:01 +0300 Subject: [PATCH 2/5] add kube flags Signed-off-by: Nikolay Mitrofanov --- pkg/kube/parse_flags.go | 4 + pkg/kube/parse_flags_test.go | 160 +++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/pkg/kube/parse_flags.go b/pkg/kube/parse_flags.go index 5d32013..1f95d38 100644 --- a/pkg/kube/parse_flags.go +++ b/pkg/kube/parse_flags.go @@ -172,6 +172,10 @@ func (p *FlagsParser) ExtractConfigAfterParse(flags *Flags) (*Config, error) { kubeConfigFile := flags.KubeConfig + if kubeConfigFile == "" && flags.KubeConfigContext != "" { + return nil, fmt.Errorf("Pass context flag --%s without kubeconfig path --%s ", kubeConfigContextFlag, kubeConfigFlag) + } + if kubeConfigFile != "" { content, err := file.ReadFile(kubeConfigFile, "kube config", logger) if err != nil { diff --git a/pkg/kube/parse_flags_test.go b/pkg/kube/parse_flags_test.go index 88cf146..8bb8db1 100644 --- a/pkg/kube/parse_flags_test.go +++ b/pkg/kube/parse_flags_test.go @@ -15,14 +15,143 @@ package kube import ( + "fmt" + "strings" "testing" flag "github.com/spf13/pflag" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/runtime" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/tools/clientcmd/api/latest" + "sigs.k8s.io/yaml" "github.com/deckhouse/lib-connection/pkg/settings" "github.com/deckhouse/lib-connection/pkg/tests" ) +func TestParseFlags(t *testing.T) { + type test struct { + name string + + arguments []string + + envsPrefix string + envs map[string]string + + hasErrorContains string + hasParseErrorContains string + + expected *Config + before func(*testing.T, *test) + test *tests.Test + } + + createValidConfigAndPassArg := func(t *testing.T, ts *test) string { + path := createValidTestConfig(t, ts.test) + ts.arguments = append(ts.arguments, fmt.Sprintf("--kubeconfig=%s", path)) + return path + } + + cases := []test{ + { + name: "no arguments provide empty config", + + arguments: nil, + + hasErrorContains: "", + hasParseErrorContains: "", + + expected: &Config{}, + }, + + { + name: "in cluster config", + + arguments: []string{ + "--kube-client-from-cluster", + }, + + hasErrorContains: "", + hasParseErrorContains: "", + + expected: &Config{ + KubeConfigInCluster: true, + }, + }, + + { + name: "kubeconfig path flag", + + arguments: []string{}, + + before: func(t *testing.T, ts *test) { + path := createValidConfigAndPassArg(t, ts) + ts.expected.KubeConfig = path + }, + + hasErrorContains: "", + hasParseErrorContains: "", + + expected: &Config{}, + }, + } + + assertError := func(t *testing.T, err error, errorContains string) bool { + if errorContains != "" { + require.Error(t, err, "should parse error") + require.Contains(t, err.Error(), errorContains, "should parse error contains") + return true + } + + require.NoError(t, err, "parse flags") + + return false + } + + for _, testCase := range cases { + t.Run(testCase.name, func(t *testing.T) { + tst := tests.ShouldNewTest(t, testCase.name) + sett := tst.Settings() + + testCase.test = tst + + if testCase.before != nil { + testCase.before(t, &testCase) + } + + parser := NewFlagsParser(sett) + parser.WithEnvsPrefix(testCase.envsPrefix) + if len(testCase.envs) > 0 { + parser.WithEnvsLookup(func(name string) (string, bool) { + val, ok := testCase.envs[name] + return val, ok + }) + } + + flagSetName := strings.ReplaceAll(testCase.name, " ", "-") + flagSetName = strings.ReplaceAll(flagSetName, ":", "-") + flagSetName = "test-parse" + flagSetName + + fset := flag.NewFlagSet(flagSetName, flag.ContinueOnError) + flags, err := parser.InitFlags(fset) + require.NoError(t, err, "init flags") + + err = fset.Parse(testCase.arguments) + if assertError(t, err, testCase.hasParseErrorContains) { + return + } + + config, err := parser.ExtractConfigAfterParse(flags) + if assertError(t, err, testCase.hasParseErrorContains) { + return + } + + require.Equal(t, testCase.expected, config, "should valid config") + }) + } +} + func TestParseFlagsHelp(t *testing.T) { tests.AssertParseFlagsHelp(t, tests.AssertParseFlagsHelpParams{ ExpectedFlags: 3, @@ -44,3 +173,34 @@ func (p *testHelpParser) InitFlags(flagSet *flag.FlagSet) error { _, err := p.parser.InitFlags(flagSet) return err } + +func createValidTestConfig(t *testing.T, test *tests.Test) string { + const ( + server = "https://anything.com:8080" + token = "the-token" + ) + + config := clientcmdapi.NewConfig() + config.APIVersion = "v1" + config.Kind = "Config" + + config.Clusters["clean"] = &clientcmdapi.Cluster{ + Server: server, + } + config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ + Token: token, + } + config.Contexts["clean"] = &clientcmdapi.Context{ + Cluster: "clean", + AuthInfo: "clean", + } + config.CurrentContext = "clean" + + json, err := runtime.Encode(latest.Codec, config) + require.NoError(t, err, "encode json") + + content, err := yaml.JSONToYAML(json) + require.NoError(t, err, "marshal kube config") + + return test.MustCreateTmpFile(t, string(content), false, "kubeconfig.yaml") +} From adef8fe4321f4c12032ade256d960954892ac169 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Sun, 1 Feb 2026 20:14:58 +0300 Subject: [PATCH 3/5] add kube flags Signed-off-by: Nikolay Mitrofanov --- pkg/kube/parse_flags.go | 74 +++++++++++------ pkg/kube/parse_flags_test.go | 123 ++++++++++++++++++++++++++--- pkg/ssh/config/parse_flags.go | 21 +++-- pkg/ssh/config/parse_flags_test.go | 58 ++++++++++---- pkg/tests/parse_flags.go | 7 +- pkg/utils/flags/base_flags.go | 41 +++++++++- pkg/utils/flags/base_flags_test.go | 120 ++++++++++++++++++++++++++++ 7 files changed, 382 insertions(+), 62 deletions(-) create mode 100644 pkg/utils/flags/base_flags_test.go diff --git a/pkg/kube/parse_flags.go b/pkg/kube/parse_flags.go index 1f95d38..c70ecb6 100644 --- a/pkg/kube/parse_flags.go +++ b/pkg/kube/parse_flags.go @@ -17,6 +17,7 @@ package kube import ( "fmt" + "github.com/deckhouse/lib-dhctl/pkg/log" flag "github.com/spf13/pflag" "k8s.io/client-go/tools/clientcmd" @@ -44,13 +45,20 @@ type Flags struct { baseFlags *baseflags.BaseFlags } -func (f *Flags) IsConflictBetweenFlags() error { - envsExtractor, err := f.baseFlags.ShouldEnvExtractor() - if err != nil { - return err - } +// Parse +// pass nil if we should use os.Args +func (f *Flags) Parse(args []string) error { + // Parse check that flags is initialized + return f.baseFlags.Parse(args) +} +func (f *Flags) IsConflictBetweenFlags() error { if f.KubeConfig != "" && f.KubeConfigInCluster { + envsExtractor, err := f.baseFlags.ShouldEnvExtractor() + if err != nil { + return err + } + return fmt.Errorf( "Cannot use both --%s and --%s or envs %s and %s at the same time", kubeConfigFlag, @@ -104,7 +112,7 @@ func NewFlagsParser(sett settings.Settings) *FlagsParser { // InitFlags // init flag.FlagSet and return struct with flags where flag.FlagSet parsed -// should call before flag.Parse or flag.FlagSet.Parse +// Flags contains copy of set. For parse use Flags.Parse // if set is parsed returns error func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { if set.Parsed() { @@ -114,9 +122,11 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { envsExtractor := p.NewEnvsExtractor() flags := &Flags{ - baseFlags: baseflags.NewBaseFlags(set, envsExtractor), + baseFlags: baseflags.NewBaseFlags(set, envsExtractor, baseflags.BaseFlagsSkipUnknownFlags()), } + set = flags.baseFlags.FlagSet() + set.StringVar( &flags.KubeConfig, kubeConfigFlag, @@ -128,7 +138,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { ) set.StringVar( - &flags.KubeConfig, + &flags.KubeConfigContext, kubeConfigContextFlag, "", envsExtractor.AddEnvToUsage( @@ -170,27 +180,45 @@ func (p *FlagsParser) ExtractConfigAfterParse(flags *Flags) (*Config, error) { sett := p.Settings() logger := sett.Logger() + if err := p.validateKubeConfigWithContext(flags, logger); err != nil { + return nil, err + } + + return &Config{ + KubeConfig: flags.KubeConfig, + KubeConfigContext: flags.KubeConfigContext, + KubeConfigInCluster: flags.KubeConfigInCluster, + }, nil +} + +func (p *FlagsParser) validateKubeConfigWithContext(flags *Flags, logger log.Logger) error { kubeConfigFile := flags.KubeConfig + context := flags.KubeConfigContext + + if kubeConfigFile == "" { + if context != "" { + return fmt.Errorf("Pass context flag --%s without kubeconfig path --%s ", kubeConfigContextFlag, kubeConfigFlag) + } - if kubeConfigFile == "" && flags.KubeConfigContext != "" { - return nil, fmt.Errorf("Pass context flag --%s without kubeconfig path --%s ", kubeConfigContextFlag, kubeConfigFlag) + return nil } - if kubeConfigFile != "" { - content, err := file.ReadFile(kubeConfigFile, "kube config", logger) - if err != nil { - return nil, err - } + content, err := file.ReadFile(kubeConfigFile, "kube config", logger) + if err != nil { + return err + } - _, err = clientcmd.Load(content) - if err != nil { - return nil, fmt.Errorf("Cannot parse kube config file '%s': %w", kubeConfigFile, err) + cfg, err := clientcmd.Load(content) + if err != nil { + return fmt.Errorf("Cannot parse kube config file '%s': %w", kubeConfigFile, err) + } + + if context != "" { + _, ok := cfg.Contexts[context] + if !ok { + return fmt.Errorf("Cannot find context '%s' in kube config %s", context, kubeConfigFile) } } - return &Config{ - KubeConfig: kubeConfigFile, - KubeConfigContext: flags.KubeConfigContext, - KubeConfigInCluster: flags.KubeConfigInCluster, - }, nil + return nil } diff --git a/pkg/kube/parse_flags_test.go b/pkg/kube/parse_flags_test.go index 8bb8db1..0fe6478 100644 --- a/pkg/kube/parse_flags_test.go +++ b/pkg/kube/parse_flags_test.go @@ -47,10 +47,16 @@ func TestParseFlags(t *testing.T) { test *tests.Test } - createValidConfigAndPassArg := func(t *testing.T, ts *test) string { - path := createValidTestConfig(t, ts.test) + appendKubeConfigArgument := func(ts *test, path string) { ts.arguments = append(ts.arguments, fmt.Sprintf("--kubeconfig=%s", path)) - return path + } + + createValidConfigAndPassArg := func(t *testing.T, ts *test) { + path := createValidTestConfig(t, ts.test) + appendKubeConfigArgument(ts, path) + if ts.expected != nil { + ts.expected.KubeConfig = path + } } cases := []test{ @@ -80,21 +86,110 @@ func TestParseFlags(t *testing.T) { }, }, + { + name: "unknown flags should skip", + + arguments: []string{ + "--ssh-extra-args=a,b", + }, + + before: createValidConfigAndPassArg, + + hasErrorContains: "", + hasParseErrorContains: "", + + expected: &Config{}, + }, + { name: "kubeconfig path flag", arguments: []string{}, - before: func(t *testing.T, ts *test) { - path := createValidConfigAndPassArg(t, ts) - ts.expected.KubeConfig = path - }, + before: createValidConfigAndPassArg, hasErrorContains: "", hasParseErrorContains: "", expected: &Config{}, }, + + { + name: "kubeconfig with valid context", + + arguments: []string{ + "--kubeconfig-context=clean", + }, + + before: createValidConfigAndPassArg, + + hasErrorContains: "", + hasParseErrorContains: "", + + expected: &Config{ + KubeConfigContext: "clean", + }, + }, + + { + name: "kubeconfig and in cluster client passed both", + + arguments: []string{ + "--kube-client-from-cluster", + }, + + before: createValidConfigAndPassArg, + + hasErrorContains: "Cannot use both --kubeconfig and --kube-client-from-cluster or envs KUBE_CONFIG and KUBE_CLIENT_FROM_CLUSTER at the same time", + hasParseErrorContains: "", + }, + + { + name: "kubeconfig and incorrect context", + + arguments: []string{ + "--kubeconfig-context=not-exists", + }, + + before: createValidConfigAndPassArg, + + hasErrorContains: "Cannot find context 'not-exists' in kube config", + hasParseErrorContains: "", + }, + + { + name: "pass context without kubeconfig", + + arguments: []string{ + "--kubeconfig-context=not-exists", + }, + + hasErrorContains: "Pass context flag --kubeconfig-context without kubeconfig path --kubeconfig", + hasParseErrorContains: "", + }, + + { + name: "not exists kubeconfig", + + before: func(t *testing.T, ts *test) { + appendKubeConfigArgument(ts, "/tmp/not-exsists-2dfr.yaml") + }, + + hasErrorContains: "Cannot get kube config file info for /tmp/not-exsists-2dfr.yaml", + hasParseErrorContains: "", + }, + + { + name: "kubeconfig as dir", + + before: func(t *testing.T, ts *test) { + dir := ts.test.MustMkSubDirs(t, "kube-config-as-dir") + appendKubeConfigArgument(ts, dir) + }, + + hasErrorContains: "should be regular file", + hasParseErrorContains: "", + }, } assertError := func(t *testing.T, err error, errorContains string) bool { @@ -137,13 +232,13 @@ func TestParseFlags(t *testing.T) { flags, err := parser.InitFlags(fset) require.NoError(t, err, "init flags") - err = fset.Parse(testCase.arguments) + err = flags.Parse(testCase.arguments) if assertError(t, err, testCase.hasParseErrorContains) { return } config, err := parser.ExtractConfigAfterParse(flags) - if assertError(t, err, testCase.hasParseErrorContains) { + if assertError(t, err, testCase.hasErrorContains) { return } @@ -169,9 +264,13 @@ type testHelpParser struct { parser *FlagsParser } -func (p *testHelpParser) InitFlags(flagSet *flag.FlagSet) error { - _, err := p.parser.InitFlags(flagSet) - return err +func (p *testHelpParser) InitFlags(flagSet *flag.FlagSet) (*flag.FlagSet, error) { + flags, err := p.parser.InitFlags(flagSet) + if err != nil { + return nil, err + } + + return flags.baseFlags.FlagSet(), err } func createValidTestConfig(t *testing.T, test *tests.Test) string { diff --git a/pkg/ssh/config/parse_flags.go b/pkg/ssh/config/parse_flags.go index 342819d..c87d3b0 100644 --- a/pkg/ssh/config/parse_flags.go +++ b/pkg/ssh/config/parse_flags.go @@ -16,7 +16,6 @@ package config import ( "fmt" - "os" "path/filepath" "sort" "strings" @@ -87,6 +86,13 @@ type Flags struct { baseFlags *baseflags.BaseFlags } +// Parse +// pass nil if we should use os.Args +func (f *Flags) Parse(args []string) error { + // Parse check that flags is initialized + return f.baseFlags.Parse(args) +} + func (f *Flags) IsConflictBetweenFlags() error { userPassedArguments := len(f.PrivateKeysPaths) > 0 || f.BastionHost != "" || @@ -278,9 +284,11 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { envsExtractor := p.NewEnvsExtractor() flags := &Flags{ - baseFlags: baseflags.NewBaseFlags(set, envsExtractor), + baseFlags: baseflags.NewBaseFlags(set, envsExtractor, baseflags.BaseFlagsSkipUnknownFlags()), } + set = flags.baseFlags.FlagSet() + set.StringSliceVar( &flags.PrivateKeysPaths, privateKeysFlag, @@ -427,7 +435,7 @@ func (p *FlagsParser) InitFlags(set *flag.FlagSet) (*Flags, error) { // ExtractConfigAfterParse // extract ConnectionConfig from flags -// should call after InitFlags and flag.Parse or flag.FlagSet.Parse +// Flags contains copy of set. For parse use Flags.Parse // if flag.FlagSet in Flags is not parse returns error func (p *FlagsParser) ExtractConfigAfterParse(flags *Flags, opts ...ValidateOption) (*ConnectionConfig, error) { if err := flags.baseFlags.IsInitialized(); err != nil { @@ -552,11 +560,8 @@ func (p *FlagsParser) ParseFlagsAndExtractConfig(arguments []string, set *flag.F return nil, err } - if arguments == nil { - arguments = os.Args[1:] - } - - if err := set.Parse(arguments); err != nil { + // nil arguments will rewrite from os.Args + if err := flags.Parse(arguments); err != nil { return nil, err } diff --git a/pkg/ssh/config/parse_flags_test.go b/pkg/ssh/config/parse_flags_test.go index 37e6adf..4188331 100644 --- a/pkg/ssh/config/parse_flags_test.go +++ b/pkg/ssh/config/parse_flags_test.go @@ -140,6 +140,42 @@ func TestParseFlags(t *testing.T) { }, }, + { + name: "unknown flags should processed", + passwords: nil, + arguments: []string{ + "--ssh-host=192.168.0.1", + "--unknown=value", + }, + hasParseErrorContains: "", + hasErrorContains: "", + + privateKeyExtractor: defaultPrivateKeyExtractor(currentHomeDir), + + expected: &ConnectionConfig{ + Config: &Config{ + Mode: Mode{ + ForceLegacy: false, + ForceModern: false, + }, + User: currentUserName, + Port: intPtr(22), + + PrivateKeys: []AgentPrivateKey{ + defaultPrivateKey(currentHomeDir), + }, + + BastionUser: currentUserName, + BastionPort: intPtr(22), + }, + Hosts: []Host{ + { + Host: "192.168.0.1", + }, + }, + }, + }, + { name: "empty rewrite HOME env", passwords: nil, @@ -668,16 +704,6 @@ sshBastionPassword: "not_secure_password_bastion" hasErrorContains: "Cannot use both --connection-config and --ssh-* flags or envs at the same time", }, - { - name: "unknown flag", - passwords: nil, - arguments: []string{ - "--ssh-host=192.168.0.1", - "--unknown=value", - }, - hasParseErrorContains: "unknown flag: --unknown", - }, - { name: "incorrect flag type", passwords: nil, @@ -909,7 +935,7 @@ sshBastionPassword: "not_secure_password_bastion" flags, err := parser.InitFlags(fset) require.NoError(t, err, "init flags") - err = fset.Parse(testCase.arguments) + err = flags.Parse(testCase.arguments) if testCase.hasParseErrorContains != "" { require.Error(t, err, "should parse error") require.Contains(t, err.Error(), testCase.hasParseErrorContains, "should parse error contains") @@ -1127,9 +1153,13 @@ type testHelpParser struct { parser *FlagsParser } -func (p *testHelpParser) InitFlags(flagSet *flag.FlagSet) error { - _, err := p.parser.InitFlags(flagSet) - return err +func (p *testHelpParser) InitFlags(flagSet *flag.FlagSet) (*flag.FlagSet, error) { + initFlags, err := p.parser.InitFlags(flagSet) + if err != nil { + return nil, err + } + + return initFlags.baseFlags.FlagSet(), nil } type parseFlagsAndExtractConfigFlagSet struct { diff --git a/pkg/tests/parse_flags.go b/pkg/tests/parse_flags.go index 17515c9..acf50c3 100644 --- a/pkg/tests/parse_flags.go +++ b/pkg/tests/parse_flags.go @@ -34,7 +34,7 @@ import ( ) type TestFlagsParser interface { - InitFlags(*flag.FlagSet) error + InitFlags(*flag.FlagSet) (*flag.FlagSet, error) } type TestFlagsParserHelpProvider func(s settings.Settings, envsPrefix string) TestFlagsParser @@ -140,10 +140,11 @@ func AssertParseFlagsHelp(t *testing.T, params AssertParseFlagsHelpParams) { flagSet := flag.NewFlagSet(params.Name, flag.ContinueOnError) - err = params.Provider(sett, "MY_PREFIX").InitFlags(flagSet) + parser := params.Provider(sett, "MY_PREFIX") + newFlagsSet, err := parser.InitFlags(flagSet) assertNoError(t, "Flags init failed", err) - err = flagSet.Parse([]string{"--help"}) + err = newFlagsSet.Parse([]string{"--help"}) if !errors.Is(err, flag.ErrHelp) { assertNoError(t, "Flags parse failed. Should return ErrHelp", fmt.Errorf("not help: %w", err)) } diff --git a/pkg/utils/flags/base_flags.go b/pkg/utils/flags/base_flags.go index 61e7cc2..d988fea 100644 --- a/pkg/utils/flags/base_flags.go +++ b/pkg/utils/flags/base_flags.go @@ -16,6 +16,7 @@ package flags import ( "fmt" + "os" "github.com/name212/govalue" flag "github.com/spf13/pflag" @@ -23,18 +24,54 @@ import ( "github.com/deckhouse/lib-connection/pkg/utils/env" ) +type BaseFlagsOpts struct { + skipUnknownFlags bool +} + +type BaseFlagsOpt func(f *BaseFlagsOpts) + +func BaseFlagsSkipUnknownFlags() BaseFlagsOpt { + return func(f *BaseFlagsOpts) { + f.skipUnknownFlags = true + } +} + type BaseFlags struct { flagSet *flag.FlagSet envExtractor *env.Extractor } -func NewBaseFlags(flagSet *flag.FlagSet, envExtractor *env.Extractor) *BaseFlags { +// NewBaseFlags +// contains copy of flagsSet +func NewBaseFlags(flagSet *flag.FlagSet, envExtractor *env.Extractor, opts ...BaseFlagsOpt) *BaseFlags { + options := &BaseFlagsOpts{} + for _, o := range opts { + o(options) + } + + set := *flagSet + if options.skipUnknownFlags { + set.ParseErrorsAllowlist.UnknownFlags = true + } + return &BaseFlags{ - flagSet: flagSet, + flagSet: &set, envExtractor: envExtractor, } } +func (f *BaseFlags) Parse(args []string) error { + if f == nil || f.flagSet == nil { + return fmt.Errorf("flags is not initialized. flagSet is nil") + } + + if args == nil { + args = os.Args[1:] + } + + return f.flagSet.Parse(args) +} + func (f *BaseFlags) IsInitialized() error { if govalue.Nil(f) { return notInitializedError("baseFlags") diff --git a/pkg/utils/flags/base_flags_test.go b/pkg/utils/flags/base_flags_test.go new file mode 100644 index 0000000..945807b --- /dev/null +++ b/pkg/utils/flags/base_flags_test.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 flags + +import ( + "testing" + + flag "github.com/spf13/pflag" + "github.com/stretchr/testify/require" + + "github.com/deckhouse/lib-connection/pkg/utils/env" +) + +func TestBaseFlagsParse(t *testing.T) { + type parseParam struct { + shouldParsed bool + opts []BaseFlagsOpt + additionalArgs []string + } + + getSet := func(res *string, opts ...BaseFlagsOpt) (*flag.FlagSet, *BaseFlags) { + fset := flag.NewFlagSet("test", flag.ContinueOnError) + fset.StringVar(res, "arg", "", "test arg") + return fset, NewBaseFlags(fset, env.NewOsExtractor(""), opts...) + } + + assertParse := func(t *testing.T, params parseParam) (*flag.FlagSet, *BaseFlags) { + args := append([]string{}, params.additionalArgs...) + args = append(args, "--arg=value") + + res := "" + + fset, base := getSet(&res, params.opts...) + + err := base.Parse(args) + if !params.shouldParsed { + require.Error(t, err, "should not parse") + return fset, base + } + + require.NoError(t, err, "should parsed") + require.Equal(t, res, "value", "should set arg") + + return fset, base + } + + t.Run("should copy flags", func(t *testing.T) { + fset, base := assertParse(t, parseParam{ + shouldParsed: true, + }) + + require.False(t, fset.Parsed(), "should not parse input flags set") + require.True(t, base.FlagSet().Parsed(), "should parse flags set copy in base") + }) + + type unknownTest struct { + name string + opts []BaseFlagsOpt + shouldParsed bool + } + + unknownTests := []unknownTest{ + { + name: "should parse flags if allow unknown", + opts: []BaseFlagsOpt{ + BaseFlagsSkipUnknownFlags(), + }, + shouldParsed: true, + }, + + { + name: "should not parse flags if disallow unknown", + opts: nil, + shouldParsed: false, + }, + } + + for _, ts := range unknownTests { + t.Run(ts.name, func(t *testing.T) { + assertParse(t, parseParam{ + shouldParsed: ts.shouldParsed, + opts: ts.opts, + additionalArgs: []string{"--unknown=val"}, + }) + }) + } + + t.Run("not parse if not initialized", func(t *testing.T) { + assertParseNils := func(t *testing.T, flags *BaseFlags) { + parse := func() { + err := flags.Parse([]string{"--arg=value"}) + require.Error(t, err, "should not parse nil base flags") + } + + require.NotPanics(t, parse, "should not parse nil base flags") + } + + var nilBase *BaseFlags + assertParseNils(t, nilBase) + + res := "" + _, base := getSet(&res) + base.flagSet = nil + + assertParseNils(t, nilBase) + require.Empty(t, res, "should not parse nil base flags") + }) +} From 742df083b38697e43d2784bbd0134ae9ec4032b3 Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Sun, 1 Feb 2026 21:13:21 +0300 Subject: [PATCH 4/5] add kube flags Signed-off-by: Nikolay Mitrofanov --- pkg/kube/parse_flags.go | 23 +++ pkg/kube/parse_flags_test.go | 252 +++++++++++++++++++++++++++++ pkg/ssh/config/parse_flags_test.go | 2 +- pkg/utils/env/extractor_test.go | 1 - 4 files changed, 276 insertions(+), 2 deletions(-) diff --git a/pkg/kube/parse_flags.go b/pkg/kube/parse_flags.go index c70ecb6..55068ad 100644 --- a/pkg/kube/parse_flags.go +++ b/pkg/kube/parse_flags.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/deckhouse/lib-dhctl/pkg/log" + "github.com/name212/govalue" flag "github.com/spf13/pflag" "k8s.io/client-go/tools/clientcmd" @@ -191,6 +192,28 @@ func (p *FlagsParser) ExtractConfigAfterParse(flags *Flags) (*Config, error) { }, nil } +// ParseFlagsAndExtractConfig +// initialize, parse and extract ConnectionConfig from flags +// set flag.FlagSet can be nil. If nil, func initialize new flag.FlagSet +// if arguments is nil extract arguments from os.Args +func (p *FlagsParser) ParseFlagsAndExtractConfig(arguments []string, set *flag.FlagSet) (*Config, error) { + if govalue.Nil(set) { + set = flag.NewFlagSet("kube", flag.ExitOnError) + } + + flags, err := p.InitFlags(set) + if err != nil { + return nil, err + } + + // nil arguments will rewrite from os.Args + if err := flags.Parse(arguments); err != nil { + return nil, err + } + + return p.ExtractConfigAfterParse(flags) +} + func (p *FlagsParser) validateKubeConfigWithContext(flags *Flags, logger log.Logger) error { kubeConfigFile := flags.KubeConfig context := flags.KubeConfigContext diff --git a/pkg/kube/parse_flags_test.go b/pkg/kube/parse_flags_test.go index 0fe6478..3125295 100644 --- a/pkg/kube/parse_flags_test.go +++ b/pkg/kube/parse_flags_test.go @@ -16,6 +16,8 @@ package kube import ( "fmt" + "os" + "os/exec" "strings" "testing" @@ -245,6 +247,202 @@ func TestParseFlags(t *testing.T) { require.Equal(t, testCase.expected, config, "should valid config") }) } + + t.Run("ParseFlagsAndExtractConfig", func(t *testing.T) { + t.Run("with args and no FlagSet", func(t *testing.T) { + assertParseAndExtract(t, assertParseAndExtractParams{ + envsPrefix: "ARGS_NO_FLAG_SET", + arguments: []string{ + "--kube-client-from-cluster", + }, + expected: &Config{ + KubeConfigInCluster: true, + }, + }) + }) + + t.Run("with args and with FlagSet", func(t *testing.T) { + flagSet := newParseFlagsAndExtractConfigFlagSet("test-connection-flagset") + + args := []string{ + "--kube-client-from-cluster", + } + + args = append(args, flagSet.additionalArguments()...) + + assertParseAndExtract(t, assertParseAndExtractParams{ + envsPrefix: "ARGS_FLAG_SET", + arguments: args, + flagSet: flagSet.flagSet, + expected: &Config{ + KubeConfigInCluster: true, + }, + }) + + flagSet.assertAdditionalFlagsParsed(t) + }) + + t.Run("without args and with FlagSet", func(t *testing.T) { + tst := tests.ShouldNewTest(t, t.Name()) + kubeConfigPath := createValidTestConfig(t, tst) + + args := []string{ + fmt.Sprintf("--kubeconfig=%s", kubeConfigPath), + "--kubeconfig-context=clean", + } + + // use subtest for safe rewrite os.Args + // we cannot use pass args with -args because we can run test from IDE + //nolint:gosec + cmd := exec.Command(os.Args[0], "-test.run=TestParseKubeFlagsAndExtractConfigNoArgs") + cmd.Env = append( + os.Environ(), + fmt.Sprintf("TEST_NO_ARGS_KUBE=%s", + strings.Join(args, " "), + ), + ) + + output, err := cmd.CombinedOutput() + require.NoError( + t, + err, + "TestParseKubeFlagsAndExtractConfigNoArgs should run without error: %s", + string(output), + ) + + tst.GetLogger().InfoF("Got output from TestParseFlagsAndExtractConfigNoArgs:\n%s", string(output)) + }) + }) +} + +func TestParseFlagsNoInitialize(t *testing.T) { + getParser := func(t *testing.T) *FlagsParser { + test := tests.ShouldNewTest(t, tests.Name(t)) + return NewFlagsParser(test.Settings()) + } + + assertError := func(t *testing.T, config *Config, err error, contains string) { + require.Error(t, err, "should not have an error") + require.Contains(t, err.Error(), contains) + require.Nil(t, config) + } + + t.Run("Extract without initialize", func(t *testing.T) { + flags := &Flags{ + Config: Config{ + KubeConfigContext: "clean", + KubeConfig: "/tmp/not-exsists-5jfr.yaml", + }, + } + + parser := getParser(t) + config, err := parser.ExtractConfigAfterParse(flags) + assertError(t, config, err, "Call InitFlags first and pass Flags from result of InitFlags") + }) + + t.Run("Extract from no parsed flagset", func(t *testing.T) { + flagSet := flag.NewFlagSet("no-parsed", flag.ContinueOnError) + parser := getParser(t) + flags, err := parser.InitFlags(flagSet) + require.NoError(t, err, "init flags should initialized") + config, err := parser.ExtractConfigAfterParse(flags) + assertError(t, config, err, "flagsSet is not parsed. Call flag.Parse or flag.FlagSet.Parse before extract config") + }) + + t.Run("Init config if flags already parsed", func(t *testing.T) { + flagSet := newParseFlagsAndExtractConfigFlagSet("already-parsed") + flagSet.parseOnlyAdditional(t) + + parser := getParser(t) + flags, err := parser.InitFlags(flagSet.flagSet) + assertError(t, nil, err, "Flags already parsed") + require.Nil(t, flags, "flags should be nil") + }) + + t.Run("ParseFlagsAndExtractConfig if flags already parsed", func(t *testing.T) { + flagSet := newParseFlagsAndExtractConfigFlagSet("already-parsed-parse-extract") + flagSet.parseOnlyAdditional(t) + + parser := getParser(t) + config, err := parser.ParseFlagsAndExtractConfig(make([]string, 0), flagSet.flagSet) + assertError(t, config, err, "Flags already parsed") + }) +} + +func TestParseKubeFlagsAndExtractConfigNoArgs(t *testing.T) { + argsStr, ok := os.LookupEnv("TEST_NO_ARGS_KUBE") + argsStr = strings.TrimSpace(argsStr) + + if !ok || argsStr == "" { + t.Skip("Run TestParseKubeFlagsAndExtractConfigNoArgs directly") + } + + // split by -- for safe process arguments with spaces + argsParts := strings.Split(argsStr, "--") + require.NotEmpty(t, argsParts, "args should not be empty") + + testArgs := make([]string, 0, len(argsParts)) + for _, arg := range argsParts { + arg = strings.TrimSpace(arg) + if arg == "" { + continue + } + + if !strings.HasPrefix(arg, "--") { + arg = fmt.Sprintf("--%s", arg) + } + + testArgs = append(testArgs, arg) + } + + const kubeConfigArg = "--kubeconfig=" + + kubeConfigPath := "" + + for _, arg := range testArgs { + if strings.HasPrefix(arg, kubeConfigArg) { + kubeConfigPath = strings.TrimPrefix(arg, kubeConfigArg) + kubeConfigPath = strings.TrimSpace(kubeConfigPath) + break + } + } + + require.NotEmpty( + t, + kubeConfigPath, + "kubeconfig path should present in args: %v", + strings.Join(testArgs, " "), + ) + + fmt.Printf("os.Args after parse: %s\n", strings.Join(testArgs, " ")) + + flagSet := newParseFlagsAndExtractConfigFlagSet("test-connection-without-args") + + require.Len(t, flagSet.arguments, 1, "should add additional arguments") + + oldArgs := os.Args + t.Cleanup(func() { + os.Args = oldArgs + }) + + withAdditional := []string{ + os.Args[0], + flagSet.arguments[0], + } + + withAdditional = append(withAdditional, testArgs...) + os.Args = withAdditional + + assertParseAndExtract(t, assertParseAndExtractParams{ + envsPrefix: "NO_ARGS", + flagSet: flagSet.flagSet, + expected: &Config{ + KubeConfig: kubeConfigPath, + KubeConfigContext: "clean", + }, + }) + + flagSet.assertAdditionalFlagsParsed(t) } func TestParseFlagsHelp(t *testing.T) { @@ -303,3 +501,57 @@ func createValidTestConfig(t *testing.T, test *tests.Test) string { return test.MustCreateTmpFile(t, string(content), false, "kubeconfig.yaml") } + +type parseFlagsAndExtractConfigFlagSet struct { + arguments []string + additionalParam string + flagSet *flag.FlagSet +} + +func newParseFlagsAndExtractConfigFlagSet(name string) *parseFlagsAndExtractConfigFlagSet { + res := &parseFlagsAndExtractConfigFlagSet{} + + flagSet := flag.NewFlagSet(name, flag.ContinueOnError) + flagSet.StringVar(&res.additionalParam, "my-param", "", "test argument") + + res.flagSet = flagSet + + res.arguments = append(res.arguments, res.additionalArguments()...) + + return res +} + +func (s *parseFlagsAndExtractConfigFlagSet) additionalArguments() []string { + return []string{ + "--my-param=val", + } +} + +func (s *parseFlagsAndExtractConfigFlagSet) assertAdditionalFlagsParsed(t *testing.T) { + require.Equal(t, s.additionalParam, "val", "should parse additional argument") +} + +func (s *parseFlagsAndExtractConfigFlagSet) parseOnlyAdditional(t *testing.T) { + err := s.flagSet.Parse(s.additionalArguments()) + require.NoError(t, err, "should parse only additional flags") + s.assertAdditionalFlagsParsed(t) +} + +type assertParseAndExtractParams struct { + envsPrefix string + arguments []string + flagSet *flag.FlagSet + expected *Config +} + +func assertParseAndExtract(t *testing.T, params assertParseAndExtractParams) { + tst := tests.ShouldNewTest(t, t.Name()) + + parser := NewFlagsParser(tst.Settings()) + parser.WithEnvsPrefix(params.envsPrefix) + + config, err := parser.ParseFlagsAndExtractConfig(params.arguments, params.flagSet) + require.NoError(t, err, "should parse and extract") + + require.Equal(t, params.expected, config, "config should be equal") +} diff --git a/pkg/ssh/config/parse_flags_test.go b/pkg/ssh/config/parse_flags_test.go index 4188331..dd8958b 100644 --- a/pkg/ssh/config/parse_flags_test.go +++ b/pkg/ssh/config/parse_flags_test.go @@ -1102,7 +1102,7 @@ func TestParseFlagsAndExtractConfigNoArgs(t *testing.T) { strings.Join(testArgs, " "), ) - t.Logf("os.Args after parse: %s", strings.Join(testArgs, " ")) + fmt.Printf("os.Args after parse: %s\n", strings.Join(testArgs, " ")) params := defaultArgsForParseFlagsAndExtractConfig(t, "without_args", &testPrivateKey{ path: privateKeyPath, diff --git a/pkg/utils/env/extractor_test.go b/pkg/utils/env/extractor_test.go index ad6ec6c..2ec0167 100644 --- a/pkg/utils/env/extractor_test.go +++ b/pkg/utils/env/extractor_test.go @@ -387,7 +387,6 @@ func TestBool(t *testing.T) { trueCase("True"), trueCase("true"), trueCase("not empty string"), - trueCase("not_empty_string"), trueCase(" not empty string with spaces "), } From 014f301d5411ed1dcec23beb511d9a3046ec48ec Mon Sep 17 00:00:00 2001 From: Nikolay Mitrofanov Date: Sun, 1 Feb 2026 22:00:38 +0300 Subject: [PATCH 5/5] add kube flags Signed-off-by: Nikolay Mitrofanov --- pkg/kube/parse_flags_test.go | 226 +++++++++++++++++++++++++++------ pkg/utils/env/extractor.go | 4 +- pkg/utils/flags/base_parser.go | 4 +- 3 files changed, 192 insertions(+), 42 deletions(-) diff --git a/pkg/kube/parse_flags_test.go b/pkg/kube/parse_flags_test.go index 3125295..253fa39 100644 --- a/pkg/kube/parse_flags_test.go +++ b/pkg/kube/parse_flags_test.go @@ -38,11 +38,11 @@ func TestParseFlags(t *testing.T) { arguments []string - envsPrefix string - envs map[string]string + envsPrefix string + envs map[string]string + noUsePredefinedLookup bool - hasErrorContains string - hasParseErrorContains string + hasErrorContains string expected *Config before func(*testing.T, *test) @@ -53,6 +53,30 @@ func TestParseFlags(t *testing.T) { ts.arguments = append(ts.arguments, fmt.Sprintf("--kubeconfig=%s", path)) } + appendEnv := func(ts *test, env string, val string, noPrefix bool) { + envs := ts.envs + if len(envs) == 0 { + envs = make(map[string]string) + } + + prefix := fmt.Sprintf("%s_", ts.envsPrefix) + if noPrefix { + prefix = "" + } + + key := prefix + env + envs[key] = val + ts.envs = envs + } + + appendKubeConfigEnv := func(ts *test, path string) { + appendEnv(ts, "KUBE_CONFIG", path, false) + } + + appendKubeConfigDefault := func(ts *test, path string) { + appendEnv(ts, "KUBECONFIG", path, true) + } + createValidConfigAndPassArg := func(t *testing.T, ts *test) { path := createValidTestConfig(t, ts.test) appendKubeConfigArgument(ts, path) @@ -61,16 +85,22 @@ func TestParseFlags(t *testing.T) { } } + createValidConfigAndPassEnv := func(t *testing.T, ts *test) { + path := createValidTestConfig(t, ts.test) + appendKubeConfigEnv(ts, path) + if ts.expected != nil { + ts.expected.KubeConfig = path + } + } + cases := []test{ { name: "no arguments provide empty config", arguments: nil, - hasErrorContains: "", - hasParseErrorContains: "", - - expected: &Config{}, + hasErrorContains: "", + expected: &Config{}, }, { @@ -80,9 +110,7 @@ func TestParseFlags(t *testing.T) { "--kube-client-from-cluster", }, - hasErrorContains: "", - hasParseErrorContains: "", - + hasErrorContains: "", expected: &Config{ KubeConfigInCluster: true, }, @@ -97,10 +125,8 @@ func TestParseFlags(t *testing.T) { before: createValidConfigAndPassArg, - hasErrorContains: "", - hasParseErrorContains: "", - - expected: &Config{}, + hasErrorContains: "", + expected: &Config{}, }, { @@ -110,10 +136,8 @@ func TestParseFlags(t *testing.T) { before: createValidConfigAndPassArg, - hasErrorContains: "", - hasParseErrorContains: "", - - expected: &Config{}, + hasErrorContains: "", + expected: &Config{}, }, { @@ -125,14 +149,130 @@ func TestParseFlags(t *testing.T) { before: createValidConfigAndPassArg, - hasErrorContains: "", - hasParseErrorContains: "", + hasErrorContains: "", + expected: &Config{ + KubeConfigContext: "clean", + }, + }, + + { + name: "get from env", + + arguments: nil, + + envsPrefix: "MY_ALL", + before: func(t *testing.T, ts *test) { + createValidConfigAndPassEnv(t, ts) + appendEnv(ts, "KUBE_CONFIG_CONTEXT", "clean", false) + }, + + hasErrorContains: "", expected: &Config{ KubeConfigContext: "clean", }, }, + { + name: "rewrite from env", + + arguments: []string{ + "--kubeconfig=/tmp/not_exists_4hr8th8.yaml", + "--kubeconfig-context=incorrect", + }, + + envsPrefix: "MY_REWRITE", + + before: func(t *testing.T, ts *test) { + createValidConfigAndPassEnv(t, ts) + appendEnv(ts, "KUBE_CONFIG_CONTEXT", "clean", false) + }, + + hasErrorContains: "", + expected: &Config{ + KubeConfigContext: "clean", + }, + }, + + { + name: "use os lookup env", + + arguments: []string{ + "--kube-client-from-cluster", + }, + + envsPrefix: "OS_LOOKUP_KUBE", + noUsePredefinedLookup: true, + + before: func(t *testing.T, ts *test) { + tests.SetEnvs(t, map[string]string{ + fmt.Sprintf("%s_KUBE_CLIENT_FROM_CLUSTER", ts.envsPrefix): "yes", + }) + }, + + hasErrorContains: "", + expected: &Config{ + KubeConfigInCluster: true, + }, + }, + + { + name: "get from KUBECONFIG env without passed kubeconfig path", + + arguments: nil, + + envsPrefix: "MY_DEFAULT", + + before: func(t *testing.T, ts *test) { + path := createValidTestConfig(t, ts.test) + appendKubeConfigDefault(ts, path) + ts.expected.KubeConfig = path + }, + + hasErrorContains: "", + expected: &Config{}, + }, + + { + name: "set KUBECONFIG no rewrite passed kubeconfig path", + + arguments: []string{ + "--kubeconfig-context=clean", + }, + + envsPrefix: "MY_DEFAULT_PASSED", + + before: func(t *testing.T, ts *test) { + createValidConfigAndPassArg(t, ts) + appendKubeConfigDefault(ts, "/tmp/not_exists.48t48g.yaml") + }, + + hasErrorContains: "", + expected: &Config{ + KubeConfigContext: "clean", + }, + }, + + { + name: "set KUBECONFIG no rewrite in cluster flag", + + arguments: []string{ + "--kube-client-from-cluster", + }, + + envsPrefix: "MY_DEFAULT_IN_CLUSTER", + + before: func(t *testing.T, ts *test) { + path := createValidTestConfig(t, ts.test) + appendKubeConfigDefault(ts, path) + }, + + hasErrorContains: "", + expected: &Config{ + KubeConfigInCluster: true, + }, + }, + { name: "kubeconfig and in cluster client passed both", @@ -142,8 +282,7 @@ func TestParseFlags(t *testing.T) { before: createValidConfigAndPassArg, - hasErrorContains: "Cannot use both --kubeconfig and --kube-client-from-cluster or envs KUBE_CONFIG and KUBE_CLIENT_FROM_CLUSTER at the same time", - hasParseErrorContains: "", + hasErrorContains: "Cannot use both --kubeconfig and --kube-client-from-cluster or envs KUBE_CONFIG and KUBE_CLIENT_FROM_CLUSTER at the same time", }, { @@ -155,8 +294,7 @@ func TestParseFlags(t *testing.T) { before: createValidConfigAndPassArg, - hasErrorContains: "Cannot find context 'not-exists' in kube config", - hasParseErrorContains: "", + hasErrorContains: "Cannot find context 'not-exists' in kube config", }, { @@ -166,8 +304,7 @@ func TestParseFlags(t *testing.T) { "--kubeconfig-context=not-exists", }, - hasErrorContains: "Pass context flag --kubeconfig-context without kubeconfig path --kubeconfig", - hasParseErrorContains: "", + hasErrorContains: "Pass context flag --kubeconfig-context without kubeconfig path --kubeconfig", }, { @@ -177,8 +314,7 @@ func TestParseFlags(t *testing.T) { appendKubeConfigArgument(ts, "/tmp/not-exsists-2dfr.yaml") }, - hasErrorContains: "Cannot get kube config file info for /tmp/not-exsists-2dfr.yaml", - hasParseErrorContains: "", + hasErrorContains: "Cannot get kube config file info for /tmp/not-exsists-2dfr.yaml", }, { @@ -189,8 +325,22 @@ func TestParseFlags(t *testing.T) { appendKubeConfigArgument(ts, dir) }, - hasErrorContains: "should be regular file", - hasParseErrorContains: "", + hasErrorContains: "should be regular file", + }, + + { + name: "pass kube config and from cluster envs", + + arguments: nil, + + envsPrefix: "MY_KUBE_FROM_CLUSTER", + + before: func(t *testing.T, ts *test) { + createValidConfigAndPassArg(t, ts) + appendEnv(ts, "KUBE_CLIENT_FROM_CLUSTER", "true", false) + }, + + hasErrorContains: "Cannot use both --kubeconfig and --kube-client-from-cluster or envs MY_KUBE_FROM_CLUSTER_KUBE_CONFIG and MY_KUBE_FROM_CLUSTER_KUBE_CLIENT_FROM_CLUSTER at the same time", }, } @@ -220,10 +370,12 @@ func TestParseFlags(t *testing.T) { parser := NewFlagsParser(sett) parser.WithEnvsPrefix(testCase.envsPrefix) if len(testCase.envs) > 0 { - parser.WithEnvsLookup(func(name string) (string, bool) { - val, ok := testCase.envs[name] - return val, ok - }) + if !testCase.noUsePredefinedLookup { + parser.WithEnvsLookup(func(name string) (string, bool) { + val, ok := testCase.envs[name] + return val, ok + }) + } } flagSetName := strings.ReplaceAll(testCase.name, " ", "-") @@ -235,9 +387,7 @@ func TestParseFlags(t *testing.T) { require.NoError(t, err, "init flags") err = flags.Parse(testCase.arguments) - if assertError(t, err, testCase.hasParseErrorContains) { - return - } + require.NoError(t, err, "should parse flags") config, err := parser.ExtractConfigAfterParse(flags) if assertError(t, err, testCase.hasErrorContains) { diff --git a/pkg/utils/env/extractor.go b/pkg/utils/env/extractor.go index d365e37..c9c049e 100644 --- a/pkg/utils/env/extractor.go +++ b/pkg/utils/env/extractor.go @@ -27,7 +27,7 @@ import ( ) type ( - EnvsLookupFunc func(name string) (string, bool) + LookupFunc func(name string) (string, bool) ) // SimplifyPrefix @@ -58,7 +58,7 @@ func NewOsExtractor(prefix string) *Extractor { // if need we use WithPrefixSeparator method for set your own or set to empty // by default slice string extractor split env string by , symbol // if need we use WithSliceSeparator method for set your own slice separator -func NewExtractor(prefix string, lookupFunc EnvsLookupFunc) *Extractor { +func NewExtractor(prefix string, lookupFunc LookupFunc) *Extractor { return &Extractor{ prefixSeparator: "_", sliceSeparator: ",", diff --git a/pkg/utils/flags/base_parser.go b/pkg/utils/flags/base_parser.go index 27572a4..08f2f72 100644 --- a/pkg/utils/flags/base_parser.go +++ b/pkg/utils/flags/base_parser.go @@ -26,7 +26,7 @@ import ( type BaseParser struct { envsPrefix string sett settings.Settings - envsLookup env.EnvsLookupFunc + envsLookup env.LookupFunc } func NewBaseParser(sett settings.Settings) *BaseParser { @@ -47,7 +47,7 @@ func (p *BaseParser) WithEnvsPrefix(envsPrefix string) { p.envsPrefix = env.SimplifyPrefix(envsPrefix) } -func (p *BaseParser) WithEnvsLookup(lookup env.EnvsLookupFunc) { +func (p *BaseParser) WithEnvsLookup(lookup env.LookupFunc) { if govalue.Nil(lookup) { p.sett.Logger().WarnF("Envs lookup function is nil. Skip set ask function.") return