mirror of
https://github.com/alibaba/higress.git
synced 2026-06-26 02:35:02 +08:00
feat: add profile/install/uninstall/upgrade command (#538)
This commit is contained in:
38
go.mod
38
go.mod
@@ -21,13 +21,16 @@ require (
|
|||||||
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5
|
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5
|
||||||
github.com/dubbogo/gost v1.13.1
|
github.com/dubbogo/gost v1.13.1
|
||||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1
|
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1
|
||||||
|
github.com/evanphx/json-patch/v5 v5.6.0
|
||||||
github.com/gogo/protobuf v1.3.2
|
github.com/gogo/protobuf v1.3.2
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/google/go-cmp v0.5.9
|
github.com/google/go-cmp v0.5.9
|
||||||
|
github.com/google/yamlfmt v0.10.0
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||||
github.com/hashicorp/consul/api v1.23.0
|
github.com/hashicorp/consul/api v1.23.0
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/hudl/fargo v1.4.0
|
github.com/hudl/fargo v1.4.0
|
||||||
|
github.com/kylelemons/godebug v1.1.0
|
||||||
github.com/nacos-group/nacos-sdk-go v1.0.8
|
github.com/nacos-group/nacos-sdk-go v1.0.8
|
||||||
github.com/nacos-group/nacos-sdk-go/v2 v2.1.2
|
github.com/nacos-group/nacos-sdk-go/v2 v2.1.2
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
@@ -38,6 +41,7 @@ require (
|
|||||||
google.golang.org/grpc v1.48.0
|
google.golang.org/grpc v1.48.0
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
helm.sh/helm/v3 v3.7.1
|
||||||
istio.io/api v0.0.0-20211122181927-8da52c66ff23
|
istio.io/api v0.0.0-20211122181927-8da52c66ff23
|
||||||
istio.io/client-go v1.12.0-rc.1.0.20211118171212-b744b6f111e4
|
istio.io/client-go v1.12.0-rc.1.0.20211118171212-b744b6f111e4
|
||||||
istio.io/gogo-genproto v0.0.0-20211115195057-0e34bdd2be67
|
istio.io/gogo-genproto v0.0.0-20211115195057-0e34bdd2be67
|
||||||
@@ -63,18 +67,24 @@ require (
|
|||||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||||
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
|
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
|
||||||
|
github.com/Masterminds/squirrel v1.5.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||||
github.com/Microsoft/hcsshim v0.8.21 // indirect
|
github.com/Microsoft/hcsshim v0.8.21 // indirect
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
|
github.com/RageCage64/multilinediff v0.2.0 // indirect
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect
|
||||||
github.com/armon/go-metrics v0.4.1 // indirect
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.41.7 // indirect
|
github.com/aws/aws-sdk-go v1.41.7 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
|
||||||
|
github.com/braydonk/yaml v0.7.0 // indirect
|
||||||
github.com/buger/jsonparser v1.1.1 // indirect
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
||||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
||||||
@@ -82,18 +92,21 @@ require (
|
|||||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
|
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
|
||||||
github.com/clbanning/mxj v1.8.4 // indirect
|
github.com/clbanning/mxj v1.8.4 // indirect
|
||||||
github.com/cncf/xds/go v0.0.0-20220520190051-1e77728a1eaa // indirect
|
github.com/cncf/xds/go v0.0.0-20220520190051-1e77728a1eaa // indirect
|
||||||
|
github.com/containerd/containerd v1.5.7 // indirect
|
||||||
github.com/containerd/continuity v0.1.0 // indirect
|
github.com/containerd/continuity v0.1.0 // indirect
|
||||||
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
|
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
|
||||||
|
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 // indirect
|
||||||
github.com/docker/cli v20.10.7+incompatible // indirect
|
github.com/docker/cli v20.10.7+incompatible // indirect
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.7+incompatible // indirect
|
github.com/docker/docker v20.10.7+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.6.3 // indirect
|
github.com/docker/docker-credential-helpers v0.6.3 // indirect
|
||||||
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
|
github.com/docker/go-metrics v0.0.1 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect
|
github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect
|
||||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
|
||||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||||
github.com/fatih/color v1.14.1 // indirect
|
github.com/fatih/color v1.14.1 // indirect
|
||||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 // indirect
|
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 // indirect
|
||||||
@@ -107,10 +120,12 @@ require (
|
|||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||||
github.com/go-openapi/swag v0.19.14 // indirect
|
github.com/go-openapi/swag v0.19.14 // indirect
|
||||||
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
github.com/goccy/go-json v0.4.8 // indirect
|
github.com/goccy/go-json v0.4.8 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
|
github.com/golang/snappy v0.0.3 // indirect
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/google/go-containerregistry v0.6.0 // indirect
|
github.com/google/go-containerregistry v0.6.0 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
@@ -118,6 +133,8 @@ require (
|
|||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||||
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
|
github.com/gosuri/uitable v0.0.4 // indirect
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
@@ -132,9 +149,15 @@ require (
|
|||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/jmoiron/sqlx v1.3.1 // indirect
|
||||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.13.0 // indirect
|
||||||
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect
|
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect
|
||||||
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
|
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
|
||||||
github.com/lestrrat-go/httpcc v1.0.0 // indirect
|
github.com/lestrrat-go/httpcc v1.0.0 // indirect
|
||||||
@@ -143,10 +166,12 @@ require (
|
|||||||
github.com/lestrrat-go/option v1.0.0 // indirect
|
github.com/lestrrat-go/option v1.0.0 // indirect
|
||||||
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect
|
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect
|
||||||
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
|
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
|
||||||
|
github.com/lib/pq v1.10.0 // indirect
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||||
github.com/mailru/easyjson v0.7.6 // indirect
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.12 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
github.com/miekg/dns v1.1.43 // indirect
|
github.com/miekg/dns v1.1.43 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
@@ -154,11 +179,13 @@ require (
|
|||||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/moby/locker v1.0.1 // indirect
|
||||||
github.com/moby/spdystream v0.2.0 // indirect
|
github.com/moby/spdystream v0.2.0 // indirect
|
||||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
|
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
|
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
@@ -172,12 +199,18 @@ require (
|
|||||||
github.com/prometheus/common v0.32.1 // indirect
|
github.com/prometheus/common v0.32.1 // indirect
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
github.com/prometheus/statsd_exporter v0.21.0 // indirect
|
github.com/prometheus/statsd_exporter v0.21.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1 // indirect
|
||||||
|
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect
|
||||||
github.com/russross/blackfriday v1.5.2 // indirect
|
github.com/russross/blackfriday v1.5.2 // indirect
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
github.com/shopspring/decimal v1.2.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
|
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
||||||
github.com/yl2chen/cidranger v1.0.2 // indirect
|
github.com/yl2chen/cidranger v1.0.2 // indirect
|
||||||
go.opencensus.io v0.23.0 // indirect
|
go.opencensus.io v0.23.0 // indirect
|
||||||
@@ -201,6 +234,7 @@ require (
|
|||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a // indirect
|
google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a // indirect
|
||||||
gopkg.in/gcfg.v1 v1.2.3 // indirect
|
gopkg.in/gcfg.v1 v1.2.3 // indirect
|
||||||
|
gopkg.in/gorp.v1 v1.7.2 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
@@ -208,10 +242,12 @@ require (
|
|||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/apiextensions-apiserver v0.22.2 // indirect
|
k8s.io/apiextensions-apiserver v0.22.2 // indirect
|
||||||
|
k8s.io/apiserver v0.22.2 // indirect
|
||||||
k8s.io/component-base v0.22.2 // indirect
|
k8s.io/component-base v0.22.2 // indirect
|
||||||
k8s.io/klog/v2 v2.10.0 // indirect
|
k8s.io/klog/v2 v2.10.0 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20211020163157-7327e2aaee2b // indirect
|
k8s.io/kube-openapi v0.0.0-20211020163157-7327e2aaee2b // indirect
|
||||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||||
|
oras.land/oras-go v0.4.0 // indirect
|
||||||
sigs.k8s.io/gateway-api v0.4.0 // indirect
|
sigs.k8s.io/gateway-api v0.4.0 // indirect
|
||||||
sigs.k8s.io/kustomize/api v0.8.11 // indirect
|
sigs.k8s.io/kustomize/api v0.8.11 // indirect
|
||||||
sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect
|
sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect
|
||||||
|
|||||||
55
go.sum
55
go.sum
@@ -102,6 +102,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
|
|||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
@@ -116,6 +117,7 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
|
|||||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||||
|
github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8=
|
||||||
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
||||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
@@ -150,6 +152,9 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
|||||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
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/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/RageCage64/multilinediff v0.2.0 h1:yNSpSF5NXIrmo6bRIgO4Q0g7SXqFD4j/WEcBE+BdCFY=
|
||||||
|
github.com/RageCage64/multilinediff v0.2.0/go.mod h1:pKr+KLgP0gvRzA+yv0/IUaYQuBYN1ucWysvsL58aMP0=
|
||||||
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
@@ -187,6 +192,7 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
|
|||||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||||
github.com/avast/retry-go/v4 v4.3.4 h1:pHLkL7jvCvP317I8Ge+Km2Yhntv3SdkJm7uekkqbKhM=
|
github.com/avast/retry-go/v4 v4.3.4 h1:pHLkL7jvCvP317I8Ge+Km2Yhntv3SdkJm7uekkqbKhM=
|
||||||
github.com/avast/retry-go/v4 v4.3.4/go.mod h1:rv+Nla6Vk3/ilU0H51VHddWHiwimzX66yZ0JT6T+UvE=
|
github.com/avast/retry-go/v4 v4.3.4/go.mod h1:rv+Nla6Vk3/ilU0H51VHddWHiwimzX66yZ0JT6T+UvE=
|
||||||
@@ -211,16 +217,24 @@ github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY
|
|||||||
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
|
github.com/braydonk/yaml v0.7.0 h1:ySkqO7r0MGoCNhiRJqE0Xe9yhINMyvOAB3nFjgyJn2k=
|
||||||
|
github.com/braydonk/yaml v0.7.0/go.mod h1:hcm3h581tudlirk8XEUPDBAimBPbmnL0Y45hCRl47N4=
|
||||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||||
|
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
|
||||||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||||
|
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng=
|
||||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||||
|
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=
|
||||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||||
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
@@ -285,6 +299,7 @@ github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1
|
|||||||
github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
|
github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
|
||||||
github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
|
github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
|
||||||
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
|
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
|
||||||
|
github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ=
|
||||||
github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=
|
github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=
|
||||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||||
@@ -305,6 +320,7 @@ github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09Zvgq
|
|||||||
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
|
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
|
||||||
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
|
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
|
||||||
github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
|
github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
|
||||||
|
github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM=
|
||||||
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
|
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
|
||||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
@@ -408,6 +424,7 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l
|
|||||||
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3/go.mod h1:gt38b7cvVKazi5XkHvINNytZXgTEntyhtyM3HQz46Nk=
|
github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3/go.mod h1:gt38b7cvVKazi5XkHvINNytZXgTEntyhtyM3HQz46Nk=
|
||||||
|
github.com/distribution/distribution/v3 v3.0.0-20210926092439-1563384b69df h1:zafDqOsnugdrReF9Pe0wybnfFtEIaegSyHNIvnwKPVk=
|
||||||
github.com/distribution/distribution/v3 v3.0.0-20210926092439-1563384b69df/go.mod h1:ZDZib/BOniVWcXcsy0voU8gR00znhe5VJm47d3H2Y5g=
|
github.com/distribution/distribution/v3 v3.0.0-20210926092439-1563384b69df/go.mod h1:ZDZib/BOniVWcXcsy0voU8gR00znhe5VJm47d3H2Y5g=
|
||||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||||
github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is=
|
github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is=
|
||||||
@@ -416,14 +433,18 @@ github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx
|
|||||||
github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||||
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
|
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
|
||||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||||
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||||
|
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
|
||||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||||
|
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
|
||||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
@@ -464,6 +485,7 @@ github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE
|
|||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/florianl/go-nflog/v2 v2.0.1/go.mod h1:g+SOgM/SuePn9bvS/eo3Ild7J71nSb29OzbxR+7cln0=
|
github.com/florianl/go-nflog/v2 v2.0.1/go.mod h1:g+SOgM/SuePn9bvS/eo3Ild7J71nSb29OzbxR+7cln0=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
@@ -483,6 +505,7 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
|
|||||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||||
github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE=
|
github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE=
|
||||||
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
|
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko=
|
||||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
@@ -565,6 +588,7 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+
|
|||||||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
||||||
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
@@ -572,8 +596,11 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8Wd
|
|||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
|
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
|
||||||
github.com/gobuffalo/flect v0.2.3/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
|
github.com/gobuffalo/flect v0.2.3/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
|
||||||
|
github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc=
|
||||||
github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=
|
github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=
|
||||||
|
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
|
||||||
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
|
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
|
||||||
|
github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA=
|
||||||
github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg=
|
github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
@@ -646,6 +673,7 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
|
|||||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
|
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
|
||||||
|
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
|
||||||
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
@@ -700,6 +728,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/yamlfmt v0.10.0 h1:0eR+Z3ZhkJ4uYIpEU/BcxpnqtkNDq8eCxon/Sj0YeRc=
|
||||||
|
github.com/google/yamlfmt v0.10.0/go.mod h1:jW0ice5/S1EBCHhIV9rkGVfUjyCXD1cTlddkKwI8TKo=
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
@@ -719,6 +749,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
|
|||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
|
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
@@ -729,6 +760,7 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
|
|||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
|
||||||
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
|
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
@@ -824,6 +856,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
|||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
|
github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE=
|
||||||
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||||
github.com/johnlanni/gost v1.11.23-0.20220713132522-0967a24036c6 h1:i9IP6menkNYRAOJQ27+81deRmcyyirLZRXR5+BIilV0=
|
github.com/johnlanni/gost v1.11.23-0.20220713132522-0967a24036c6 h1:i9IP6menkNYRAOJQ27+81deRmcyyirLZRXR5+BIilV0=
|
||||||
github.com/johnlanni/gost v1.11.23-0.20220713132522-0967a24036c6/go.mod h1:PhJ8+qZJx+Txjx1KthNPuVkCvUca0jRLgKWj/noGgeI=
|
github.com/johnlanni/gost v1.11.23-0.20220713132522-0967a24036c6/go.mod h1:PhJ8+qZJx+Txjx1KthNPuVkCvUca0jRLgKWj/noGgeI=
|
||||||
@@ -859,6 +892,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
|||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||||
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
|
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
|
||||||
|
github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs=
|
||||||
github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
@@ -887,7 +921,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.7 h1:i2SeK33aOFJlUNJZzf2IpXRBvqBBnaGXfY5Xaop/GsE=
|
github.com/lestrrat-go/backoff/v2 v2.0.7 h1:i2SeK33aOFJlUNJZzf2IpXRBvqBBnaGXfY5Xaop/GsE=
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||||
@@ -912,6 +948,7 @@ github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod
|
|||||||
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc=
|
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc=
|
||||||
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0=
|
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
||||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||||
@@ -933,8 +970,11 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
|
|||||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
|
||||||
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
|
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
|
||||||
|
github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
|
||||||
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
|
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
|
||||||
|
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
||||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||||
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
||||||
@@ -967,10 +1007,12 @@ github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mN
|
|||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||||
@@ -1020,12 +1062,14 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
|
|||||||
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||||
github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:NT0cwArZg/wGdvY8pzej4tPr+9WGmDdkF8Suj+mkz2g=
|
github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:NT0cwArZg/wGdvY8pzej4tPr+9WGmDdkF8Suj+mkz2g=
|
||||||
github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
|
github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
|
||||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||||
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||||
|
github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM=
|
||||||
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||||
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
||||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||||
@@ -1041,6 +1085,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
||||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
@@ -1150,6 +1195,7 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap
|
|||||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
@@ -1222,6 +1268,7 @@ github.com/prometheus/statsd_exporter v0.21.0 h1:hA05Q5RFeIjgwKIYEdFd59xu5Wwaznf
|
|||||||
github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ=
|
github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ=
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
@@ -1229,6 +1276,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||||||
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc h1:BD7uZqkN8CpjJtN/tScAKiccBikU4dlqe/gNrkRaPY4=
|
||||||
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo=
|
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo=
|
||||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
@@ -1394,9 +1442,13 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
|
||||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||||
|
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
|
||||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||||
|
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
|
||||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||||
|
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
@@ -2103,6 +2155,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||||||
gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
|
gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
|
||||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||||
|
gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
|
||||||
gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
|
gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
@@ -2194,6 +2247,7 @@ k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
|
|||||||
k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
|
k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
|
||||||
k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU=
|
k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU=
|
||||||
k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
|
k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
|
||||||
|
k8s.io/apiserver v0.22.2 h1:TdIfZJc6YNhu2WxeAOWq1TvukHF0Sfx0+ln4XK9qnL4=
|
||||||
k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI=
|
k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI=
|
||||||
k8s.io/cli-runtime v0.22.1/go.mod h1:YqwGrlXeEk15Yn3em2xzr435UGwbrCw5x+COQoTYfoo=
|
k8s.io/cli-runtime v0.22.1/go.mod h1:YqwGrlXeEk15Yn3em2xzr435UGwbrCw5x+COQoTYfoo=
|
||||||
k8s.io/cli-runtime v0.22.2 h1:fsd9rFk9FSaVq4SUq1fM27c8CFGsYZUJ/3BkgmjYWuY=
|
k8s.io/cli-runtime v0.22.2 h1:fsd9rFk9FSaVq4SUq1fM27c8CFGsYZUJ/3BkgmjYWuY=
|
||||||
@@ -2272,6 +2326,7 @@ k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
|
|||||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
|
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
|
||||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
|
oras.land/oras-go v0.4.0 h1:u6+7D+raZDYHwlz/uOwNANiRmyYDSSMW7A9E1xXycUQ=
|
||||||
oras.land/oras-go v0.4.0/go.mod h1:VJcU+VE4rkclUbum5C0O7deEZbBYnsnpbGSACwTjOcg=
|
oras.land/oras-go v0.4.0/go.mod h1:VJcU+VE4rkclUbum5C0O7deEZbBYnsnpbGSACwTjOcg=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
|
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
|
||||||
|
|||||||
21
pkg/cmd/hgctl/common.go
Normal file
21
pkg/cmd/hgctl/common.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 hgctl
|
||||||
|
|
||||||
|
const (
|
||||||
|
yamlOutput = "yaml"
|
||||||
|
jsonOutput = "json"
|
||||||
|
flagsOutput = "flags"
|
||||||
|
)
|
||||||
297
pkg/cmd/hgctl/helm/common.go
Normal file
297
pkg/cmd/hgctl/helm/common.go
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 helm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/tpath"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadYamlProfile gets the overlay yaml file from list of files and return profile value from file overlay and set overlay.
|
||||||
|
func ReadYamlProfile(inFilenames []string, setFlags []string) (string, string, error) {
|
||||||
|
profileName := DefaultProfileName
|
||||||
|
// The profile coming from --set flag has the highest precedence.
|
||||||
|
psf := GetValueForSetFlag(setFlags, "profile")
|
||||||
|
if psf != "" {
|
||||||
|
profileName = psf
|
||||||
|
}
|
||||||
|
return "", profileName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUninstallProfileName() string {
|
||||||
|
return DefaultUninstallProfileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadLayeredYAMLs(filenames []string) (string, error) {
|
||||||
|
return readLayeredYAMLs(filenames, os.Stdin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLayeredYAMLs(filenames []string, stdinReader io.Reader) (string, error) {
|
||||||
|
var ly string
|
||||||
|
var stdin bool
|
||||||
|
for _, fn := range filenames {
|
||||||
|
var b []byte
|
||||||
|
var err error
|
||||||
|
if fn == "-" {
|
||||||
|
if stdin {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stdin = true
|
||||||
|
b, err = io.ReadAll(stdinReader)
|
||||||
|
} else {
|
||||||
|
b, err = os.ReadFile(strings.TrimSpace(fn))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ly, err = util.OverlayYAML(ly, string(b))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ly, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValueForSetFlag parses the passed set flags which have format key=value and if any set the given path,
|
||||||
|
// returns the corresponding value, otherwise returns the empty string. setFlags must have valid format.
|
||||||
|
func GetValueForSetFlag(setFlags []string, path string) string {
|
||||||
|
ret := ""
|
||||||
|
for _, sf := range setFlags {
|
||||||
|
p, v := getPV(sf)
|
||||||
|
if p == path {
|
||||||
|
ret = v
|
||||||
|
}
|
||||||
|
// if set multiple times, return last set value
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPV returns the path and value components for the given set flag string, which must be in path=value format.
|
||||||
|
func getPV(setFlag string) (path string, value string) {
|
||||||
|
pv := strings.Split(setFlag, "=")
|
||||||
|
if len(pv) != 2 {
|
||||||
|
return setFlag, ""
|
||||||
|
}
|
||||||
|
path, value = strings.TrimSpace(pv[0]), strings.TrimSpace(pv[1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateConfig(inFilenames []string, setFlags []string) (string, *Profile, string, error) {
|
||||||
|
if err := validateSetFlags(setFlags); err != nil {
|
||||||
|
return "", nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fy, profileName, err := ReadYamlProfile(inFilenames, setFlags)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
profileString, profile, err := GenProfile(profileName, fy, setFlags)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return profileString, profile, profileName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSetFlags validates that setFlags all have path=value format.
|
||||||
|
func validateSetFlags(setFlags []string) error {
|
||||||
|
for _, sf := range setFlags {
|
||||||
|
pv := strings.Split(sf, "=")
|
||||||
|
if len(pv) != 2 {
|
||||||
|
return fmt.Errorf("set flag %s has incorrect format, must be path=value", sf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func overlaySetFlagValues(iopYAML string, setFlags []string) (string, error) {
|
||||||
|
iop := make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(iopYAML), &iop); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Unmarshal returns nil for empty manifests but we need something to insert into.
|
||||||
|
if iop == nil {
|
||||||
|
iop = make(map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sf := range setFlags {
|
||||||
|
p, v := getPV(sf)
|
||||||
|
inc, _, err := tpath.GetPathContext(iop, util.PathFromString(p), true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// input value type is always string, transform it to correct type before setting.
|
||||||
|
if err := tpath.WritePathContext(inc, util.ParseValue(v), false); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := yaml.Marshal(iop)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInstallPackagePath returns the installPackagePath in the given IstioOperator YAML string.
|
||||||
|
func getInstallPackagePath(profileYAML string) (string, error) {
|
||||||
|
profile, err := UnmarshalProfile(profileYAML)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if profile == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return profile.InstallPackagePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfileYAML returns the YAML for the given profile name, using the given profileOrPath string, which may be either
|
||||||
|
// a profile label or a file path.
|
||||||
|
func GetProfileYAML(installPackagePath, profileOrPath string) (string, error) {
|
||||||
|
if profileOrPath == "" {
|
||||||
|
profileOrPath = DefaultProfileFilename
|
||||||
|
}
|
||||||
|
profiles, err := readProfiles(installPackagePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read profiles: %v", err)
|
||||||
|
}
|
||||||
|
// If charts are a file path and profile is a name like default, transform it to the file path.
|
||||||
|
if profiles[profileOrPath] && installPackagePath != "" {
|
||||||
|
profileOrPath = filepath.Join(installPackagePath, "profiles", profileOrPath+".yaml")
|
||||||
|
}
|
||||||
|
// This contains the IstioOperator CR.
|
||||||
|
baseCRYAML, err := ReadProfileYAML(profileOrPath, installPackagePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
//if !IsDefaultProfile(profileOrPath) {
|
||||||
|
// // Profile definitions are relative to the default profileOrPath, so read that first.
|
||||||
|
// dfn := DefaultFilenameForProfile(profileOrPath)
|
||||||
|
// defaultYAML, err := ReadProfileYAML(dfn, installPackagePath)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
// baseCRYAML, err = util.OverlayYAML(defaultYAML, baseCRYAML)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
return baseCRYAML, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDefaultProfile reports whether the given profile is the default profile.
|
||||||
|
func IsDefaultProfile(profile string) bool {
|
||||||
|
return profile == "" || profile == DefaultProfileName || filepath.Base(profile) == DefaultProfileFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFilenameForProfile returns the profile name of the default profile for the given profile.
|
||||||
|
func DefaultFilenameForProfile(profile string) string {
|
||||||
|
switch {
|
||||||
|
case util.IsFilePath(profile):
|
||||||
|
return filepath.Join(filepath.Dir(profile), DefaultProfileFilename)
|
||||||
|
default:
|
||||||
|
return DefaultProfileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadProfileYAML reads the YAML values associated with the given profile. It uses an appropriate reader for the
|
||||||
|
// profile format (compiled-in, file, HTTP, etc.).
|
||||||
|
func ReadProfileYAML(profile, manifestsPath string) (string, error) {
|
||||||
|
var err error
|
||||||
|
var globalValues string
|
||||||
|
|
||||||
|
// Get global values from profile.
|
||||||
|
switch {
|
||||||
|
case util.IsFilePath(profile):
|
||||||
|
if globalValues, err = readFile(profile); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if globalValues, err = LoadValues(profile, manifestsPath); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read profile %v from %v: %v", profile, manifestsPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return globalValues, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(path string) (string, error) {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalProfile unmarshals a string containing Profile as YAML.
|
||||||
|
func UnmarshalProfile(profileYAML string) (*Profile, error) {
|
||||||
|
profile := &Profile{}
|
||||||
|
if err := yaml.Unmarshal([]byte(profileYAML), profile); err != nil {
|
||||||
|
return nil, fmt.Errorf("%s:\n\nYAML:\n%s", err, profileYAML)
|
||||||
|
}
|
||||||
|
return profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenProfile generates an Profile from the given profile name or path, and overlay YAMLs from user
|
||||||
|
// files and the --set flag. If successful, it returns an Profile string and struct.
|
||||||
|
func GenProfile(profileOrPath, fileOverlayYAML string, setFlags []string) (string, *Profile, error) {
|
||||||
|
installPackagePath, err := getInstallPackagePath(fileOverlayYAML)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
if sfp := GetValueForSetFlag(setFlags, "installPackagePath"); sfp != "" {
|
||||||
|
// set flag installPackagePath has the highest precedence, if set.
|
||||||
|
installPackagePath = sfp
|
||||||
|
}
|
||||||
|
|
||||||
|
// To generate the base profileOrPath for overlaying with user values, we need the installPackagePath where the profiles
|
||||||
|
// can be found, and the selected profileOrPath. Both of these can come from either the user overlay file or --set flag.
|
||||||
|
outYAML, err := GetProfileYAML(installPackagePath, profileOrPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine file and --set overlays and translate any K8s settings in values to Profile format
|
||||||
|
overlayYAML, err := overlaySetFlagValues(fileOverlayYAML, setFlags)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
// Merge user file and --set flags.
|
||||||
|
outYAML, err = util.OverlayYAML(outYAML, overlayYAML)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("could not overlay user config over base: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalProfile, err := UnmarshalProfile(outYAML)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
finalProfile.InstallPackagePath = installPackagePath
|
||||||
|
|
||||||
|
if finalProfile.Profile == "" {
|
||||||
|
finalProfile.Profile = DefaultProfileName
|
||||||
|
}
|
||||||
|
return util.ToYAML(finalProfile), finalProfile, nil
|
||||||
|
}
|
||||||
63
pkg/cmd/hgctl/helm/name/name.go
Normal file
63
pkg/cmd/hgctl/helm/name/name.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 name
|
||||||
|
|
||||||
|
// Kubernetes Kind strings.
|
||||||
|
const (
|
||||||
|
CRDStr = "CustomResourceDefinition"
|
||||||
|
ClusterRoleStr = "ClusterRole"
|
||||||
|
ClusterRoleBindingStr = "ClusterRoleBinding"
|
||||||
|
CMStr = "ConfigMap"
|
||||||
|
DaemonSetStr = "DaemonSet"
|
||||||
|
DeploymentStr = "Deployment"
|
||||||
|
EndpointStr = "Endpoints"
|
||||||
|
HPAStr = "HorizontalPodAutoscaler"
|
||||||
|
IngressStr = "Ingress"
|
||||||
|
IstioOperator = "IstioOperator"
|
||||||
|
MutatingWebhookConfigurationStr = "MutatingWebhookConfiguration"
|
||||||
|
NamespaceStr = "Namespace"
|
||||||
|
PVCStr = "PersistentVolumeClaim"
|
||||||
|
PodStr = "Pod"
|
||||||
|
PDBStr = "PodDisruptionBudget"
|
||||||
|
ReplicationControllerStr = "ReplicationController"
|
||||||
|
ReplicaSetStr = "ReplicaSet"
|
||||||
|
RoleStr = "Role"
|
||||||
|
RoleBindingStr = "RoleBinding"
|
||||||
|
SAStr = "ServiceAccount"
|
||||||
|
ServiceStr = "Service"
|
||||||
|
SecretStr = "Secret"
|
||||||
|
StatefulSetStr = "StatefulSet"
|
||||||
|
ValidatingWebhookConfigurationStr = "ValidatingWebhookConfiguration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Istio Kind strings
|
||||||
|
const (
|
||||||
|
EnvoyFilterStr = "EnvoyFilter"
|
||||||
|
GatewayStr = "Gateway"
|
||||||
|
DestinationRuleStr = "DestinationRule"
|
||||||
|
MeshPolicyStr = "MeshPolicy"
|
||||||
|
PeerAuthenticationStr = "PeerAuthentication"
|
||||||
|
VirtualServiceStr = "VirtualService"
|
||||||
|
IstioOperatorStr = "IstioOperator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Istio API Group Names
|
||||||
|
const (
|
||||||
|
AuthenticationAPIGroupName = "authentication.istio.io"
|
||||||
|
ConfigAPIGroupName = "config.istio.io"
|
||||||
|
NetworkingAPIGroupName = "networking.istio.io"
|
||||||
|
OperatorAPIGroupName = "operator.istio.io"
|
||||||
|
SecurityAPIGroupName = "security.istio.io"
|
||||||
|
)
|
||||||
573
pkg/cmd/hgctl/helm/object/objects.go
Normal file
573
pkg/cmd/hgctl/helm/object/objects.go
Normal file
@@ -0,0 +1,573 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
names "github.com/alibaba/higress/pkg/cmd/hgctl/helm/name"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/tpath"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// YAMLSeparator is a separator for multi-document YAML files.
|
||||||
|
YAMLSeparator = "\n---\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// K8sObject is an in-memory representation of a k8s object, used for moving between different representations
|
||||||
|
// (Unstructured, JSON, YAML) with cached rendering.
|
||||||
|
type K8sObject struct {
|
||||||
|
object *unstructured.Unstructured
|
||||||
|
|
||||||
|
Group string
|
||||||
|
Kind string
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
|
||||||
|
json []byte
|
||||||
|
yaml []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewK8sObject creates a new K8sObject and returns a ptr to it.
|
||||||
|
func NewK8sObject(u *unstructured.Unstructured, json, yaml []byte) *K8sObject {
|
||||||
|
o := &K8sObject{
|
||||||
|
object: u,
|
||||||
|
json: json,
|
||||||
|
yaml: yaml,
|
||||||
|
}
|
||||||
|
|
||||||
|
gvk := u.GetObjectKind().GroupVersionKind()
|
||||||
|
o.Group = gvk.Group
|
||||||
|
o.Kind = gvk.Kind
|
||||||
|
o.Name = u.GetName()
|
||||||
|
o.Namespace = u.GetNamespace()
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns a unique, insecure hash based on kind, namespace and name.
|
||||||
|
func Hash(kind, namespace, name string) string {
|
||||||
|
switch kind {
|
||||||
|
case names.ClusterRoleStr, names.ClusterRoleBindingStr, names.MeshPolicyStr:
|
||||||
|
namespace = ""
|
||||||
|
}
|
||||||
|
return strings.Join([]string{kind, namespace, name}, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromHash parses kind, namespace and name from a hash.
|
||||||
|
func FromHash(hash string) (kind, namespace, name string) {
|
||||||
|
hv := strings.Split(hash, ":")
|
||||||
|
if len(hv) != 3 {
|
||||||
|
return "Bad hash string: " + hash, "", ""
|
||||||
|
}
|
||||||
|
kind, namespace, name = hv[0], hv[1], hv[2]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashNameKind returns a unique, insecure hash based on kind and name.
|
||||||
|
func HashNameKind(kind, name string) string {
|
||||||
|
return strings.Join([]string{kind, name}, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSONToK8sObject parses JSON to an K8sObject.
|
||||||
|
func ParseJSONToK8sObject(json []byte) (*K8sObject, error) {
|
||||||
|
o, _, err := unstructured.UnstructuredJSONScheme.Decode(json, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing json into unstructured object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, ok := o.(*unstructured.Unstructured)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("parsed unexpected type %T", o)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewK8sObject(u, json, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseYAMLToK8sObject parses YAML to an Object.
|
||||||
|
func ParseYAMLToK8sObject(yaml []byte) (*K8sObject, error) {
|
||||||
|
r := bytes.NewReader(yaml)
|
||||||
|
decoder := k8syaml.NewYAMLOrJSONDecoder(r, 1024)
|
||||||
|
|
||||||
|
out := &unstructured.Unstructured{}
|
||||||
|
err := decoder.Decode(out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding object %v: %v", string(yaml), err)
|
||||||
|
}
|
||||||
|
return NewK8sObject(out, nil, yaml), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnstructuredObject exposes the raw object, primarily for testing
|
||||||
|
func (o *K8sObject) UnstructuredObject() *unstructured.Unstructured {
|
||||||
|
return o.object
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveK8sConflict - This method resolves k8s object possible
|
||||||
|
// conflicting settings. Which K8sObjects may need such method
|
||||||
|
// depends on the type of the K8sObject.
|
||||||
|
func (o *K8sObject) ResolveK8sConflict() *K8sObject {
|
||||||
|
if o.Kind == names.PDBStr {
|
||||||
|
return resolvePDBConflict(o)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unstructured exposes the raw object content, primarily for testing
|
||||||
|
func (o *K8sObject) Unstructured() map[string]any {
|
||||||
|
return o.UnstructuredObject().UnstructuredContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container returns a container subtree for Deployment objects if one is found, or nil otherwise.
|
||||||
|
func (o *K8sObject) Container(name string) map[string]any {
|
||||||
|
u := o.Unstructured()
|
||||||
|
path := fmt.Sprintf("spec.template.spec.containers.[name:%s]", name)
|
||||||
|
node, f, err := tpath.GetPathContext(u, util.PathFromString(path), false)
|
||||||
|
if err == nil && f {
|
||||||
|
// Must be the type from the schema.
|
||||||
|
return node.Node.(map[string]any)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupVersionKind returns the GroupVersionKind for the K8sObject
|
||||||
|
func (o *K8sObject) GroupVersionKind() schema.GroupVersionKind {
|
||||||
|
return o.object.GroupVersionKind()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the APIVersion of the K8sObject
|
||||||
|
func (o *K8sObject) Version() string {
|
||||||
|
return o.object.GetAPIVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns a unique hash for the K8sObject
|
||||||
|
func (o *K8sObject) Hash() string {
|
||||||
|
return Hash(o.Kind, o.Namespace, o.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashNameKind returns a hash for the K8sObject based on the name and kind only.
|
||||||
|
func (o *K8sObject) HashNameKind() string {
|
||||||
|
return HashNameKind(o.Kind, o.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON returns a JSON representation of the K8sObject, using an internal cache.
|
||||||
|
func (o *K8sObject) JSON() ([]byte, error) {
|
||||||
|
if o.json != nil {
|
||||||
|
return o.json, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := o.object.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAML returns a YAML representation of the K8sObject, using an internal cache.
|
||||||
|
func (o *K8sObject) YAML() ([]byte, error) {
|
||||||
|
if o == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if o.yaml != nil {
|
||||||
|
return o.yaml, nil
|
||||||
|
}
|
||||||
|
oj, err := o.JSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o.json = oj
|
||||||
|
y, err := yaml.JSONToYAML(oj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o.yaml = y
|
||||||
|
return y, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLDebugString returns a YAML representation of the K8sObject, or an error string if the K8sObject cannot be rendered to YAML.
|
||||||
|
func (o *K8sObject) YAMLDebugString() string {
|
||||||
|
y, err := o.YAML()
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return string(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// K8sObjects holds a collection of k8s objects, so that we can filter / sequence them
|
||||||
|
type K8sObjects []*K8sObject
|
||||||
|
|
||||||
|
// String implements the Stringer interface.
|
||||||
|
func (os K8sObjects) String() string {
|
||||||
|
var out []string
|
||||||
|
for _, oo := range os {
|
||||||
|
out = append(out, oo.YAMLDebugString())
|
||||||
|
}
|
||||||
|
return strings.Join(out, YAMLSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns a slice with the keys of os.
|
||||||
|
func (os K8sObjects) Keys() []string {
|
||||||
|
var out []string
|
||||||
|
for _, oo := range os {
|
||||||
|
out = append(out, oo.Hash())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnstructuredItems returns the list of items of unstructured.Unstructured.
|
||||||
|
func (os K8sObjects) UnstructuredItems() []unstructured.Unstructured {
|
||||||
|
var usList []unstructured.Unstructured
|
||||||
|
for _, obj := range os {
|
||||||
|
usList = append(usList, *obj.UnstructuredObject())
|
||||||
|
}
|
||||||
|
return usList
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseK8sObjectsFromYAMLManifest returns a K8sObjects representation of manifest.
|
||||||
|
func ParseK8sObjectsFromYAMLManifest(manifest string) (K8sObjects, error) {
|
||||||
|
return ParseK8sObjectsFromYAMLManifestFailOption(manifest, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseK8sObjectsFromYAMLManifestFailOption returns a K8sObjects representation of manifest. Continues parsing when a bad object
|
||||||
|
// is found if failOnError is set to false.
|
||||||
|
func ParseK8sObjectsFromYAMLManifestFailOption(manifest string, failOnError bool) (K8sObjects, error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
var yamls []string
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(manifest))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "---") {
|
||||||
|
// yaml separator
|
||||||
|
yamls = append(yamls, b.String())
|
||||||
|
b.Reset()
|
||||||
|
} else {
|
||||||
|
if _, err := b.WriteString(line); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := b.WriteString("\n"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yamls = append(yamls, b.String())
|
||||||
|
|
||||||
|
var objects K8sObjects
|
||||||
|
|
||||||
|
for _, yaml := range yamls {
|
||||||
|
yaml = removeNonYAMLLines(yaml)
|
||||||
|
if yaml == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
o, err := ParseYAMLToK8sObject([]byte(yaml))
|
||||||
|
if err != nil {
|
||||||
|
e := fmt.Errorf("failed to parse YAML to a k8s object: %s", err)
|
||||||
|
if failOnError {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if o.Valid() {
|
||||||
|
objects = append(objects, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeNonYAMLLines(yms string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
for _, s := range strings.Split(yms, "\n") {
|
||||||
|
if strings.HasPrefix(s, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.WriteString(s)
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// helm charts sometimes emits blank objects with just a "disabled" comment.
|
||||||
|
return strings.TrimSpace(b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLManifest returns a YAML representation of K8sObjects os.
|
||||||
|
func (os K8sObjects) YAMLManifest() (string, error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
for i, item := range os {
|
||||||
|
if i != 0 {
|
||||||
|
if _, err := b.WriteString("\n\n"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ym, err := item.YAML()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error building yaml: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := b.Write(ym); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := b.Write([]byte(YAMLSeparator)); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort will order the items in K8sObjects in order of score, group, kind, name. The intent is to
|
||||||
|
// have a deterministic ordering in which K8sObjects are applied.
|
||||||
|
func (os K8sObjects) Sort(score func(o *K8sObject) int) {
|
||||||
|
sort.Slice(os, func(i, j int) bool {
|
||||||
|
iScore := score(os[i])
|
||||||
|
jScore := score(os[j])
|
||||||
|
return iScore < jScore ||
|
||||||
|
(iScore == jScore &&
|
||||||
|
os[i].Group < os[j].Group) ||
|
||||||
|
(iScore == jScore &&
|
||||||
|
os[i].Group == os[j].Group &&
|
||||||
|
os[i].Kind < os[j].Kind) ||
|
||||||
|
(iScore == jScore &&
|
||||||
|
os[i].Group == os[j].Group &&
|
||||||
|
os[i].Kind == os[j].Kind &&
|
||||||
|
os[i].Name < os[j].Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMap returns a map of K8sObject hash to K8sObject.
|
||||||
|
func (os K8sObjects) ToMap() map[string]*K8sObject {
|
||||||
|
ret := make(map[string]*K8sObject)
|
||||||
|
for _, oo := range os {
|
||||||
|
if oo.Valid() {
|
||||||
|
ret[oo.Hash()] = oo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNameKindMap returns a map of K8sObject name/kind hash to K8sObject.
|
||||||
|
func (os K8sObjects) ToNameKindMap() map[string]*K8sObject {
|
||||||
|
ret := make(map[string]*K8sObject)
|
||||||
|
for _, oo := range os {
|
||||||
|
if oo.Valid() {
|
||||||
|
ret[oo.HashNameKind()] = oo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid checks returns true if Kind of K8sObject is not empty.
|
||||||
|
func (o *K8sObject) Valid() bool {
|
||||||
|
return o.Kind != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullName returns namespace/name of K8s object
|
||||||
|
func (o *K8sObject) FullName() string {
|
||||||
|
return fmt.Sprintf("%s/%s", o.Namespace, o.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if o and other are both valid and equal to each other.
|
||||||
|
func (o *K8sObject) Equal(other *K8sObject) bool {
|
||||||
|
if o == nil {
|
||||||
|
return other == nil
|
||||||
|
}
|
||||||
|
if other == nil {
|
||||||
|
return o == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ay, err := o.YAML()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
by, err := other.YAML()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.IsYAMLEqual(string(ay), string(by))
|
||||||
|
}
|
||||||
|
|
||||||
|
func istioCustomResources(group string) bool {
|
||||||
|
switch group {
|
||||||
|
case names.ConfigAPIGroupName,
|
||||||
|
names.SecurityAPIGroupName,
|
||||||
|
names.AuthenticationAPIGroupName,
|
||||||
|
names.NetworkingAPIGroupName:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultObjectOrder is default sorting function used to sort k8s objects.
|
||||||
|
func DefaultObjectOrder() func(o *K8sObject) int {
|
||||||
|
return func(o *K8sObject) int {
|
||||||
|
gk := o.Group + "/" + o.Kind
|
||||||
|
switch {
|
||||||
|
// Create CRDs asap - both because they are slow and because we will likely create instances of them soon
|
||||||
|
case gk == "apiextensions.k8s.io/CustomResourceDefinition":
|
||||||
|
return -1000
|
||||||
|
|
||||||
|
// We need to create ServiceAccounts, Roles before we bind them with a RoleBinding
|
||||||
|
case gk == "/ServiceAccount" || gk == "rbac.authorization.k8s.io/ClusterRole":
|
||||||
|
return 1
|
||||||
|
case gk == "rbac.authorization.k8s.io/ClusterRoleBinding":
|
||||||
|
return 2
|
||||||
|
|
||||||
|
// validatingwebhookconfiguration is configured to FAIL-OPEN in the default install. For the
|
||||||
|
// re-install case we want to apply the validatingwebhookconfiguration first to reset any
|
||||||
|
// orphaned validatingwebhookconfiguration that is FAIL-CLOSE.
|
||||||
|
case gk == "admissionregistration.k8s.io/ValidatingWebhookConfiguration":
|
||||||
|
return 3
|
||||||
|
|
||||||
|
case istioCustomResources(o.Group):
|
||||||
|
return 4
|
||||||
|
|
||||||
|
// Pods might need configmap or secrets - avoid backoff by creating them first
|
||||||
|
case gk == "/ConfigMap" || gk == "/Secrets":
|
||||||
|
return 100
|
||||||
|
|
||||||
|
// Create the pods after we've created other things they might be waiting for
|
||||||
|
case gk == "extensions/Deployment" || gk == "app/Deployment":
|
||||||
|
return 1000
|
||||||
|
|
||||||
|
// Autoscalers typically act on a deployment
|
||||||
|
case gk == "autoscaling/HorizontalPodAutoscaler":
|
||||||
|
return 1001
|
||||||
|
|
||||||
|
// Create services late - after pods have been started
|
||||||
|
case gk == "/Service":
|
||||||
|
return 10000
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObjectsNotInLists(objects K8sObjects, lists ...K8sObjects) K8sObjects {
|
||||||
|
var ret K8sObjects
|
||||||
|
|
||||||
|
filterMap := make(map[*K8sObject]bool)
|
||||||
|
for _, list := range lists {
|
||||||
|
for _, object := range list {
|
||||||
|
filterMap[object] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range objects {
|
||||||
|
if !filterMap[o] {
|
||||||
|
ret = append(ret, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// KindObjects returns the subset of objs with the given kind.
|
||||||
|
func KindObjects(objs K8sObjects, kind string) K8sObjects {
|
||||||
|
var ret K8sObjects
|
||||||
|
for _, o := range objs {
|
||||||
|
if o.Kind == kind {
|
||||||
|
ret = append(ret, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
//// ParseK8SYAMLToIstioOperator parses a IstioOperator CustomResource YAML string and unmarshals in into
|
||||||
|
//// an IstioOperatorSpec object. It returns the object and an API group/version with it.
|
||||||
|
//func ParseK8SYAMLToIstioOperator(yml string) (*v1alpha1.HigressOperator, *schema.GroupVersionKind, error) {
|
||||||
|
// o, err := ParseYAMLToK8sObject([]byte(yml))
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
// iop := &v1alpha1.HigressOperator{}
|
||||||
|
// if err := yaml.UnmarshalStrict([]byte(yml), iop); err != nil {
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
// gvk := o.GroupVersionKind()
|
||||||
|
// //v1alpha1.SetNamespace(iop.Spec, o.Namespace)
|
||||||
|
// return iop, &gvk, nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
// AllObjectHashes returns a map with object hashes of all the objects contained in cmm as the keys.
|
||||||
|
func AllObjectHashes(m string) map[string]bool {
|
||||||
|
ret := make(map[string]bool)
|
||||||
|
objs, err := ParseK8sObjectsFromYAMLManifest(m)
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
for _, o := range objs {
|
||||||
|
ret[o.Hash()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolvePDBConflict When user uses both minAvailable and
|
||||||
|
// maxUnavailable to configure istio instances, these two
|
||||||
|
// parameters are mutually exclusive, care must be taken
|
||||||
|
// to resolve the issue
|
||||||
|
func resolvePDBConflict(o *K8sObject) *K8sObject {
|
||||||
|
if o.json == nil {
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
if o.object.Object["spec"] == nil {
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
spec := o.object.Object["spec"].(map[string]any)
|
||||||
|
isDefault := func(item any) bool {
|
||||||
|
var ii intstr.IntOrString
|
||||||
|
switch item := item.(type) {
|
||||||
|
case int:
|
||||||
|
ii = intstr.FromInt(item)
|
||||||
|
case int64:
|
||||||
|
ii = intstr.FromInt(int(item))
|
||||||
|
case string:
|
||||||
|
ii = intstr.FromString(item)
|
||||||
|
default:
|
||||||
|
ii = intstr.FromInt(0)
|
||||||
|
}
|
||||||
|
intVal, err := intstr.GetScaledValueFromIntOrPercent(&ii, 100, false)
|
||||||
|
if err != nil || intVal == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if spec["maxUnavailable"] != nil && spec["minAvailable"] != nil {
|
||||||
|
// When both maxUnavailable and minAvailable present and
|
||||||
|
// neither has value 0, this is considered a conflict,
|
||||||
|
// then maxUnavailale will take precedence.
|
||||||
|
if !isDefault(spec["maxUnavailable"]) && !isDefault(spec["minAvailable"]) {
|
||||||
|
delete(spec, "minAvailable")
|
||||||
|
// Make sure that the json and yaml representation of the object
|
||||||
|
// is consistent with the changed object
|
||||||
|
o.json = nil
|
||||||
|
o.json, _ = o.JSON()
|
||||||
|
if o.yaml != nil {
|
||||||
|
o.yaml = nil
|
||||||
|
o.yaml, _ = o.YAML()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
713
pkg/cmd/hgctl/helm/object/objects_test.go
Normal file
713
pkg/cmd/hgctl/helm/object/objects_test.go
Normal file
@@ -0,0 +1,713 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHash(t *testing.T) {
|
||||||
|
hashTests := []struct {
|
||||||
|
desc string
|
||||||
|
kind string
|
||||||
|
namespace string
|
||||||
|
name string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"CalculateHashForObjectWithNormalCharacter", "Service", "default", "ingressgateway", "Service:default:ingressgateway"},
|
||||||
|
{"CalculateHashForObjectWithDash", "Deployment", "istio-system", "istio-pilot", "Deployment:istio-system:istio-pilot"},
|
||||||
|
{"CalculateHashForObjectWithDot", "ConfigMap", "istio-system", "my.config", "ConfigMap:istio-system:my.config"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range hashTests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
got := Hash(tt.kind, tt.namespace, tt.name)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Hash(%s): got %s for kind %s, namespace %s, name %s, want %s", tt.desc, got, tt.kind, tt.namespace, tt.name, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromHash(t *testing.T) {
|
||||||
|
hashTests := []struct {
|
||||||
|
desc string
|
||||||
|
hash string
|
||||||
|
kind string
|
||||||
|
namespace string
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{"ParseHashWithNormalCharacter", "Service:default:ingressgateway", "Service", "default", "ingressgateway"},
|
||||||
|
{"ParseHashForObjectWithDash", "Deployment:istio-system:istio-pilot", "Deployment", "istio-system", "istio-pilot"},
|
||||||
|
{"ParseHashForObjectWithDot", "ConfigMap:istio-system:my.config", "ConfigMap", "istio-system", "my.config"},
|
||||||
|
{"InvalidHash", "test", "Bad hash string: test", "", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range hashTests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
k, ns, name := FromHash(tt.hash)
|
||||||
|
if k != tt.kind || ns != tt.namespace || name != tt.name {
|
||||||
|
t.Errorf("FromHash(%s): got kind %s, namespace %s, name %s, want kind %s, namespace %s, name %s", tt.desc, k, ns, name, tt.kind, tt.namespace, tt.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashNameKind(t *testing.T) {
|
||||||
|
hashNameKindTests := []struct {
|
||||||
|
desc string
|
||||||
|
kind string
|
||||||
|
name string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"CalculateHashNameKindForObjectWithNormalCharacter", "Service", "ingressgateway", "Service:ingressgateway"},
|
||||||
|
{"CalculateHashNameKindForObjectWithDash", "Deployment", "istio-pilot", "Deployment:istio-pilot"},
|
||||||
|
{"CalculateHashNameKindForObjectWithDot", "ConfigMap", "my.config", "ConfigMap:my.config"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range hashNameKindTests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
got := HashNameKind(tt.kind, tt.name)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("HashNameKind(%s): got %s for kind %s, name %s, want %s", tt.desc, got, tt.kind, tt.name, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseJSONToK8sObject(t *testing.T) {
|
||||||
|
testDeploymentJSON := `{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": {
|
||||||
|
"name": "istio-citadel",
|
||||||
|
"namespace": "istio-system",
|
||||||
|
"labels": {
|
||||||
|
"istio": "citadel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"replicas": 1,
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"istio": "citadel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"istio": "citadel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [
|
||||||
|
{
|
||||||
|
"name": "citadel",
|
||||||
|
"image": "docker.io/istio/citadel:1.1.8",
|
||||||
|
"args": [
|
||||||
|
"--append-dns-names=true",
|
||||||
|
"--grpc-port=8060",
|
||||||
|
"--grpc-hostname=citadel",
|
||||||
|
"--citadel-storage-namespace=istio-system",
|
||||||
|
"--custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system",
|
||||||
|
"--monitoring-port=15014",
|
||||||
|
"--self-signed-ca=true"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
testPodJSON := `{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Pod",
|
||||||
|
"metadata": {
|
||||||
|
"name": "istio-galley-75bcd59768-hpt5t",
|
||||||
|
"namespace": "istio-system",
|
||||||
|
"labels": {
|
||||||
|
"istio": "galley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [
|
||||||
|
{
|
||||||
|
"name": "galley",
|
||||||
|
"image": "docker.io/istio/galley:1.1.8",
|
||||||
|
"command": [
|
||||||
|
"/usr/local/bin/galley",
|
||||||
|
"server",
|
||||||
|
"--meshConfigFile=/etc/mesh-config/mesh",
|
||||||
|
"--livenessProbeInterval=1s",
|
||||||
|
"--livenessProbePath=/healthliveness",
|
||||||
|
"--readinessProbePath=/healthready",
|
||||||
|
"--readinessProbeInterval=1s",
|
||||||
|
"--deployment-namespace=istio-system",
|
||||||
|
"--insecure=true",
|
||||||
|
"--validation-webhook-config-file",
|
||||||
|
"/etc/config/validatingwebhookconfiguration.yaml",
|
||||||
|
"--monitoringPort=15014",
|
||||||
|
"--log_output_level=default:info"
|
||||||
|
],
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"containerPort": 443,
|
||||||
|
"protocol": "TCP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"containerPort": 15014,
|
||||||
|
"protocol": "TCP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"containerPort": 9901,
|
||||||
|
"protocol": "TCP"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
testServiceJSON := `{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Service",
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"app": "pilot"
|
||||||
|
},
|
||||||
|
"name": "istio-pilot",
|
||||||
|
"namespace": "istio-system"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"clusterIP": "10.102.230.31",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"name": "grpc-xds",
|
||||||
|
"port": 15010,
|
||||||
|
"protocol": "TCP",
|
||||||
|
"targetPort": 15010
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "https-xds",
|
||||||
|
"port": 15011,
|
||||||
|
"protocol": "TCP",
|
||||||
|
"targetPort": 15011
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "http-legacy-discovery",
|
||||||
|
"port": 8080,
|
||||||
|
"protocol": "TCP",
|
||||||
|
"targetPort": 8080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "http-monitoring",
|
||||||
|
"port": 15014,
|
||||||
|
"protocol": "TCP",
|
||||||
|
"targetPort": 15014
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": {
|
||||||
|
"istio": "pilot"
|
||||||
|
},
|
||||||
|
"sessionAffinity": "None",
|
||||||
|
"type": "ClusterIP"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
testInvalidJSON := `invalid json`
|
||||||
|
|
||||||
|
parseJSONToK8sObjectTests := []struct {
|
||||||
|
desc string
|
||||||
|
objString string
|
||||||
|
wantGroup string
|
||||||
|
wantKind string
|
||||||
|
wantName string
|
||||||
|
wantNamespace string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ParseJsonToK8sDeployment", testDeploymentJSON, "apps", "Deployment", "istio-citadel", "istio-system", false},
|
||||||
|
{"ParseJsonToK8sPod", testPodJSON, "", "Pod", "istio-galley-75bcd59768-hpt5t", "istio-system", false},
|
||||||
|
{"ParseJsonToK8sService", testServiceJSON, "", "Service", "istio-pilot", "istio-system", false},
|
||||||
|
{"ParseJsonError", testInvalidJSON, "", "", "", "", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range parseJSONToK8sObjectTests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
k8sObj, err := ParseJSONToK8sObject([]byte(tt.objString))
|
||||||
|
if err == nil {
|
||||||
|
if tt.wantErr {
|
||||||
|
t.Errorf("ParseJsonToK8sObject(%s): should be error", tt.desc)
|
||||||
|
}
|
||||||
|
k8sObjStr := k8sObj.YAMLDebugString()
|
||||||
|
if k8sObj.Group != tt.wantGroup {
|
||||||
|
t.Errorf("ParseJsonToK8sObject(%s): got group %s for k8s object %s, want %s", tt.desc, k8sObj.Group, k8sObjStr, tt.wantGroup)
|
||||||
|
}
|
||||||
|
if k8sObj.Kind != tt.wantKind {
|
||||||
|
t.Errorf("ParseJsonToK8sObject(%s): got kind %s for k8s object %s, want %s", tt.desc, k8sObj.Kind, k8sObjStr, tt.wantKind)
|
||||||
|
}
|
||||||
|
if k8sObj.Name != tt.wantName {
|
||||||
|
t.Errorf("ParseJsonToK8sObject(%s): got name %s for k8s object %s, want %s", tt.desc, k8sObj.Name, k8sObjStr, tt.wantName)
|
||||||
|
}
|
||||||
|
if k8sObj.Namespace != tt.wantNamespace {
|
||||||
|
t.Errorf("ParseJsonToK8sObject(%s): got group %s for k8s object %s, want %s", tt.desc, k8sObj.Namespace, k8sObjStr, tt.wantNamespace)
|
||||||
|
}
|
||||||
|
} else if !tt.wantErr {
|
||||||
|
t.Errorf("ParseJsonToK8sObject(%s): got unexpected error: %v", tt.desc, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseYAMLToK8sObject(t *testing.T) {
|
||||||
|
testDeploymentYaml := `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: istio-citadel
|
||||||
|
namespace: istio-system
|
||||||
|
labels:
|
||||||
|
istio: citadel
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
istio: citadel
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
istio: citadel
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: citadel
|
||||||
|
image: docker.io/istio/citadel:1.1.8
|
||||||
|
args:
|
||||||
|
- "--append-dns-names=true"
|
||||||
|
- "--grpc-port=8060"
|
||||||
|
- "--grpc-hostname=citadel"
|
||||||
|
- "--citadel-storage-namespace=istio-system"
|
||||||
|
- "--custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system"
|
||||||
|
- "--monitoring-port=15014"
|
||||||
|
- "--self-signed-ca=true"`
|
||||||
|
|
||||||
|
testPodYaml := `apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: istio-galley-75bcd59768-hpt5t
|
||||||
|
namespace: istio-system
|
||||||
|
labels:
|
||||||
|
istio: galley
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: galley
|
||||||
|
image: docker.io/istio/galley:1.1.8
|
||||||
|
command:
|
||||||
|
- "/usr/local/bin/galley"
|
||||||
|
- server
|
||||||
|
- "--meshConfigFile=/etc/mesh-config/mesh"
|
||||||
|
- "--livenessProbeInterval=1s"
|
||||||
|
- "--livenessProbePath=/healthliveness"
|
||||||
|
- "--readinessProbePath=/healthready"
|
||||||
|
- "--readinessProbeInterval=1s"
|
||||||
|
- "--deployment-namespace=istio-system"
|
||||||
|
- "--insecure=true"
|
||||||
|
- "--validation-webhook-config-file"
|
||||||
|
- "/etc/config/validatingwebhookconfiguration.yaml"
|
||||||
|
- "--monitoringPort=15014"
|
||||||
|
- "--log_output_level=default:info"
|
||||||
|
ports:
|
||||||
|
- containerPort: 443
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 15014
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 9901
|
||||||
|
protocol: TCP`
|
||||||
|
|
||||||
|
testServiceYaml := `apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: pilot
|
||||||
|
name: istio-pilot
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.102.230.31
|
||||||
|
ports:
|
||||||
|
- name: grpc-xds
|
||||||
|
port: 15010
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 15010
|
||||||
|
- name: https-xds
|
||||||
|
port: 15011
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 15011
|
||||||
|
- name: http-legacy-discovery
|
||||||
|
port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 8080
|
||||||
|
- name: http-monitoring
|
||||||
|
port: 15014
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 15014
|
||||||
|
selector:
|
||||||
|
istio: pilot
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP`
|
||||||
|
|
||||||
|
parseYAMLToK8sObjectTests := []struct {
|
||||||
|
desc string
|
||||||
|
objString string
|
||||||
|
wantGroup string
|
||||||
|
wantKind string
|
||||||
|
wantName string
|
||||||
|
wantNamespace string
|
||||||
|
}{
|
||||||
|
{"ParseYamlToK8sDeployment", testDeploymentYaml, "apps", "Deployment", "istio-citadel", "istio-system"},
|
||||||
|
{"ParseYamlToK8sPod", testPodYaml, "", "Pod", "istio-galley-75bcd59768-hpt5t", "istio-system"},
|
||||||
|
{"ParseYamlToK8sService", testServiceYaml, "", "Service", "istio-pilot", "istio-system"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range parseYAMLToK8sObjectTests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
k8sObj, err := ParseYAMLToK8sObject([]byte(tt.objString))
|
||||||
|
if err != nil {
|
||||||
|
k8sObjStr := k8sObj.YAMLDebugString()
|
||||||
|
if k8sObj.Group != tt.wantGroup {
|
||||||
|
t.Errorf("ParseYAMLToK8sObject(%s): got group %s for k8s object %s, want %s", tt.desc, k8sObj.Group, k8sObjStr, tt.wantGroup)
|
||||||
|
}
|
||||||
|
if k8sObj.Group != tt.wantGroup {
|
||||||
|
t.Errorf("ParseYAMLToK8sObject(%s): got kind %s for k8s object %s, want %s", tt.desc, k8sObj.Kind, k8sObjStr, tt.wantKind)
|
||||||
|
}
|
||||||
|
if k8sObj.Name != tt.wantName {
|
||||||
|
t.Errorf("ParseYAMLToK8sObject(%s): got name %s for k8s object %s, want %s", tt.desc, k8sObj.Name, k8sObjStr, tt.wantName)
|
||||||
|
}
|
||||||
|
if k8sObj.Namespace != tt.wantNamespace {
|
||||||
|
t.Errorf("ParseYAMLToK8sObject(%s): got group %s for k8s object %s, want %s", tt.desc, k8sObj.Namespace, k8sObjStr, tt.wantNamespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseK8sObjectsFromYAMLManifest(t *testing.T) {
|
||||||
|
testDeploymentYaml := `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: istio-citadel
|
||||||
|
namespace: istio-system
|
||||||
|
labels:
|
||||||
|
istio: citadel
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
istio: citadel
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
istio: citadel
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: citadel
|
||||||
|
image: docker.io/istio/citadel:1.1.8
|
||||||
|
args:
|
||||||
|
- "--append-dns-names=true"
|
||||||
|
- "--grpc-port=8060"
|
||||||
|
- "--grpc-hostname=citadel"
|
||||||
|
- "--citadel-storage-namespace=istio-system"
|
||||||
|
- "--custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system"
|
||||||
|
- "--monitoring-port=15014"
|
||||||
|
- "--self-signed-ca=true"`
|
||||||
|
|
||||||
|
testPodYaml := `apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: istio-galley-75bcd59768-hpt5t
|
||||||
|
namespace: istio-system
|
||||||
|
labels:
|
||||||
|
istio: galley
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: galley
|
||||||
|
image: docker.io/istio/galley:1.1.8
|
||||||
|
command:
|
||||||
|
- "/usr/local/bin/galley"
|
||||||
|
- server
|
||||||
|
- "--meshConfigFile=/etc/mesh-config/mesh"
|
||||||
|
- "--livenessProbeInterval=1s"
|
||||||
|
- "--livenessProbePath=/healthliveness"
|
||||||
|
- "--readinessProbePath=/healthready"
|
||||||
|
- "--readinessProbeInterval=1s"
|
||||||
|
- "--deployment-namespace=istio-system"
|
||||||
|
- "--insecure=true"
|
||||||
|
- "--validation-webhook-config-file"
|
||||||
|
- "/etc/config/validatingwebhookconfiguration.yaml"
|
||||||
|
- "--monitoringPort=15014"
|
||||||
|
- "--log_output_level=default:info"
|
||||||
|
ports:
|
||||||
|
- containerPort: 443
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 15014
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 9901
|
||||||
|
protocol: TCP`
|
||||||
|
|
||||||
|
testServiceYaml := `apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: pilot
|
||||||
|
name: istio-pilot
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.102.230.31
|
||||||
|
ports:
|
||||||
|
- name: grpc-xds
|
||||||
|
port: 15010
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 15010
|
||||||
|
- name: https-xds
|
||||||
|
port: 15011
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 15011
|
||||||
|
- name: http-legacy-discovery
|
||||||
|
port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 8080
|
||||||
|
- name: http-monitoring
|
||||||
|
port: 15014
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 15014
|
||||||
|
selector:
|
||||||
|
istio: pilot
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP`
|
||||||
|
|
||||||
|
parseK8sObjectsFromYAMLManifestTests := []struct {
|
||||||
|
desc string
|
||||||
|
objsMap map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"FromHybridYAMLManifest",
|
||||||
|
map[string]string{
|
||||||
|
"Deployment:istio-system:istio-citadel": testDeploymentYaml,
|
||||||
|
"Pod:istio-system:istio-galley-75bcd59768-hpt5t": testPodYaml,
|
||||||
|
"Service:istio-system:istio-pilot": testServiceYaml,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range parseK8sObjectsFromYAMLManifestTests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
testManifestYaml := strings.Join([]string{testDeploymentYaml, testPodYaml, testServiceYaml}, YAMLSeparator)
|
||||||
|
gotK8sObjs, err := ParseK8sObjectsFromYAMLManifest(testManifestYaml)
|
||||||
|
if err != nil {
|
||||||
|
gotK8sObjsMap := gotK8sObjs.ToMap()
|
||||||
|
for objHash, want := range tt.objsMap {
|
||||||
|
if gotObj, ok := gotK8sObjsMap[objHash]; ok {
|
||||||
|
gotObjYaml := gotObj.YAMLDebugString()
|
||||||
|
if !util.IsYAMLEqual(gotObjYaml, want) {
|
||||||
|
t.Errorf("ParseK8sObjectsFromYAMLManifest(%s): got:\n%s\n\nwant:\n%s\nDiff:\n%s\n", tt.desc, gotObjYaml, want, util.YAMLDiff(gotObjYaml, want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestK8sObject_Equal(t *testing.T) {
|
||||||
|
obj1 := K8sObject{
|
||||||
|
object: &unstructured.Unstructured{Object: map[string]any{
|
||||||
|
"key": "value1",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
obj2 := K8sObject{
|
||||||
|
object: &unstructured.Unstructured{Object: map[string]any{
|
||||||
|
"key": "value2",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
o1 *K8sObject
|
||||||
|
o2 *K8sObject
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Equals",
|
||||||
|
o1: &obj1,
|
||||||
|
o2: &obj1,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "NotEquals",
|
||||||
|
o1: &obj1,
|
||||||
|
o2: &obj2,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "NilSource",
|
||||||
|
o1: nil,
|
||||||
|
o2: &obj2,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "NilDest",
|
||||||
|
o1: &obj1,
|
||||||
|
o2: nil,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "TwoNils",
|
||||||
|
o1: nil,
|
||||||
|
o2: nil,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
res := tt.o1.Equal(tt.o2)
|
||||||
|
if res != tt.want {
|
||||||
|
t.Errorf("got %v, want: %v", res, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestK8sObject_ResolveK8sConflict(t *testing.T) {
|
||||||
|
getK8sObject := func(ystr string) *K8sObject {
|
||||||
|
o, err := ParseYAMLToK8sObject([]byte(ystr))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// Ensure that json data is in sync.
|
||||||
|
// Since the object was created using yaml, json is empty.
|
||||||
|
// make sure the object json is set correctly.
|
||||||
|
o.json, _ = o.JSON()
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
o1 *K8sObject
|
||||||
|
o2 *K8sObject
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "not applicable kind",
|
||||||
|
o1: getK8sObject(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: pilot
|
||||||
|
name: istio-pilot
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.102.230.31`),
|
||||||
|
o2: getK8sObject(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: pilot
|
||||||
|
name: istio-pilot
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.102.230.31`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "only minAvailable is set",
|
||||||
|
o1: getK8sObject(`
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: zk-pdb
|
||||||
|
spec:
|
||||||
|
minAvailable: 2`),
|
||||||
|
o2: getK8sObject(`
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: zk-pdb
|
||||||
|
spec:
|
||||||
|
minAvailable: 2`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "only maxUnavailable is set",
|
||||||
|
o1: getK8sObject(`
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: istio
|
||||||
|
spec:
|
||||||
|
maxUnavailable: 3`),
|
||||||
|
o2: getK8sObject(`
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: istio
|
||||||
|
spec:
|
||||||
|
maxUnavailable: 3`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "minAvailable and maxUnavailable are set to none zero values",
|
||||||
|
o1: getK8sObject(`
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: istio
|
||||||
|
spec:
|
||||||
|
maxUnavailable: 50%
|
||||||
|
minAvailable: 3`),
|
||||||
|
o2: getK8sObject(`
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: istio
|
||||||
|
spec:
|
||||||
|
maxUnavailable: 50%`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "both minAvailable and maxUnavailable are set default",
|
||||||
|
o1: getK8sObject(`
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: istio
|
||||||
|
spec:
|
||||||
|
minAvailable: 0
|
||||||
|
maxUnavailable: 0`),
|
||||||
|
o2: getK8sObject(`
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: istio
|
||||||
|
spec:
|
||||||
|
maxUnavailable: 0
|
||||||
|
minAvailable: 0`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
newObj := tt.o1.ResolveK8sConflict()
|
||||||
|
if !newObj.Equal(tt.o2) {
|
||||||
|
newObjjson, _ := newObj.JSON()
|
||||||
|
wantedObjjson, _ := tt.o2.JSON()
|
||||||
|
t.Errorf("Got: %s, want: %s", string(newObjjson), string(wantedObjjson))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
287
pkg/cmd/hgctl/helm/profile.go
Normal file
287
pkg/cmd/hgctl/helm/profile.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 helm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InstallMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
InstallK8s InstallMode = "k8s"
|
||||||
|
InstallLocalK8s InstallMode = "local-k8s"
|
||||||
|
InstallLocalDocker InstallMode = "local-docker"
|
||||||
|
InstallLocal InstallMode = "local"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
Profile string `json:"profile,omitempty"`
|
||||||
|
InstallPackagePath string `json:"installPackagePath,omitempty"`
|
||||||
|
Global ProfileGlobal `json:"global,omitempty"`
|
||||||
|
Console ProfileConsole `json:"console,omitempty"`
|
||||||
|
Gateway ProfileGateway `json:"gateway,omitempty"`
|
||||||
|
Controller ProfileController `json:"controller,omitempty"`
|
||||||
|
Storage ProfileStorage `json:"storage,omitempty"`
|
||||||
|
Values map[string]any `json:"values,omitempty"`
|
||||||
|
Charts ProfileCharts `json:"charts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileGlobal struct {
|
||||||
|
Install InstallMode `json:"install,omitempty"`
|
||||||
|
IngressClass string `json:"ingressClass,omitempty"`
|
||||||
|
WatchNamespace string `json:"watchNamespace,omitempty"`
|
||||||
|
DisableAlpnH2 bool `json:"disableAlpnH2,omitempty"`
|
||||||
|
EnableStatus bool `json:"enableStatus,omitempty"`
|
||||||
|
EnableIstioAPI bool `json:"enableIstioAPI,omitempty"`
|
||||||
|
Namespace string `json:"namespace,omitempty"`
|
||||||
|
IstioNamespace string `json:"istioNamespace,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileGlobal) SetFlags(install InstallMode) ([]string, error) {
|
||||||
|
sets := make([]string, 0)
|
||||||
|
sets = append(sets, fmt.Sprintf("global.ingressClass=%s", p.IngressClass))
|
||||||
|
sets = append(sets, fmt.Sprintf("global.watchNamespace=%s", p.WatchNamespace))
|
||||||
|
sets = append(sets, fmt.Sprintf("global.disableAlpnH2=%t", p.DisableAlpnH2))
|
||||||
|
sets = append(sets, fmt.Sprintf("global.enableStatus=%t", p.EnableStatus))
|
||||||
|
sets = append(sets, fmt.Sprintf("global.enableIstioAPI=%t", p.EnableIstioAPI))
|
||||||
|
sets = append(sets, fmt.Sprintf("global.istioNamespace=%s", p.IstioNamespace))
|
||||||
|
if install == InstallLocalK8s {
|
||||||
|
sets = append(sets, fmt.Sprintf("global.local=%t", true))
|
||||||
|
}
|
||||||
|
return sets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileGlobal) Validate(install InstallMode) []error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
// now only support k8s and local-k8s installation mode
|
||||||
|
if p.Install != InstallK8s && p.Install != InstallLocalK8s {
|
||||||
|
errs = append(errs, errors.New("global.install only can be set to k8s or local-k8s"))
|
||||||
|
}
|
||||||
|
if len(p.IngressClass) == 0 {
|
||||||
|
errs = append(errs, errors.New("global.ingressClass can't be empty"))
|
||||||
|
}
|
||||||
|
if len(p.Namespace) == 0 {
|
||||||
|
errs = append(errs, errors.New("global.namespace can't be empty"))
|
||||||
|
}
|
||||||
|
if len(p.IstioNamespace) == 0 {
|
||||||
|
errs = append(errs, errors.New("global.istioNamespace can't be empty"))
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileConsole struct {
|
||||||
|
Port uint32 `json:"port,omitempty"`
|
||||||
|
Replicas uint32 `json:"replicas,omitempty"`
|
||||||
|
ServiceType string `json:"serviceType,omitempty"`
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
TlsSecretName string `json:"tlsSecretName,omitempty"`
|
||||||
|
WebLoginPrompt string `json:"webLoginPrompt,omitempty"`
|
||||||
|
AdminPasswordValue string `json:"adminPasswordValue,omitempty"`
|
||||||
|
AdminPasswordLength uint32 `json:"adminPasswordLength,omitempty"`
|
||||||
|
O11yEnabled bool `json:"o11YEnabled,omitempty"`
|
||||||
|
PvcRwxSupported bool `json:"pvcRwxSupported,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileConsole) SetFlags(install InstallMode) ([]string, error) {
|
||||||
|
sets := make([]string, 0)
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.replicaCount=%d", p.Replicas))
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.service.type=%s", p.ServiceType))
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.domain=%s", p.Domain))
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.tlsSecretName=%s", p.TlsSecretName))
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.web.login.prompt=%s", p.WebLoginPrompt))
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.admin.password.value=%s", p.AdminPasswordValue))
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.admin.password.length=%d", p.AdminPasswordLength))
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.o11y.enabled=%t", p.O11yEnabled))
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-console.pvc.rwxSupported=%t", p.PvcRwxSupported))
|
||||||
|
return sets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileConsole) Validate(install InstallMode) []error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
if p.Replicas <= 0 {
|
||||||
|
errs = append(errs, errors.New("console.replica need be large than zero"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ServiceType != "ClusterIP" && p.ServiceType != "NodePort" && p.ServiceType != "LoadBalancer" {
|
||||||
|
errs = append(errs, errors.New("console.serviceType can only be set to ClusterIP, NodePort or LoadBalancer"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileGateway struct {
|
||||||
|
Replicas uint32 `json:"replicas,omitempty"`
|
||||||
|
HttpPort uint32 `json:"httpPort,omitempty"`
|
||||||
|
HttpsPort uint32 `json:"httpsPort,omitempty"`
|
||||||
|
MetricsPort uint32 `json:"metricsPort,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileGateway) SetFlags(install InstallMode) ([]string, error) {
|
||||||
|
sets := make([]string, 0)
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-core.gateway.replicas=%d", p.Replicas))
|
||||||
|
return sets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileGateway) Validate(install InstallMode) []error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
if p.Replicas <= 0 {
|
||||||
|
errs = append(errs, errors.New("gateway.replica need be large than zero"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileController struct {
|
||||||
|
Replicas uint32 `json:"replicas,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileController) SetFlags(install InstallMode) ([]string, error) {
|
||||||
|
sets := make([]string, 0)
|
||||||
|
sets = append(sets, fmt.Sprintf("higress-core.controller.replicas=%d", p.Replicas))
|
||||||
|
return sets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileController) Validate(install InstallMode) []error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
if p.Replicas <= 0 {
|
||||||
|
errs = append(errs, errors.New("controller.replica need be large than zero"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileStorage struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Ns string `json:"ns,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
DataEncKey string `json:"DataEncKey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileStorage) Validate(install InstallMode) []error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
type Chart struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileCharts struct {
|
||||||
|
Higress Chart `json:"higress,omitempty"`
|
||||||
|
Istio Chart `json:"istio,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProfileCharts) Validate(install InstallMode) []error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Profile) ValuesYaml() (string, error) {
|
||||||
|
setFlags := make([]string, 0)
|
||||||
|
// Get global setting
|
||||||
|
globalFlags, _ := p.Global.SetFlags(p.Global.Install)
|
||||||
|
setFlags = append(setFlags, globalFlags...)
|
||||||
|
|
||||||
|
// Get console setting
|
||||||
|
consoleFlags, _ := p.Console.SetFlags(p.Global.Install)
|
||||||
|
setFlags = append(setFlags, consoleFlags...)
|
||||||
|
|
||||||
|
// Get gateway setting
|
||||||
|
gatewayFlags, _ := p.Gateway.SetFlags(p.Global.Install)
|
||||||
|
setFlags = append(setFlags, gatewayFlags...)
|
||||||
|
|
||||||
|
// Get controller setting
|
||||||
|
controllerFlags, _ := p.Controller.SetFlags(p.Global.Install)
|
||||||
|
setFlags = append(setFlags, controllerFlags...)
|
||||||
|
|
||||||
|
valueOverlayYAML := ""
|
||||||
|
if p.Values != nil {
|
||||||
|
out, err := yaml.Marshal(p.Values)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
valueOverlayYAML = string(out)
|
||||||
|
}
|
||||||
|
// merge values and setFlags
|
||||||
|
overlayYAML, err := overlaySetFlagValues(valueOverlayYAML, setFlags)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return overlayYAML, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Profile) IstioEnabled() bool {
|
||||||
|
if (p.Global.Install == InstallK8s || p.Global.Install == InstallLocalK8s) && p.Global.EnableIstioAPI {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Profile) Validate() error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
errsGlobal := p.Global.Validate(p.Global.Install)
|
||||||
|
if len(errsGlobal) > 0 {
|
||||||
|
errs = append(errs, errsGlobal...)
|
||||||
|
}
|
||||||
|
errsConsole := p.Console.Validate(p.Global.Install)
|
||||||
|
if len(errsConsole) > 0 {
|
||||||
|
errs = append(errs, errsConsole...)
|
||||||
|
}
|
||||||
|
errsGateway := p.Gateway.Validate(p.Global.Install)
|
||||||
|
if len(errsGateway) > 0 {
|
||||||
|
errs = append(errs, errsGateway...)
|
||||||
|
}
|
||||||
|
errsController := p.Controller.Validate(p.Global.Install)
|
||||||
|
if len(errsController) > 0 {
|
||||||
|
errs = append(errs, errsController...)
|
||||||
|
}
|
||||||
|
errsStorage := p.Storage.Validate(p.Global.Install)
|
||||||
|
if len(errsController) > 0 {
|
||||||
|
errs = append(errs, errsStorage...)
|
||||||
|
}
|
||||||
|
errsCharts := p.Charts.Validate(p.Global.Install)
|
||||||
|
if len(errsCharts) > 0 {
|
||||||
|
errs = append(errs, errsCharts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New(ToString(errs, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString returns a string representation of errors, with elements separated by separator string. Any nil errors in the
|
||||||
|
// slice are skipped.
|
||||||
|
func ToString(errors []error, separator string) string {
|
||||||
|
var out string
|
||||||
|
for i, e := range errors {
|
||||||
|
if e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i != 0 {
|
||||||
|
out += separator
|
||||||
|
}
|
||||||
|
out += e.Error()
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
593
pkg/cmd/hgctl/helm/render.go
Normal file
593
pkg/cmd/hgctl/helm/render.go
Normal file
@@ -0,0 +1,593 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 helm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/manifests"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||||
|
"helm.sh/helm/v3/pkg/action"
|
||||||
|
"helm.sh/helm/v3/pkg/chart"
|
||||||
|
"helm.sh/helm/v3/pkg/chart/loader"
|
||||||
|
"helm.sh/helm/v3/pkg/chartutil"
|
||||||
|
"helm.sh/helm/v3/pkg/cli"
|
||||||
|
"helm.sh/helm/v3/pkg/downloader"
|
||||||
|
"helm.sh/helm/v3/pkg/engine"
|
||||||
|
"helm.sh/helm/v3/pkg/getter"
|
||||||
|
"helm.sh/helm/v3/pkg/repo"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultProfileName is the name of the default profile for installation.
|
||||||
|
DefaultProfileName = "local-k8s"
|
||||||
|
// DefaultProfileFilename is the name of the default profile yaml file for installation.
|
||||||
|
DefaultProfileFilename = "local-k8s.yaml"
|
||||||
|
// DefaultUninstallProfileName is the name of the default profile yaml file for uninstallation.
|
||||||
|
DefaultUninstallProfileName = "local-k8s"
|
||||||
|
|
||||||
|
// ChartsSubdirName = "charts"
|
||||||
|
profilesRoot = "profiles"
|
||||||
|
|
||||||
|
RepoLatestVersion = "latest"
|
||||||
|
RepoChartIndexYamlHigressIndex = "higress"
|
||||||
|
|
||||||
|
YAMLSeparator = "\n---\n"
|
||||||
|
NotesFileNameSuffix = ".txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadValues(profileName string, chartsDir string) (string, error) {
|
||||||
|
path := strings.Join([]string{profilesRoot, builtinProfileToFilename(profileName)}, "/")
|
||||||
|
by, err := fs.ReadFile(manifests.BuiltinOrDir(chartsDir), path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(by), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readProfiles(chartsDir string) (map[string]bool, error) {
|
||||||
|
profiles := map[string]bool{}
|
||||||
|
f := manifests.BuiltinOrDir(chartsDir)
|
||||||
|
dir, err := fs.ReadDir(f, profilesRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, f := range dir {
|
||||||
|
if f.Name() == "_all.yaml" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
trimmedString := strings.TrimSuffix(f.Name(), ".yaml")
|
||||||
|
if f.Name() != trimmedString {
|
||||||
|
profiles[trimmedString] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return profiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinProfileToFilename(name string) string {
|
||||||
|
if name == "" {
|
||||||
|
return DefaultProfileFilename
|
||||||
|
}
|
||||||
|
return name + ".yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripPrefix removes the given prefix from prefix.
|
||||||
|
func stripPrefix(path, prefix string) string {
|
||||||
|
pl := len(strings.Split(prefix, "/"))
|
||||||
|
pv := strings.Split(path, "/")
|
||||||
|
return strings.Join(pv[pl:], "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListProfiles list all the profiles.
|
||||||
|
func ListProfiles(charts string) ([]string, error) {
|
||||||
|
profiles, err := readProfiles(charts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return util.StringBoolMapToSlice(profiles), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultFilters = []util.FilterFunc{
|
||||||
|
util.LicenseFilter,
|
||||||
|
util.FormatterFilter,
|
||||||
|
util.SpaceFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderer is responsible for rendering helm chart with new values.
|
||||||
|
type Renderer interface {
|
||||||
|
Init() error
|
||||||
|
RenderManifest(valsYaml string) (string, error)
|
||||||
|
SetVersion(version string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RendererOptions struct {
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
|
||||||
|
// fields for LocalRenderer
|
||||||
|
FS fs.FS
|
||||||
|
Dir string
|
||||||
|
|
||||||
|
// fields for RemoteRenderer
|
||||||
|
Version string
|
||||||
|
RepoURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RendererOption func(*RendererOptions)
|
||||||
|
|
||||||
|
func WithName(name string) RendererOption {
|
||||||
|
return func(opts *RendererOptions) {
|
||||||
|
opts.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNamespace(ns string) RendererOption {
|
||||||
|
return func(opts *RendererOptions) {
|
||||||
|
opts.Namespace = ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFS(f fs.FS) RendererOption {
|
||||||
|
return func(opts *RendererOptions) {
|
||||||
|
opts.FS = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDir(dir string) RendererOption {
|
||||||
|
return func(opts *RendererOptions) {
|
||||||
|
opts.Dir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithVersion(version string) RendererOption {
|
||||||
|
return func(opts *RendererOptions) {
|
||||||
|
opts.Version = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithRepoURL(repo string) RendererOption {
|
||||||
|
return func(opts *RendererOptions) {
|
||||||
|
opts.RepoURL = repo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalRenderer load chart from local file system
|
||||||
|
type LocalRenderer struct {
|
||||||
|
Opts *RendererOptions
|
||||||
|
Chart *chart.Chart
|
||||||
|
Started bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lr *LocalRenderer) Init() error {
|
||||||
|
fileNames, err := getFileNames(lr.Opts.FS, lr.Opts.Dir)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("chart of component %s doesn't exist", lr.Opts.Name)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("getFileNames err: %s", err)
|
||||||
|
}
|
||||||
|
var files []*loader.BufferedFile
|
||||||
|
for _, fileName := range fileNames {
|
||||||
|
data, err := fs.ReadFile(lr.Opts.FS, fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ReadFile %s err: %s", fileName, err)
|
||||||
|
}
|
||||||
|
// todo:// explain why we need to do this
|
||||||
|
name := util.StripPrefix(fileName, lr.Opts.Dir)
|
||||||
|
file := &loader.BufferedFile{
|
||||||
|
Name: name,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
newChart, err := loader.LoadFiles(files)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("load chart of component %s err: %s", lr.Opts.Name, err)
|
||||||
|
}
|
||||||
|
lr.Chart = newChart
|
||||||
|
lr.Started = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lr *LocalRenderer) RenderManifest(valsYaml string) (string, error) {
|
||||||
|
if !lr.Started {
|
||||||
|
return "", errors.New("LocalRenderer has not been init")
|
||||||
|
}
|
||||||
|
return renderManifest(valsYaml, lr.Chart, true, lr.Opts, DefaultFilters...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lr *LocalRenderer) SetVersion(version string) {
|
||||||
|
lr.Opts.Version = version
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLocalRenderer(opts ...RendererOption) (Renderer, error) {
|
||||||
|
newOpts := &RendererOptions{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(newOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := verifyRendererOptions(newOpts); err != nil {
|
||||||
|
return nil, fmt.Errorf("verify err: %s", err)
|
||||||
|
}
|
||||||
|
return &LocalRenderer{
|
||||||
|
Opts: newOpts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoteRenderer struct {
|
||||||
|
Opts *RendererOptions
|
||||||
|
Chart *chart.Chart
|
||||||
|
Started bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RemoteRenderer) initChartPathOptions() *action.ChartPathOptions {
|
||||||
|
return &action.ChartPathOptions{
|
||||||
|
RepoURL: rr.Opts.RepoURL,
|
||||||
|
Version: rr.Opts.Version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RemoteRenderer) Init() error {
|
||||||
|
cpOpts := rr.initChartPathOptions()
|
||||||
|
settings := cli.New()
|
||||||
|
// using release name as chart name by default
|
||||||
|
cp, err := locateChart(cpOpts, rr.Opts.Name, settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check chart dependencies to make sure all are present in /charts
|
||||||
|
chartRequested, err := loader.Load(cp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := verifyInstallable(chartRequested); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Chart = chartRequested
|
||||||
|
rr.Started = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RemoteRenderer) SetVersion(version string) {
|
||||||
|
rr.Opts.Version = version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RemoteRenderer) RenderManifest(valsYaml string) (string, error) {
|
||||||
|
if !rr.Started {
|
||||||
|
return "", errors.New("RemoteRenderer has not been init")
|
||||||
|
}
|
||||||
|
return renderManifest(valsYaml, rr.Chart, false, rr.Opts, DefaultFilters...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoteRenderer(opts ...RendererOption) (Renderer, error) {
|
||||||
|
newOpts := &RendererOptions{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(newOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RemoteRenderer{
|
||||||
|
Opts: newOpts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyRendererOptions(opts *RendererOptions) error {
|
||||||
|
if opts.Name == "" {
|
||||||
|
return errors.New("missing component name for Renderer")
|
||||||
|
}
|
||||||
|
if opts.Namespace == "" {
|
||||||
|
return errors.New("missing component namespace for Renderer")
|
||||||
|
}
|
||||||
|
if opts.FS == nil {
|
||||||
|
return errors.New("missing chart FS for Renderer")
|
||||||
|
}
|
||||||
|
if opts.Dir == "" {
|
||||||
|
return errors.New("missing chart dir for Renderer")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read all files recursively under root path from a certain local file system
|
||||||
|
func getFileNames(f fs.FS, root string) ([]string, error) {
|
||||||
|
var fileNames []string
|
||||||
|
if err := fs.WalkDir(f, root, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fileNames = append(fileNames, path)
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fileNames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyInstallable(cht *chart.Chart) error {
|
||||||
|
typ := cht.Metadata.Type
|
||||||
|
if typ == "" || typ == "application" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s chart %s is not installable", typ, cht.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderManifest(valsYaml string, cht *chart.Chart, builtIn bool, opts *RendererOptions, filters ...util.FilterFunc) (string, error) {
|
||||||
|
valsMap := make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(valsYaml), &valsMap); err != nil {
|
||||||
|
return "", fmt.Errorf("unmarshal failed err: %s", err)
|
||||||
|
}
|
||||||
|
RelOpts := chartutil.ReleaseOptions{
|
||||||
|
Name: opts.Name,
|
||||||
|
Namespace: opts.Namespace,
|
||||||
|
}
|
||||||
|
// TODO need to specify k8s version
|
||||||
|
caps := chartutil.DefaultCapabilities
|
||||||
|
// maybe we need a configuration to change this caps
|
||||||
|
resVals, err := chartutil.ToRenderValues(cht, valsMap, RelOpts, caps)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("ToRenderValues failed err: %s", err)
|
||||||
|
}
|
||||||
|
if builtIn {
|
||||||
|
resVals["Values"].(chartutil.Values)["enabled"] = true
|
||||||
|
}
|
||||||
|
filesMap, err := engine.Render(cht, resVals)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Render chart failed err: %s", err)
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(filesMap))
|
||||||
|
for key := range filesMap {
|
||||||
|
// remove notation files such as Notes.txt
|
||||||
|
if strings.HasSuffix(key, NotesFileNameSuffix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
// to ensure that every manifest rendered by same values are the same
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
file := filesMap[keys[i]]
|
||||||
|
file = util.ApplyFilters(file, filters...)
|
||||||
|
// ignore empty manifest
|
||||||
|
if file == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(file, YAMLSeparator) {
|
||||||
|
file += YAMLSeparator
|
||||||
|
}
|
||||||
|
builder.WriteString(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// render CRD
|
||||||
|
crdFiles := cht.CRDObjects()
|
||||||
|
// Sort crd files by name to ensure stable manifest output
|
||||||
|
sort.Slice(crdFiles, func(i, j int) bool { return crdFiles[i].Name < crdFiles[j].Name })
|
||||||
|
for _, crdFile := range crdFiles {
|
||||||
|
f := string(crdFile.File.Data)
|
||||||
|
// add yaml separator if the rendered file doesn't have one at the end
|
||||||
|
f = strings.TrimSpace(f) + "\n"
|
||||||
|
if !strings.HasSuffix(f, YAMLSeparator) {
|
||||||
|
f += YAMLSeparator
|
||||||
|
}
|
||||||
|
builder.WriteString(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// locateChart locate the target chart path by sequential orders:
|
||||||
|
// 1. find local helm repository using "name-version.tgz" format
|
||||||
|
// 2. using downloader to pull remote chart
|
||||||
|
func locateChart(cpOpts *action.ChartPathOptions, name string, settings *cli.EnvSettings) (string, error) {
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
version := strings.TrimSpace(cpOpts.Version)
|
||||||
|
|
||||||
|
// check if it's in Helm's chart cache
|
||||||
|
// cacheName is hardcoded as format of helm. eg: grafana-6.31.1.tgz
|
||||||
|
cacheName := name + "-" + cpOpts.Version + ".tgz"
|
||||||
|
cachePath := path.Join(settings.RepositoryCache, cacheName)
|
||||||
|
if _, err := os.Stat(cachePath); err == nil {
|
||||||
|
abs, err := filepath.Abs(cachePath)
|
||||||
|
if err != nil {
|
||||||
|
return abs, err
|
||||||
|
}
|
||||||
|
if cpOpts.Verify {
|
||||||
|
if _, err := downloader.VerifyChart(abs, cpOpts.Keyring); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return abs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dl := downloader.ChartDownloader{
|
||||||
|
Out: os.Stdout,
|
||||||
|
Keyring: cpOpts.Keyring,
|
||||||
|
Getters: getter.All(settings),
|
||||||
|
Options: []getter.Option{
|
||||||
|
getter.WithPassCredentialsAll(cpOpts.PassCredentialsAll),
|
||||||
|
getter.WithTLSClientConfig(cpOpts.CertFile, cpOpts.KeyFile, cpOpts.CaFile),
|
||||||
|
getter.WithInsecureSkipVerifyTLS(cpOpts.InsecureSkipTLSverify),
|
||||||
|
},
|
||||||
|
RepositoryConfig: settings.RepositoryConfig,
|
||||||
|
RepositoryCache: settings.RepositoryCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
if cpOpts.Verify {
|
||||||
|
dl.Verify = downloader.VerifyAlways
|
||||||
|
}
|
||||||
|
if cpOpts.RepoURL != "" {
|
||||||
|
chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(cpOpts.RepoURL, cpOpts.Username, cpOpts.Password, name, version,
|
||||||
|
cpOpts.CertFile, cpOpts.KeyFile, cpOpts.CaFile, cpOpts.InsecureSkipTLSverify, cpOpts.PassCredentialsAll, getter.All(settings))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
name = chartURL
|
||||||
|
|
||||||
|
// Only pass the user/pass on when the user has said to or when the
|
||||||
|
// location of the chart repo and the chart are the same domain.
|
||||||
|
u1, err := url.Parse(cpOpts.RepoURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
u2, err := url.Parse(chartURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host on URL (returned from url.Parse) contains the port if present.
|
||||||
|
// This check ensures credentials are not passed between different
|
||||||
|
// services on different ports.
|
||||||
|
if cpOpts.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) {
|
||||||
|
dl.Options = append(dl.Options, getter.WithBasicAuth(cpOpts.Username, cpOpts.Password))
|
||||||
|
} else {
|
||||||
|
dl.Options = append(dl.Options, getter.WithBasicAuth("", ""))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dl.Options = append(dl.Options, getter.WithBasicAuth(cpOpts.Username, cpOpts.Password))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if RepositoryCache doesn't exist, create it
|
||||||
|
if err := os.MkdirAll(settings.RepositoryCache, 0o755); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileAbsPath, err := filepath.Abs(filename)
|
||||||
|
if err != nil {
|
||||||
|
return filename, err
|
||||||
|
}
|
||||||
|
return fileAbsPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseLatestVersion(repoUrl string, version string) (string, error) {
|
||||||
|
|
||||||
|
cpOpts := &action.ChartPathOptions{
|
||||||
|
RepoURL: repoUrl,
|
||||||
|
Version: version,
|
||||||
|
}
|
||||||
|
settings := cli.New()
|
||||||
|
|
||||||
|
indexURL, err := repo.ResolveReferenceURL(repoUrl, "index.yaml")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(repoUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("invalid chart URL format: %s", repoUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := getter.All(settings).ByScheme(u.Scheme)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not find protocol handler for: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get(indexURL,
|
||||||
|
getter.WithURL(cpOpts.RepoURL),
|
||||||
|
getter.WithInsecureSkipVerifyTLS(cpOpts.InsecureSkipTLSverify),
|
||||||
|
getter.WithTLSClientConfig(cpOpts.CertFile, cpOpts.KeyFile, cpOpts.CaFile),
|
||||||
|
getter.WithBasicAuth(cpOpts.Username, cpOpts.Password),
|
||||||
|
getter.WithPassCredentialsAll(cpOpts.PassCredentialsAll),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
index, err := io.ReadAll(resp)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
indexFile, err := loadIndex(index)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get higress helm chart latest version
|
||||||
|
if entries, ok := indexFile.Entries[RepoChartIndexYamlHigressIndex]; ok {
|
||||||
|
return entries[0].AppVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("can't find higress latest version")
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadIndex loads an index file and does minimal validity checking.
|
||||||
|
//
|
||||||
|
// The source parameter is only used for logging.
|
||||||
|
// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
|
||||||
|
func loadIndex(data []byte) (*repo.IndexFile, error) {
|
||||||
|
i := &repo.IndexFile{}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return i, errors.New("empty index.yaml file")
|
||||||
|
}
|
||||||
|
if err := jsonOrYamlUnmarshal(data, i); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
for _, cvs := range i.Entries {
|
||||||
|
for idx := len(cvs) - 1; idx >= 0; idx-- {
|
||||||
|
if cvs[idx] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cvs[idx].APIVersion == "" {
|
||||||
|
cvs[idx].APIVersion = chart.APIVersionV1
|
||||||
|
}
|
||||||
|
if err := cvs[idx].Validate(); err != nil {
|
||||||
|
cvs = append(cvs[:idx], cvs[idx+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.SortEntries()
|
||||||
|
if i.APIVersion == "" {
|
||||||
|
return i, errors.New("no API version specified")
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonOrYamlUnmarshal unmarshals the given byte slice containing JSON or YAML
|
||||||
|
// into the provided interface.
|
||||||
|
//
|
||||||
|
// It automatically detects whether the data is in JSON or YAML format by
|
||||||
|
// checking its validity as JSON. If the data is valid JSON, it will use the
|
||||||
|
// `encoding/json` package to unmarshal it. Otherwise, it will use the
|
||||||
|
// `sigs.k8s.io/yaml` package to unmarshal the YAML data.
|
||||||
|
func jsonOrYamlUnmarshal(b []byte, i interface{}) error {
|
||||||
|
if json.Valid(b) {
|
||||||
|
return json.Unmarshal(b, i)
|
||||||
|
}
|
||||||
|
return yaml.UnmarshalStrict(b, i)
|
||||||
|
}
|
||||||
548
pkg/cmd/hgctl/helm/tpath/tree.go
Normal file
548
pkg/cmd/hgctl/helm/tpath/tree.go
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 tpath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
yaml2 "sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PathContext provides a means for traversing a tree towards the root.
|
||||||
|
type PathContext struct {
|
||||||
|
// Parent in the Parent of this PathContext.
|
||||||
|
Parent *PathContext
|
||||||
|
// KeyToChild is the key required to reach the child.
|
||||||
|
KeyToChild any
|
||||||
|
// Node is the actual Node in the data tree.
|
||||||
|
Node any
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the Stringer interface.
|
||||||
|
func (nc *PathContext) String() string {
|
||||||
|
ret := "\n--------------- NodeContext ------------------\n"
|
||||||
|
if nc.Parent != nil {
|
||||||
|
ret += fmt.Sprintf("Parent.Node=\n%s\n", nc.Parent.Node)
|
||||||
|
ret += fmt.Sprintf("KeyToChild=%v\n", nc.Parent.KeyToChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += fmt.Sprintf("Node=\n%s\n", nc.Node)
|
||||||
|
ret += "----------------------------------------------\n"
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPathContext returns the PathContext for the Node which has the given path from root.
|
||||||
|
// It returns false and no error if the given path is not found, or an error code in other error situations, like
|
||||||
|
// a malformed path.
|
||||||
|
// It also creates a tree of PathContexts during the traversal so that Parent nodes can be updated if required. This is
|
||||||
|
// required when (say) appending to a list, where the parent list itself must be updated.
|
||||||
|
func GetPathContext(root any, path util.Path, createMissing bool) (*PathContext, bool, error) {
|
||||||
|
return getPathContext(&PathContext{Node: root}, path, path, createMissing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WritePathContext writes the given value to the Node in the given PathContext.
|
||||||
|
func WritePathContext(nc *PathContext, value any, merge bool) error {
|
||||||
|
|
||||||
|
if !util.IsValueNil(value) {
|
||||||
|
return setPathContext(nc, value, merge)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nc.Parent == nil {
|
||||||
|
return errors.New("cannot delete root element")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isSliceOrPtrInterface(nc.Parent.Node):
|
||||||
|
if err := util.DeleteFromSlicePtr(nc.Parent.Node, nc.Parent.KeyToChild.(int)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isMapOrInterface(nc.Parent.Parent.Node) {
|
||||||
|
return util.InsertIntoMap(nc.Parent.Parent.Node, nc.Parent.Parent.KeyToChild, nc.Parent.Node)
|
||||||
|
}
|
||||||
|
// TODO: The case of deleting a list.list.node element is not currently supported.
|
||||||
|
return fmt.Errorf("cannot delete path: unsupported parent.parent type %T for delete", nc.Parent.Parent.Node)
|
||||||
|
case util.IsMap(nc.Parent.Node):
|
||||||
|
return util.DeleteFromMap(nc.Parent.Node, nc.Parent.KeyToChild)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot delete path: unsupported parent type %T for delete", nc.Parent.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteNode writes value to the tree in root at the given path, creating any required missing internal nodes in path.
|
||||||
|
func WriteNode(root any, path util.Path, value any) error {
|
||||||
|
pc, _, err := getPathContext(&PathContext{Node: root}, path, path, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return WritePathContext(pc, value, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeNode merges value to the tree in root at the given path, creating any required missing internal nodes in path.
|
||||||
|
func MergeNode(root any, path util.Path, value any) error {
|
||||||
|
pc, _, err := getPathContext(&PathContext{Node: root}, path, path, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return WritePathContext(pc, value, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find returns the value at path from the given tree, or false if the path does not exist.
|
||||||
|
// It behaves differently from GetPathContext in that it never creates map entries at the leaf and does not provide
|
||||||
|
// a way to mutate the parent of the found node.
|
||||||
|
func Find(inputTree map[string]any, path util.Path) (any, bool, error) {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return nil, false, fmt.Errorf("path is empty")
|
||||||
|
}
|
||||||
|
node, found := find(inputTree, path)
|
||||||
|
return node, found, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete sets value at path of input untyped tree to nil
|
||||||
|
func Delete(root map[string]any, path util.Path) (bool, error) {
|
||||||
|
pc, _, err := getPathContext(&PathContext{Node: root}, path, path, false)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, WritePathContext(pc, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPathContext is the internal implementation of GetPathContext.
|
||||||
|
// If createMissing is true, it creates any missing map (but NOT list) path entries in root.
|
||||||
|
func getPathContext(nc *PathContext, fullPath, remainPath util.Path, createMissing bool) (*PathContext, bool, error) {
|
||||||
|
if len(remainPath) == 0 {
|
||||||
|
return nc, true, nil
|
||||||
|
}
|
||||||
|
pe := remainPath[0]
|
||||||
|
|
||||||
|
if nc.Node == nil {
|
||||||
|
if !createMissing {
|
||||||
|
return nil, false, fmt.Errorf("node %s is zero", pe)
|
||||||
|
}
|
||||||
|
if util.IsNPathElement(pe) || util.IsKVPathElement(pe) {
|
||||||
|
nc.Node = []any{}
|
||||||
|
} else {
|
||||||
|
nc.Node = make(map[string]any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(nc.Node)
|
||||||
|
if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
ncNode := v.Interface()
|
||||||
|
|
||||||
|
// For list types, we need a key to identify the selected list item. This can be either a value key of the
|
||||||
|
// form :matching_value in the case of a leaf list, or a matching key:value in the case of a non-leaf list.
|
||||||
|
if lst, ok := ncNode.([]any); ok {
|
||||||
|
// If the path element has the form [N], a list element is being selected by index. Return the element at index
|
||||||
|
// N if it exists.
|
||||||
|
if util.IsNPathElement(pe) {
|
||||||
|
idx, err := util.PathN(pe)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("path %s, index %s: %s", fullPath, pe, err)
|
||||||
|
}
|
||||||
|
var foundNode any
|
||||||
|
if idx >= len(lst) || idx < 0 {
|
||||||
|
if !createMissing {
|
||||||
|
return nil, false, fmt.Errorf("index %d exceeds list length %d at path %s", idx, len(lst), remainPath)
|
||||||
|
}
|
||||||
|
idx = len(lst)
|
||||||
|
foundNode = make(map[string]any)
|
||||||
|
} else {
|
||||||
|
foundNode = lst[idx]
|
||||||
|
}
|
||||||
|
nn := &PathContext{
|
||||||
|
Parent: nc,
|
||||||
|
Node: foundNode,
|
||||||
|
}
|
||||||
|
nc.KeyToChild = idx
|
||||||
|
return getPathContext(nn, fullPath, remainPath[1:], createMissing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the path element must have form [key:value]. In this case, go through all list elements, which
|
||||||
|
// must have map type, and try to find one which has a matching key:value.
|
||||||
|
for idx, le := range lst {
|
||||||
|
// non-leaf list, expect to match item by key:value.
|
||||||
|
if lm, ok := le.(map[any]any); ok {
|
||||||
|
k, v, err := util.PathKV(pe)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("path %s: %s", fullPath, err)
|
||||||
|
}
|
||||||
|
if stringsEqual(lm[k], v) {
|
||||||
|
nn := &PathContext{
|
||||||
|
Parent: nc,
|
||||||
|
Node: lm,
|
||||||
|
}
|
||||||
|
nc.KeyToChild = idx
|
||||||
|
nn.KeyToChild = k
|
||||||
|
if len(remainPath) == 1 {
|
||||||
|
return nn, true, nil
|
||||||
|
}
|
||||||
|
return getPathContext(nn, fullPath, remainPath[1:], createMissing)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// repeat of the block above for the case where tree unmarshals to map[string]interface{}. There doesn't
|
||||||
|
// seem to be a way to merge this case into the above block.
|
||||||
|
if lm, ok := le.(map[string]any); ok {
|
||||||
|
k, v, err := util.PathKV(pe)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("path %s: %s", fullPath, err)
|
||||||
|
}
|
||||||
|
if stringsEqual(lm[k], v) {
|
||||||
|
nn := &PathContext{
|
||||||
|
Parent: nc,
|
||||||
|
Node: lm,
|
||||||
|
}
|
||||||
|
nc.KeyToChild = idx
|
||||||
|
nn.KeyToChild = k
|
||||||
|
if len(remainPath) == 1 {
|
||||||
|
return nn, true, nil
|
||||||
|
}
|
||||||
|
return getPathContext(nn, fullPath, remainPath[1:], createMissing)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// leaf list, expect path element [V], match based on value V.
|
||||||
|
v, err := util.PathV(pe)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("path %s: %s", fullPath, err)
|
||||||
|
}
|
||||||
|
if matchesRegex(v, le) {
|
||||||
|
nn := &PathContext{
|
||||||
|
Parent: nc,
|
||||||
|
Node: le,
|
||||||
|
}
|
||||||
|
nc.KeyToChild = idx
|
||||||
|
return getPathContext(nn, fullPath, remainPath[1:], createMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false, fmt.Errorf("path %s: element %s not found", fullPath, pe)
|
||||||
|
}
|
||||||
|
|
||||||
|
if util.IsMap(ncNode) {
|
||||||
|
var nn any
|
||||||
|
if m, ok := ncNode.(map[any]any); ok {
|
||||||
|
nn, ok = m[pe]
|
||||||
|
if !ok {
|
||||||
|
// remainPath == 1 means the patch is creation of a new leaf.
|
||||||
|
if createMissing || len(remainPath) == 1 {
|
||||||
|
m[pe] = make(map[any]any)
|
||||||
|
nn = m[pe]
|
||||||
|
} else {
|
||||||
|
return nil, false, fmt.Errorf("path not found at element %s in path %s", pe, fullPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reflect.ValueOf(ncNode).IsNil() {
|
||||||
|
ncNode = make(map[string]any)
|
||||||
|
nc.Node = ncNode
|
||||||
|
}
|
||||||
|
if m, ok := ncNode.(map[string]any); ok {
|
||||||
|
nn, ok = m[pe]
|
||||||
|
if !ok {
|
||||||
|
// remainPath == 1 means the patch is creation of a new leaf.
|
||||||
|
if createMissing || len(remainPath) == 1 {
|
||||||
|
nextElementNPath := len(remainPath) > 1 && util.IsNPathElement(remainPath[1])
|
||||||
|
if nextElementNPath {
|
||||||
|
m[pe] = make([]any, 0)
|
||||||
|
} else {
|
||||||
|
m[pe] = make(map[string]any)
|
||||||
|
}
|
||||||
|
nn = m[pe]
|
||||||
|
} else {
|
||||||
|
return nil, false, fmt.Errorf("path not found at element %s in path %s", pe, fullPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
npc := &PathContext{
|
||||||
|
Parent: nc,
|
||||||
|
Node: nn,
|
||||||
|
}
|
||||||
|
// for slices, use the address so that the slice can be mutated.
|
||||||
|
if util.IsSlice(nn) {
|
||||||
|
npc.Node = &nn
|
||||||
|
}
|
||||||
|
nc.KeyToChild = pe
|
||||||
|
return getPathContext(npc, fullPath, remainPath[1:], createMissing)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false, fmt.Errorf("leaf type %T in non-leaf Node %s", nc.Node, remainPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPathContext writes the given value to the Node in the given PathContext,
|
||||||
|
// enlarging all PathContext lists to ensure all indexes are valid.
|
||||||
|
func setPathContext(nc *PathContext, value any, merge bool) error {
|
||||||
|
processParent, err := setValueContext(nc, value, merge)
|
||||||
|
if err != nil || !processParent {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the path included insertions, process them now
|
||||||
|
if nc.Parent.Parent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return setPathContext(nc.Parent, nc.Parent.Node, false) // note: tail recursive
|
||||||
|
}
|
||||||
|
|
||||||
|
// setValueContext writes the given value to the Node in the given PathContext.
|
||||||
|
// If setting the value requires growing the final slice, grows it.
|
||||||
|
func setValueContext(nc *PathContext, value any, merge bool) (bool, error) {
|
||||||
|
if nc.Parent == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vv, mapFromString := tryToUnmarshalStringToYAML(value)
|
||||||
|
|
||||||
|
switch parentNode := nc.Parent.Node.(type) {
|
||||||
|
case *any:
|
||||||
|
switch vParentNode := (*parentNode).(type) {
|
||||||
|
case []any:
|
||||||
|
idx := nc.Parent.KeyToChild.(int)
|
||||||
|
if idx == -1 {
|
||||||
|
// Treat -1 as insert-at-end of list
|
||||||
|
idx = len(vParentNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx >= len(vParentNode) {
|
||||||
|
newElements := make([]any, idx-len(vParentNode)+1)
|
||||||
|
vParentNode = append(vParentNode, newElements...)
|
||||||
|
*parentNode = vParentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
merged, err := mergeConditional(vv, nc.Node, merge)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vParentNode[idx] = merged
|
||||||
|
nc.Node = merged
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("don't know about vtype %T", vParentNode)
|
||||||
|
}
|
||||||
|
case map[string]any:
|
||||||
|
key := nc.Parent.KeyToChild.(string)
|
||||||
|
|
||||||
|
// Update is treated differently depending on whether the value is a scalar or map type. If scalar,
|
||||||
|
// insert a new element into the terminal node, otherwise replace the terminal node with the new subtree.
|
||||||
|
if ncNode, ok := nc.Node.(*any); ok && !mapFromString {
|
||||||
|
switch vNcNode := (*ncNode).(type) {
|
||||||
|
case []any:
|
||||||
|
switch vv.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
// the vv is a map, and the node is a slice
|
||||||
|
mergedValue := append(vNcNode, vv)
|
||||||
|
parentNode[key] = mergedValue
|
||||||
|
case *any:
|
||||||
|
merged, err := mergeConditional(vv, vNcNode, merge)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parentNode[key] = merged
|
||||||
|
nc.Node = merged
|
||||||
|
default:
|
||||||
|
// the vv is an basic JSON type (int, float, string, bool)
|
||||||
|
vv = append(vNcNode, vv)
|
||||||
|
parentNode[key] = vv
|
||||||
|
nc.Node = vv
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("don't know about vnc type %T", vNcNode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For map passed as string type, the root is the new key.
|
||||||
|
if mapFromString {
|
||||||
|
if err := util.DeleteFromMap(nc.Parent.Node, nc.Parent.KeyToChild); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
vm := vv.(map[string]any)
|
||||||
|
newKey := getTreeRoot(vm)
|
||||||
|
return false, util.InsertIntoMap(nc.Parent.Node, newKey, vm[newKey])
|
||||||
|
}
|
||||||
|
parentNode[key] = vv
|
||||||
|
nc.Node = vv
|
||||||
|
}
|
||||||
|
// TODO `map[interface{}]interface{}` is used by tests in operator/cmd/mesh, we should add our own tests
|
||||||
|
case map[any]any:
|
||||||
|
key := nc.Parent.KeyToChild.(string)
|
||||||
|
parentNode[key] = vv
|
||||||
|
nc.Node = vv
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("don't know about type %T", parentNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeConditional returns a merge of newVal and originalVal if merge is true, otherwise it returns newVal.
|
||||||
|
func mergeConditional(newVal, originalVal any, merge bool) (any, error) {
|
||||||
|
if !merge || util.IsValueNilOrDefault(originalVal) {
|
||||||
|
return newVal, nil
|
||||||
|
}
|
||||||
|
newS, err := yaml.Marshal(newVal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if util.IsYAMLEmpty(string(newS)) {
|
||||||
|
return originalVal, nil
|
||||||
|
}
|
||||||
|
originalS, err := yaml.Marshal(originalVal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if util.IsYAMLEmpty(string(originalS)) {
|
||||||
|
return newVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedS, err := util.OverlayYAML(string(originalS), string(newS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if util.IsMap(originalVal) {
|
||||||
|
// For JSON compatibility
|
||||||
|
out := make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(mergedS), &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
// For scalars and slices, copy the type
|
||||||
|
out := originalVal
|
||||||
|
if err := yaml.Unmarshal([]byte(mergedS), &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// find returns the value at path from the given tree, or false if the path does not exist.
|
||||||
|
func find(treeNode any, path util.Path) (any, bool) {
|
||||||
|
if len(path) == 0 || treeNode == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
switch nt := treeNode.(type) {
|
||||||
|
case map[any]any:
|
||||||
|
val := nt[path[0]]
|
||||||
|
if val == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if len(path) == 1 {
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
return find(val, path[1:])
|
||||||
|
case map[string]any:
|
||||||
|
val := nt[path[0]]
|
||||||
|
if val == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if len(path) == 1 {
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
return find(val, path[1:])
|
||||||
|
case []any:
|
||||||
|
idx, err := strconv.Atoi(path[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if idx >= len(nt) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
val := nt[idx]
|
||||||
|
return find(val, path[1:])
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringsEqual reports whether the string representations of a and b are equal. a and b may have different types.
|
||||||
|
func stringsEqual(a, b any) bool {
|
||||||
|
return fmt.Sprint(a) == fmt.Sprint(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchesRegex reports whether str regex matches pattern.
|
||||||
|
func matchesRegex(pattern, str any) bool {
|
||||||
|
match, err := regexp.MatchString(fmt.Sprint(pattern), fmt.Sprint(str))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSliceOrPtrInterface reports whether v is a slice, a ptr to slice or interface to slice.
|
||||||
|
func isSliceOrPtrInterface(v any) bool {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() == reflect.Ptr {
|
||||||
|
vv = vv.Elem()
|
||||||
|
}
|
||||||
|
if vv.Kind() == reflect.Interface {
|
||||||
|
vv = vv.Elem()
|
||||||
|
}
|
||||||
|
return vv.Kind() == reflect.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// isMapOrInterface reports whether v is a map, or interface to a map.
|
||||||
|
func isMapOrInterface(v any) bool {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() == reflect.Interface {
|
||||||
|
vv = vv.Elem()
|
||||||
|
}
|
||||||
|
return vv.Kind() == reflect.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryToUnmarshalStringToYAML tries to unmarshal something that may be a YAML list or map into a structure. If not
|
||||||
|
// possible, returns original scalar value.
|
||||||
|
func tryToUnmarshalStringToYAML(s any) (any, bool) {
|
||||||
|
// If value type is a string it could either be a literal string or a map type passed as a string. Try to unmarshal
|
||||||
|
// to discover it's the latter.
|
||||||
|
vv := s
|
||||||
|
|
||||||
|
if reflect.TypeOf(vv).Kind() == reflect.String {
|
||||||
|
sv := strings.Split(vv.(string), "\n")
|
||||||
|
// Need to be careful not to transform string literals into maps unless they really are maps, since scalar handling
|
||||||
|
// is different for inserts.
|
||||||
|
if len(sv) == 1 && strings.Contains(s.(string), ": ") ||
|
||||||
|
len(sv) > 1 && strings.Contains(s.(string), ":") {
|
||||||
|
nv := make(map[string]any)
|
||||||
|
if err := json.Unmarshal([]byte(vv.(string)), &nv); err == nil {
|
||||||
|
// treat JSON as string
|
||||||
|
return vv, false
|
||||||
|
}
|
||||||
|
if err := yaml2.Unmarshal([]byte(vv.(string)), &nv); err == nil {
|
||||||
|
return nv, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// looks like a literal or failed unmarshal, return original type.
|
||||||
|
return vv, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTreeRoot returns the first key found in m. It assumes a single root tree.
|
||||||
|
func getTreeRoot(m map[string]any) string {
|
||||||
|
for k := range m {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
843
pkg/cmd/hgctl/helm/tpath/tree_test.go
Normal file
843
pkg/cmd/hgctl/helm/tpath/tree_test.go
Normal file
@@ -0,0 +1,843 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 tpath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWritePathContext(t *testing.T) {
|
||||||
|
rootYAML := `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- name: n2
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
`
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
path string
|
||||||
|
value any
|
||||||
|
want string
|
||||||
|
wantFound bool
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "AddListEntry",
|
||||||
|
path: `a.b.[name:n2].list`,
|
||||||
|
value: `foo`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- name: n2
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
- foo
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ModifyListEntryValue",
|
||||||
|
path: `a.b.[name:n1].value`,
|
||||||
|
value: `v2`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v2
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ModifyListEntryValueQuoted",
|
||||||
|
path: `a.b.[name:n1].value`,
|
||||||
|
value: `v2`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: "n1"
|
||||||
|
value: v2
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ModifyListEntry",
|
||||||
|
path: `a.b.[name:n2].list.[:v2]`,
|
||||||
|
value: `v3`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v3
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ModifyListEntryMapValue",
|
||||||
|
path: `a.b.[name:n2]`,
|
||||||
|
value: `name: n2
|
||||||
|
list:
|
||||||
|
- nk1: nv1
|
||||||
|
- nk2: nv2`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- name: n2
|
||||||
|
list:
|
||||||
|
- nk1: nv1
|
||||||
|
- nk2: nv2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ModifyNthListEntry",
|
||||||
|
path: `a.b.[1].list.[:v2]`,
|
||||||
|
value: `v-the-second`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v-the-second
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ModifyNthLeafListEntry",
|
||||||
|
path: `a.b.[1].list.[2]`,
|
||||||
|
value: `v-the-third`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v-the-third
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ModifyListEntryValueDotless",
|
||||||
|
path: `a.b[name:n1].value`,
|
||||||
|
value: `v2`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v2
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DeleteListEntry",
|
||||||
|
path: `a.b.[name:n1]`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DeleteListEntryValue",
|
||||||
|
path: `a.b.[name:n2].list.[:v2]`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DeleteListEntryIndex",
|
||||||
|
path: `a.b.[name:n2].list.[1]`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DeleteListEntryValueRegex",
|
||||||
|
path: `a.b.[name:n2].list.[:v3]`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DeleteListLeafEntryBogusIndex",
|
||||||
|
path: `a.b.[name:n2].list.[-200]`,
|
||||||
|
wantFound: false,
|
||||||
|
wantErr: `path a.b.[name:n2].list.[-200]: element [-200] not found`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DeleteListEntryBogusIndex",
|
||||||
|
path: `a.b.[1000000].list.[:v2]`,
|
||||||
|
wantFound: false,
|
||||||
|
wantErr: `index 1000000 exceeds list length 2 at path [1000000].list.[:v2]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "AddMapEntry",
|
||||||
|
path: `a.new_key`,
|
||||||
|
value: `new_val`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- name: n2
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
new_key: new_val
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "AddMapEntryMapValue",
|
||||||
|
path: `a.new_key`,
|
||||||
|
value: `new_key:
|
||||||
|
nk1:
|
||||||
|
nk2: nv2`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v1
|
||||||
|
- name: n2
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
new_key:
|
||||||
|
nk1:
|
||||||
|
nk2: nv2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ModifyMapEntryMapValue",
|
||||||
|
path: `a.b`,
|
||||||
|
value: `nk1:
|
||||||
|
nk2: nv2`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
nk1:
|
||||||
|
nk2: nv2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DeleteMapEntry",
|
||||||
|
path: `a.b`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
a: {}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "path not found",
|
||||||
|
path: `a.c.[name:n2].list.[:v3]`,
|
||||||
|
wantFound: false,
|
||||||
|
wantErr: `path not found at element c in path a.c.[name:n2].list.[:v3]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "error key",
|
||||||
|
path: `a.b.[].list`,
|
||||||
|
wantFound: false,
|
||||||
|
wantErr: `path a.b.[].list: [] is not a valid key:value path element`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid index",
|
||||||
|
path: `a.c.[n2].list.[:v3]`,
|
||||||
|
wantFound: false,
|
||||||
|
wantErr: `path not found at element c in path a.c.[n2].list.[:v3]`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
root := make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pc, gotFound, gotErr := GetPathContext(root, util.PathFromString(tt.path), false)
|
||||||
|
if gotErr, wantErr := errToString(gotErr), tt.wantErr; gotErr != wantErr {
|
||||||
|
t.Fatalf("GetPathContext(%s): gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
|
||||||
|
}
|
||||||
|
if gotFound != tt.wantFound {
|
||||||
|
t.Fatalf("GetPathContext(%s): gotFound:%v, wantFound:%v", tt.desc, gotFound, tt.wantFound)
|
||||||
|
}
|
||||||
|
if tt.wantErr != "" || !tt.wantFound {
|
||||||
|
if tt.want != "" {
|
||||||
|
t.Error("tt.want is set but never checked")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WritePathContext(pc, tt.value, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotYAML := util.ToYAML(root)
|
||||||
|
diff := util.YAMLDiff(gotYAML, tt.want)
|
||||||
|
if diff != "" {
|
||||||
|
t.Errorf("%s: (got:-, want:+):\n%s\n", tt.desc, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteNode(t *testing.T) {
|
||||||
|
testTreeYAML := `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: val1
|
||||||
|
list1:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3a: key1
|
||||||
|
i3b:
|
||||||
|
list2:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3a: key1
|
||||||
|
i3b:
|
||||||
|
i1: va11
|
||||||
|
`
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
baseYAML string
|
||||||
|
path string
|
||||||
|
value string
|
||||||
|
want string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "insert empty",
|
||||||
|
path: "a.b.c",
|
||||||
|
value: "val1",
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: val1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "overwrite",
|
||||||
|
baseYAML: testTreeYAML,
|
||||||
|
path: "a.b.c",
|
||||||
|
value: "val2",
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: val2
|
||||||
|
list1:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3a: key1
|
||||||
|
i3b:
|
||||||
|
list2:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3a: key1
|
||||||
|
i3b:
|
||||||
|
i1: va11
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "partial create",
|
||||||
|
baseYAML: testTreeYAML,
|
||||||
|
path: "a.b.d",
|
||||||
|
value: "val3",
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: val1
|
||||||
|
d: val3
|
||||||
|
list1:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3a: key1
|
||||||
|
i3b:
|
||||||
|
list2:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3a: key1
|
||||||
|
i3b:
|
||||||
|
i1: va11
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "list keys",
|
||||||
|
baseYAML: testTreeYAML,
|
||||||
|
path: "a.b.list1.[i3a:key1].i3b.list2.[i3a:key1].i3b.i1",
|
||||||
|
value: "val2",
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: val1
|
||||||
|
list1:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3a: key1
|
||||||
|
i3b:
|
||||||
|
list2:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3a: key1
|
||||||
|
i3b:
|
||||||
|
i1: val2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
// For https://github.com/istio/istio/issues/20950
|
||||||
|
{
|
||||||
|
desc: "with initial list",
|
||||||
|
baseYAML: `
|
||||||
|
components:
|
||||||
|
ingressGateways:
|
||||||
|
- enabled: true
|
||||||
|
`,
|
||||||
|
path: "components.ingressGateways[0].enabled",
|
||||||
|
value: "false",
|
||||||
|
want: `
|
||||||
|
components:
|
||||||
|
ingressGateways:
|
||||||
|
- enabled: "false"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no initial list",
|
||||||
|
baseYAML: "",
|
||||||
|
path: "components.ingressGateways[0].enabled",
|
||||||
|
value: "false",
|
||||||
|
want: `
|
||||||
|
components:
|
||||||
|
ingressGateways:
|
||||||
|
- enabled: "false"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no initial list for entry",
|
||||||
|
baseYAML: `
|
||||||
|
a: {}
|
||||||
|
`,
|
||||||
|
path: "a.list.[0]",
|
||||||
|
value: "v1",
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ExtendNthLeafListEntry",
|
||||||
|
baseYAML: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
`,
|
||||||
|
path: `a.list.[1]`,
|
||||||
|
value: `v2`,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ExtendLeafListEntryLargeIndex",
|
||||||
|
baseYAML: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
`,
|
||||||
|
path: `a.list.[999]`,
|
||||||
|
value: `v2`,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ExtendLeafListEntryNegativeIndex",
|
||||||
|
baseYAML: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
`,
|
||||||
|
path: `a.list.[-1]`,
|
||||||
|
value: `v2`,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ExtendNthListEntry",
|
||||||
|
baseYAML: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- name: foo
|
||||||
|
`,
|
||||||
|
path: `a.list.[1].name`,
|
||||||
|
value: `bar`,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
list:
|
||||||
|
- name: foo
|
||||||
|
- name: bar
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
root := make(map[string]any)
|
||||||
|
if tt.baseYAML != "" {
|
||||||
|
if err := yaml.Unmarshal([]byte(tt.baseYAML), &root); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p := util.PathFromString(tt.path)
|
||||||
|
err := WriteNode(root, p, tt.value)
|
||||||
|
if gotErr, wantErr := errToString(err), tt.wantErr; gotErr != wantErr {
|
||||||
|
t.Errorf("%s: gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got, want := util.ToYAML(root), tt.want; err == nil && util.YAMLDiff(got, want) != "" {
|
||||||
|
t.Errorf("%s: got:\n%s\nwant:\n%s\ndiff:\n%s\n", tt.desc, got, want, util.YAMLDiff(got, want))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeNode(t *testing.T) {
|
||||||
|
testTreeYAML := `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: val1
|
||||||
|
list1:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
`
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
baseYAML string
|
||||||
|
path string
|
||||||
|
value string
|
||||||
|
want string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "merge list entry",
|
||||||
|
baseYAML: testTreeYAML,
|
||||||
|
path: "a.b.list1.[i1:val1]",
|
||||||
|
value: `
|
||||||
|
i2b: val2`,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: val1
|
||||||
|
list1:
|
||||||
|
- i1: val1
|
||||||
|
i2b: val2
|
||||||
|
- i2: val2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "merge list 2",
|
||||||
|
baseYAML: testTreeYAML,
|
||||||
|
path: "a.b.list1",
|
||||||
|
value: `
|
||||||
|
i3:
|
||||||
|
a: val3
|
||||||
|
`,
|
||||||
|
want: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: val1
|
||||||
|
list1:
|
||||||
|
- i1: val1
|
||||||
|
- i2: val2
|
||||||
|
- i3:
|
||||||
|
a: val3
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
root := make(map[string]any)
|
||||||
|
if tt.baseYAML != "" {
|
||||||
|
if err := yaml.Unmarshal([]byte(tt.baseYAML), &root); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p := util.PathFromString(tt.path)
|
||||||
|
iv := make(map[string]any)
|
||||||
|
err := yaml.Unmarshal([]byte(tt.value), &iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = MergeNode(root, p, iv)
|
||||||
|
if gotErr, wantErr := errToString(err), tt.wantErr; gotErr != wantErr {
|
||||||
|
t.Errorf("%s: gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got, want := util.ToYAML(root), tt.want; err == nil && util.YAMLDiff(got, want) != "" {
|
||||||
|
t.Errorf("%s: got:\n%s\nwant:\n%s\ndiff:\n%s\n", tt.desc, got, want, util.YAMLDiff(got, want))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errToString returns the string representation of err and the empty string if
|
||||||
|
// err is nil.
|
||||||
|
func errToString(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSecretVolumes simulates https://github.com/istio/istio/issues/20381
|
||||||
|
func TestSecretVolumes(t *testing.T) {
|
||||||
|
rootYAML := `
|
||||||
|
values:
|
||||||
|
gateways:
|
||||||
|
istio-egressgateway:
|
||||||
|
secretVolumes: []
|
||||||
|
`
|
||||||
|
root := make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
overrides := []struct {
|
||||||
|
path string
|
||||||
|
value any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[0].name",
|
||||||
|
value: "egressgateway-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[0].secretName",
|
||||||
|
value: "istio-egressgateway-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[0].mountPath",
|
||||||
|
value: "/etc/istio/egressgateway-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[1].name",
|
||||||
|
value: "egressgateway-ca-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[1].secretName",
|
||||||
|
value: "istio-egressgateway-ca-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[1].mountPath",
|
||||||
|
value: "/etc/istio/egressgateway-ca-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[2].name",
|
||||||
|
value: "nginx-client-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[2].secretName",
|
||||||
|
value: "nginx-client-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[2].mountPath",
|
||||||
|
value: "/etc/istio/nginx-client-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[3].name",
|
||||||
|
value: "nginx-ca-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[3].secretName",
|
||||||
|
value: "nginx-ca-certs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "values.gateways.istio-egressgateway.secretVolumes[3].mountPath",
|
||||||
|
value: "/etc/istio/nginx-ca-certs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, override := range overrides {
|
||||||
|
|
||||||
|
pc, _, err := GetPathContext(root, util.PathFromString(override.path), true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetPathContext(%q): %v", override.path, err)
|
||||||
|
}
|
||||||
|
err = WritePathContext(pc, override.value, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("WritePathContext(%q): %v", override.path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
want := `
|
||||||
|
values:
|
||||||
|
gateways:
|
||||||
|
istio-egressgateway:
|
||||||
|
secretVolumes:
|
||||||
|
- mountPath: /etc/istio/egressgateway-certs
|
||||||
|
name: egressgateway-certs
|
||||||
|
secretName: istio-egressgateway-certs
|
||||||
|
- mountPath: /etc/istio/egressgateway-ca-certs
|
||||||
|
name: egressgateway-ca-certs
|
||||||
|
secretName: istio-egressgateway-ca-certs
|
||||||
|
- mountPath: /etc/istio/nginx-client-certs
|
||||||
|
name: nginx-client-certs
|
||||||
|
secretName: nginx-client-certs
|
||||||
|
- mountPath: /etc/istio/nginx-ca-certs
|
||||||
|
name: nginx-ca-certs
|
||||||
|
secretName: nginx-ca-certs
|
||||||
|
`
|
||||||
|
gotYAML := util.ToYAML(root)
|
||||||
|
diff := util.YAMLDiff(gotYAML, want)
|
||||||
|
if diff != "" {
|
||||||
|
t.Errorf("TestSecretVolumes: diff:\n%s\n", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulates https://github.com/istio/istio/issues/19196
|
||||||
|
func TestWriteEscapedPathContext(t *testing.T) {
|
||||||
|
rootYAML := `
|
||||||
|
values:
|
||||||
|
sidecarInjectorWebhook:
|
||||||
|
injectedAnnotations: {}
|
||||||
|
`
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
path string
|
||||||
|
value any
|
||||||
|
want string
|
||||||
|
wantFound bool
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "ModifyEscapedPathValue",
|
||||||
|
path: `values.sidecarInjectorWebhook.injectedAnnotations.container\.apparmor\.security\.beta\.kubernetes\.io/istio-proxy`,
|
||||||
|
value: `runtime/default`,
|
||||||
|
wantFound: true,
|
||||||
|
want: `
|
||||||
|
values:
|
||||||
|
sidecarInjectorWebhook:
|
||||||
|
injectedAnnotations:
|
||||||
|
container.apparmor.security.beta.kubernetes.io/istio-proxy: runtime/default
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
root := make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pc, gotFound, gotErr := GetPathContext(root, util.PathFromString(tt.path), false)
|
||||||
|
if gotErr, wantErr := errToString(gotErr), tt.wantErr; gotErr != wantErr {
|
||||||
|
t.Fatalf("GetPathContext(%s): gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
|
||||||
|
}
|
||||||
|
if gotFound != tt.wantFound {
|
||||||
|
t.Fatalf("GetPathContext(%s): gotFound:%v, wantFound:%v", tt.desc, gotFound, tt.wantFound)
|
||||||
|
}
|
||||||
|
if tt.wantErr != "" || !tt.wantFound {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WritePathContext(pc, tt.value, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotYAML := util.ToYAML(root)
|
||||||
|
diff := util.YAMLDiff(gotYAML, tt.want)
|
||||||
|
if diff != "" {
|
||||||
|
t.Errorf("%s: diff:\n%s\n", tt.desc, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
58
pkg/cmd/hgctl/helm/tpath/util.go
Normal file
58
pkg/cmd/hgctl/helm/tpath/util.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 tpath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
yaml2 "sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddSpecRoot adds a root node called "spec" to the given tree and returns the resulting tree.
|
||||||
|
func AddSpecRoot(tree string) (string, error) {
|
||||||
|
t, nt := make(map[string]any), make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(tree), &t); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
nt["spec"] = t
|
||||||
|
out, err := yaml.Marshal(nt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSpecSubtree returns the subtree under "spec".
|
||||||
|
func GetSpecSubtree(yml string) (string, error) {
|
||||||
|
return GetConfigSubtree(yml, "spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigSubtree returns the subtree at the given path.
|
||||||
|
func GetConfigSubtree(manifest, path string) (string, error) {
|
||||||
|
root := make(map[string]any)
|
||||||
|
if err := yaml2.Unmarshal([]byte(manifest), &root); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
nc, _, err := GetPathContext(root, util.PathFromString(path), false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
out, err := yaml2.Marshal(nc.Node)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
122
pkg/cmd/hgctl/helm/tpath/util_test.go
Normal file
122
pkg/cmd/hgctl/helm/tpath/util_test.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 tpath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddSpecRoot(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
expect string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
in: ``,
|
||||||
|
expect: `spec: {}
|
||||||
|
`,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "add-root",
|
||||||
|
in: `
|
||||||
|
a: va
|
||||||
|
b: foo`,
|
||||||
|
expect: `spec:
|
||||||
|
a: va
|
||||||
|
b: foo
|
||||||
|
`,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "err",
|
||||||
|
in: `i can't be yaml, can I?`,
|
||||||
|
expect: ``,
|
||||||
|
err: errors.New(""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got, err := AddSpecRoot(tt.in); got != tt.expect ||
|
||||||
|
((err != nil && tt.err == nil) || (err == nil && tt.err != nil)) {
|
||||||
|
t.Errorf("%s AddSpecRoot(%s) => %s, want %s", tt.desc, tt.in, got, tt.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfigSubtree(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
manifest string
|
||||||
|
path string
|
||||||
|
expect string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
manifest: ``,
|
||||||
|
path: ``,
|
||||||
|
expect: `{}
|
||||||
|
`,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "subtree",
|
||||||
|
manifest: `
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
- name: n1
|
||||||
|
value: v2
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
path: `a`,
|
||||||
|
expect: `b:
|
||||||
|
- name: n1
|
||||||
|
value: v2
|
||||||
|
- list:
|
||||||
|
- v1
|
||||||
|
- v2
|
||||||
|
- v3_regex
|
||||||
|
name: n2
|
||||||
|
`,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "err",
|
||||||
|
manifest: "not-yaml",
|
||||||
|
path: "not-subnode",
|
||||||
|
expect: ``,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got, err := GetConfigSubtree(tt.manifest, tt.path); got != tt.expect || (err == nil) == tt.err {
|
||||||
|
t.Errorf("%s GetConfigSubtree(%s, %s) => %s, want %s", tt.desc, tt.manifest, tt.path, got, tt.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
194
pkg/cmd/hgctl/install.go
Normal file
194
pkg/cmd/hgctl/install.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 hgctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/options"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
setFlagHelpStr = `Override an higress profile value, e.g. to choose a profile
|
||||||
|
(--set profile=local-k8s), or override profile values (--set gateway.replicas=2), or override helm values (--set values.global.proxy.resources.requsts.cpu=500m).`
|
||||||
|
// manifestsFlagHelpStr is the command line description for --manifests
|
||||||
|
manifestsFlagHelpStr = `Specify a path to a directory of profiles
|
||||||
|
(e.g. ~/Downloads/higress/manifests).`
|
||||||
|
outputHelpstr = "Specify a file to write profile yaml"
|
||||||
|
|
||||||
|
profileNameK8s = "k8s"
|
||||||
|
profileNameLocalK8s = "local-k8s"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InstallArgs struct {
|
||||||
|
InFilenames []string
|
||||||
|
// KubeConfigPath is the path to kube config file.
|
||||||
|
KubeConfigPath string
|
||||||
|
// Context is the cluster context in the kube config
|
||||||
|
Context string
|
||||||
|
// Set is a string with element format "path=value" where path is an profile path and the value is a
|
||||||
|
// value to set the node at that path to.
|
||||||
|
Set []string
|
||||||
|
// ManifestsPath is a path to a ManifestsPath and profiles directory in the local filesystem with a release tgz.
|
||||||
|
ManifestsPath string
|
||||||
|
// verbose generates verbose output.
|
||||||
|
verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *InstallArgs) String() string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("KubeConfigPath: " + a.KubeConfigPath + "\n")
|
||||||
|
b.WriteString("Context: " + a.Context + "\n")
|
||||||
|
b.WriteString("Set: " + fmt.Sprint(a.Set) + "\n")
|
||||||
|
b.WriteString("ManifestsPath: " + a.ManifestsPath + "\n")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInstallFlags(cmd *cobra.Command, args *InstallArgs) {
|
||||||
|
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
|
||||||
|
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --manifests is an alias for --set installPackagePath=
|
||||||
|
func applyFlagAliases(flags []string, manifestsPath string) []string {
|
||||||
|
if manifestsPath != "" {
|
||||||
|
flags = append(flags, fmt.Sprintf("installPackagePath=%s", manifestsPath))
|
||||||
|
}
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// newInstallCmd generates a higress install manifest and applies it to a cluster
|
||||||
|
func newInstallCmd() *cobra.Command {
|
||||||
|
iArgs := &InstallArgs{}
|
||||||
|
installCmd := &cobra.Command{
|
||||||
|
Use: "install",
|
||||||
|
Short: "Applies an higress manifest, installing or reconfiguring higress on a cluster.",
|
||||||
|
Long: "The install command generates an higress install manifest and applies it to a cluster.",
|
||||||
|
// nolint: lll
|
||||||
|
Example: ` # Apply a default higress installation
|
||||||
|
hgctl install
|
||||||
|
|
||||||
|
# Install higress on local kubernetes cluster
|
||||||
|
hgctl install --set profile=local-k8s
|
||||||
|
|
||||||
|
# To override profile setting
|
||||||
|
hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2"
|
||||||
|
|
||||||
|
# To override helm setting
|
||||||
|
hgctl install --set profile=local-k8s --set values.global.proxy.resources.requsts.cpu=500m"
|
||||||
|
|
||||||
|
`,
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return Install(cmd.OutOrStdout(), iArgs)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
addInstallFlags(installCmd, iArgs)
|
||||||
|
flags := installCmd.Flags()
|
||||||
|
options.AddKubeConfigFlags(flags)
|
||||||
|
return installCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func Install(writer io.Writer, iArgs *InstallArgs) error {
|
||||||
|
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
|
||||||
|
|
||||||
|
// check profileName
|
||||||
|
psf := helm.GetValueForSetFlag(setFlags, "profile")
|
||||||
|
if len(psf) == 0 {
|
||||||
|
psf = promptProfileName(writer)
|
||||||
|
setFlags = append(setFlags, fmt.Sprintf("profile=%s", psf))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, profile, profileName, err := helm.GenerateConfig(iArgs.InFilenames, setFlags)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate config: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to install higress on profile:%s ......\n", profileName)
|
||||||
|
|
||||||
|
fmt.Fprintf(writer, "start to validate profile:%s ......\n", profileName)
|
||||||
|
|
||||||
|
err = profile.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = InstallManifests(profile, writer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to install manifests: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptProfileName(writer io.Writer) string {
|
||||||
|
answer := ""
|
||||||
|
fmt.Fprintf(writer, "Please select higress install configration profile:\n")
|
||||||
|
fmt.Fprintf(writer, "1.Install higress to local kubernetes cluster like kind etc.\n")
|
||||||
|
fmt.Fprintf(writer, "2.Install higress to kubernetes cluster\n")
|
||||||
|
for {
|
||||||
|
fmt.Fprintf(writer, "Please input 1 or 2 to select, input your selection:")
|
||||||
|
fmt.Scanln(&answer)
|
||||||
|
if strings.TrimSpace(answer) == "1" {
|
||||||
|
return profileNameLocalK8s
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(answer) == "2" {
|
||||||
|
return profileNameK8s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profileNameLocalK8s
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstallManifests(profile *helm.Profile, writer io.Writer) error {
|
||||||
|
fmt.Fprintf(writer, "start to check kubernetes cluster enviroment ......\n")
|
||||||
|
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build kubernetes client: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to init higress installer ......\n")
|
||||||
|
op, err := installer.NewInstaller(profile, cliClient, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to run higress installer ......\n")
|
||||||
|
if err := op.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to render manifests ......\n")
|
||||||
|
manifestMap, err := op.RenderManifests()
|
||||||
|
|
||||||
|
//for name, yaml := range manifestMap {
|
||||||
|
// fileName := "~/Downloads/higress/manifests/" + string(name) + ".yaml"
|
||||||
|
// os.WriteFile(fileName, []byte(yaml), 0640)
|
||||||
|
//}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to apply manifests ......\n")
|
||||||
|
if err := op.ApplyManifests(manifestMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "install higress complete!\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
295
pkg/cmd/hgctl/installer/component.go
Normal file
295
pkg/cmd/hgctl/installer/component.go
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 installer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ComponentName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Higress ComponentName = "higress"
|
||||||
|
Istio ComponentName = "istio"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ComponentMap = map[string]ComponentName{
|
||||||
|
"higress": Higress,
|
||||||
|
"istio": Istio,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Component interface {
|
||||||
|
// ComponentName returns the name of the component.
|
||||||
|
ComponentName() ComponentName
|
||||||
|
// Namespace returns the namespace for the component.
|
||||||
|
Namespace() string
|
||||||
|
// Enabled reports whether the component is enabled.
|
||||||
|
Enabled() bool
|
||||||
|
// Run starts the component. Must be called before the component is used.
|
||||||
|
Run() error
|
||||||
|
RenderManifest() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentOptions struct {
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
// local
|
||||||
|
ChartPath string
|
||||||
|
// remote
|
||||||
|
RepoURL string
|
||||||
|
ChartName string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentOption func(*ComponentOptions)
|
||||||
|
|
||||||
|
func WithComponentNamespace(namespace string) ComponentOption {
|
||||||
|
return func(opts *ComponentOptions) {
|
||||||
|
opts.Namespace = namespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithComponentChartPath(path string) ComponentOption {
|
||||||
|
return func(opts *ComponentOptions) {
|
||||||
|
opts.ChartPath = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithComponentChartName(chartName string) ComponentOption {
|
||||||
|
return func(opts *ComponentOptions) {
|
||||||
|
opts.ChartName = chartName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithComponentRepoURL(url string) ComponentOption {
|
||||||
|
return func(opts *ComponentOptions) {
|
||||||
|
opts.RepoURL = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithComponentVersion(version string) ComponentOption {
|
||||||
|
return func(opts *ComponentOptions) {
|
||||||
|
opts.Version = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HigressComponent struct {
|
||||||
|
profile *helm.Profile
|
||||||
|
started bool
|
||||||
|
opts *ComponentOptions
|
||||||
|
renderer helm.Renderer
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HigressComponent) ComponentName() ComponentName {
|
||||||
|
return Higress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HigressComponent) Namespace() string {
|
||||||
|
return h.opts.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HigressComponent) Enabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HigressComponent) Run() error {
|
||||||
|
// Parse latest version
|
||||||
|
if h.opts.Version == helm.RepoLatestVersion {
|
||||||
|
fmt.Fprintf(h.writer, "start to get higress helm chart latest version ......")
|
||||||
|
}
|
||||||
|
latestVersion, err := helm.ParseLatestVersion(h.opts.RepoURL, h.opts.Version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(h.writer, "latest version is %s\n", latestVersion)
|
||||||
|
|
||||||
|
// Reset helm chart version
|
||||||
|
h.opts.Version = latestVersion
|
||||||
|
h.renderer.SetVersion(latestVersion)
|
||||||
|
fmt.Fprintf(h.writer, "start to download higress helm chart version: %s, url: %s\n", h.opts.Version, h.opts.RepoURL)
|
||||||
|
|
||||||
|
if err := h.renderer.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.started = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HigressComponent) RenderManifest() (string, error) {
|
||||||
|
if !h.started {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
fmt.Fprintf(h.writer, "start to render higress helm chart......\n")
|
||||||
|
valsYaml, err := h.profile.ValuesYaml()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
manifest, err2 := renderComponentManifest(valsYaml, h.renderer, true, h.ComponentName(), h.opts.Namespace)
|
||||||
|
if err2 != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return manifest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) {
|
||||||
|
newOpts := &ComponentOptions{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(newOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
var renderer helm.Renderer
|
||||||
|
var err error
|
||||||
|
if newOpts.RepoURL != "" {
|
||||||
|
renderer, err = helm.NewRemoteRenderer(
|
||||||
|
helm.WithName(newOpts.ChartName),
|
||||||
|
helm.WithNamespace(newOpts.Namespace),
|
||||||
|
helm.WithRepoURL(newOpts.RepoURL),
|
||||||
|
helm.WithVersion(newOpts.Version),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderer, err = helm.NewLocalRenderer(
|
||||||
|
helm.WithName(newOpts.ChartName),
|
||||||
|
helm.WithNamespace(newOpts.Namespace),
|
||||||
|
helm.WithVersion(newOpts.Version),
|
||||||
|
helm.WithFS(os.DirFS(newOpts.ChartPath)),
|
||||||
|
helm.WithDir(string(Higress)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
higressComponent := &HigressComponent{
|
||||||
|
profile: profile,
|
||||||
|
renderer: renderer,
|
||||||
|
opts: newOpts,
|
||||||
|
writer: writer,
|
||||||
|
}
|
||||||
|
return higressComponent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IstioCRDComponent struct {
|
||||||
|
profile *helm.Profile
|
||||||
|
started bool
|
||||||
|
opts *ComponentOptions
|
||||||
|
renderer helm.Renderer
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) {
|
||||||
|
newOpts := &ComponentOptions{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(newOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
var renderer helm.Renderer
|
||||||
|
var err error
|
||||||
|
if newOpts.RepoURL != "" {
|
||||||
|
renderer, err = helm.NewRemoteRenderer(
|
||||||
|
helm.WithName(newOpts.ChartName),
|
||||||
|
helm.WithNamespace(newOpts.Namespace),
|
||||||
|
helm.WithRepoURL(newOpts.RepoURL),
|
||||||
|
helm.WithVersion(newOpts.Version),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderer, err = helm.NewLocalRenderer(
|
||||||
|
helm.WithName(newOpts.ChartName),
|
||||||
|
helm.WithNamespace(newOpts.Namespace),
|
||||||
|
helm.WithVersion(newOpts.Version),
|
||||||
|
helm.WithFS(os.DirFS(newOpts.ChartPath)),
|
||||||
|
helm.WithDir(string(Istio)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
istioComponent := &IstioCRDComponent{
|
||||||
|
profile: profile,
|
||||||
|
renderer: renderer,
|
||||||
|
opts: newOpts,
|
||||||
|
writer: writer,
|
||||||
|
}
|
||||||
|
return istioComponent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IstioCRDComponent) ComponentName() ComponentName {
|
||||||
|
return Istio
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IstioCRDComponent) Namespace() string {
|
||||||
|
return i.opts.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IstioCRDComponent) Enabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IstioCRDComponent) Run() error {
|
||||||
|
fmt.Fprintf(i.writer, "start to download istio helm chart version: %s, url: %s\n", i.opts.Version, i.opts.RepoURL)
|
||||||
|
if err := i.renderer.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.started = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IstioCRDComponent) RenderManifest() (string, error) {
|
||||||
|
if !i.started {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
fmt.Fprintf(i.writer, "start to render istio helm chart......\n")
|
||||||
|
values := make(map[string]any)
|
||||||
|
manifest, err := renderComponentManifest(values, i.renderer, false, i.ComponentName(), i.opts.Namespace)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return manifest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderComponentManifest(spec any, renderer helm.Renderer, addOn bool, name ComponentName, namespace string) (string, error) {
|
||||||
|
var valsBytes []byte
|
||||||
|
var valsYaml string
|
||||||
|
var err error
|
||||||
|
if yamlString, ok := spec.(string); ok {
|
||||||
|
valsYaml = yamlString
|
||||||
|
} else {
|
||||||
|
if !util.IsValueNil(spec) {
|
||||||
|
valsBytes, err = yaml.Marshal(spec)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
valsYaml = string(valsBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final, err := renderer.RenderManifest(valsYaml)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return final, nil
|
||||||
|
}
|
||||||
193
pkg/cmd/hgctl/installer/installer.go
Normal file
193
pkg/cmd/hgctl/installer/installer.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 installer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/object"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Installer struct {
|
||||||
|
started bool
|
||||||
|
components map[ComponentName]Component
|
||||||
|
kubeCli kubernetes.CLIClient
|
||||||
|
profile *helm.Profile
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run must be invoked before invoking other functions.
|
||||||
|
func (o *Installer) Run() error {
|
||||||
|
for name, component := range o.components {
|
||||||
|
if !component.Enabled() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := component.Run(); err != nil {
|
||||||
|
return fmt.Errorf("component %s run failed, err: %s", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.started = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderManifests renders component manifests specified by profile.
|
||||||
|
func (o *Installer) RenderManifests() (map[ComponentName]string, error) {
|
||||||
|
if !o.started {
|
||||||
|
return nil, errors.New("HigressOperator is not running")
|
||||||
|
}
|
||||||
|
res := make(map[ComponentName]string)
|
||||||
|
for name, component := range o.components {
|
||||||
|
if !component.Enabled() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
manifest, err := component.RenderManifest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("component %s RenderManifest err: %v", name, err)
|
||||||
|
}
|
||||||
|
res[name] = manifest
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyManifests apply component manifests to k8s cluster
|
||||||
|
func (o *Installer) ApplyManifests(manifestMap map[ComponentName]string) error {
|
||||||
|
if o.kubeCli == nil {
|
||||||
|
return errors.New("no injected k8s cli into Installer")
|
||||||
|
}
|
||||||
|
for name, manifest := range manifestMap {
|
||||||
|
namespace := o.components[name].Namespace()
|
||||||
|
if err := o.applyManifest(manifest, namespace); err != nil {
|
||||||
|
return fmt.Errorf("component %s ApplyManifest err: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Installer) applyManifest(manifest string, ns string) error {
|
||||||
|
if err := o.kubeCli.CreateNamespace(ns); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, obj := range objs {
|
||||||
|
// check namespaced object if namespace property has been existed
|
||||||
|
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||||
|
obj.Namespace = ns
|
||||||
|
obj.UnstructuredObject().SetNamespace(ns)
|
||||||
|
}
|
||||||
|
if o.isNamespacedObject(obj) {
|
||||||
|
fmt.Fprintf(o.writer, "start to apply object kind: %s, object name: %s on namespace: %s ......\n", obj.Kind, obj.Name, obj.Namespace)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(o.writer, "start to apply object kind: %s, object name: %s ......\n", obj.Kind, obj.Name)
|
||||||
|
}
|
||||||
|
if err := o.kubeCli.ApplyObject(obj.UnstructuredObject()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteManifests delete component manifests to k8s cluster
|
||||||
|
func (o *Installer) DeleteManifests(manifestMap map[ComponentName]string) error {
|
||||||
|
if o.kubeCli == nil {
|
||||||
|
return errors.New("no injected k8s cli into Installer")
|
||||||
|
}
|
||||||
|
for name, manifest := range manifestMap {
|
||||||
|
namespace := o.components[name].Namespace()
|
||||||
|
if err := o.deleteManifest(manifest, namespace); err != nil {
|
||||||
|
return fmt.Errorf("component %s DeleteManifest err: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteManifest delete manifest to certain namespace
|
||||||
|
func (o *Installer) deleteManifest(manifest string, ns string) error {
|
||||||
|
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, obj := range objs {
|
||||||
|
// check namespaced object if namespace property has been existed
|
||||||
|
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||||
|
obj.Namespace = ns
|
||||||
|
obj.UnstructuredObject().SetNamespace(ns)
|
||||||
|
}
|
||||||
|
if o.isNamespacedObject(obj) {
|
||||||
|
fmt.Fprintf(o.writer, "start to delete object kind: %s, object name: %s on namespace: %s ......\n", obj.Kind, obj.Name, obj.Namespace)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(o.writer, "start to delete object kind: %s, object name: %s ......\n", obj.Kind, obj.Name)
|
||||||
|
}
|
||||||
|
if err := o.kubeCli.DeleteObject(obj.UnstructuredObject()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Installer) isNamespacedObject(obj *object.K8sObject) bool {
|
||||||
|
if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer) (*Installer, error) {
|
||||||
|
if profile == nil {
|
||||||
|
return nil, errors.New("Install profile is empty")
|
||||||
|
}
|
||||||
|
// initialize components
|
||||||
|
components := make(map[ComponentName]Component)
|
||||||
|
higressComponent, err := NewHigressComponent(profile, writer,
|
||||||
|
WithComponentNamespace(profile.Global.Namespace),
|
||||||
|
WithComponentChartPath(profile.InstallPackagePath),
|
||||||
|
WithComponentVersion(profile.Charts.Higress.Version),
|
||||||
|
WithComponentRepoURL(profile.Charts.Higress.Url),
|
||||||
|
WithComponentChartName(profile.Charts.Higress.Name),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err)
|
||||||
|
}
|
||||||
|
components[Higress] = higressComponent
|
||||||
|
|
||||||
|
if profile.IstioEnabled() {
|
||||||
|
istioCRDComponent, err := NewIstioCRDComponent(profile, writer,
|
||||||
|
WithComponentNamespace(profile.Global.IstioNamespace),
|
||||||
|
WithComponentChartPath(profile.InstallPackagePath),
|
||||||
|
WithComponentVersion(profile.Charts.Istio.Version),
|
||||||
|
WithComponentRepoURL(profile.Charts.Istio.Url),
|
||||||
|
WithComponentChartName(profile.Charts.Istio.Name),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err)
|
||||||
|
}
|
||||||
|
components[Istio] = istioCRDComponent
|
||||||
|
}
|
||||||
|
op := &Installer{
|
||||||
|
profile: profile,
|
||||||
|
components: components,
|
||||||
|
kubeCli: cli,
|
||||||
|
writer: writer,
|
||||||
|
}
|
||||||
|
return op, nil
|
||||||
|
}
|
||||||
@@ -19,17 +19,21 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
kubescheme "k8s.io/client-go/kubernetes/scheme"
|
kubescheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
"k8s.io/client-go/tools/remotecommand"
|
"k8s.io/client-go/tools/remotecommand"
|
||||||
|
"k8s.io/client-go/util/retry"
|
||||||
|
ctrClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLIClient interface {
|
type CLIClient interface {
|
||||||
@@ -44,6 +48,15 @@ type CLIClient interface {
|
|||||||
|
|
||||||
// PodExec takes a command and the pod data to run the command in the specified pod.
|
// PodExec takes a command and the pod data to run the command in the specified pod.
|
||||||
PodExec(namespacedName types.NamespacedName, container string, command string) (stdout string, stderr string, err error)
|
PodExec(namespacedName types.NamespacedName, container string, command string) (stdout string, stderr string, err error)
|
||||||
|
|
||||||
|
// ApplyObject creates or updates unstructured object
|
||||||
|
ApplyObject(obj *unstructured.Unstructured) error
|
||||||
|
|
||||||
|
// DeleteObject delete unstructured object
|
||||||
|
DeleteObject(obj *unstructured.Unstructured) error
|
||||||
|
|
||||||
|
// CreateNamespace create namespace
|
||||||
|
CreateNamespace(namespace string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ CLIClient = &client{}
|
var _ CLIClient = &client{}
|
||||||
@@ -52,6 +65,7 @@ type client struct {
|
|||||||
config *rest.Config
|
config *rest.Config
|
||||||
restClient *rest.RESTClient
|
restClient *rest.RESTClient
|
||||||
kube kubernetes.Interface
|
kube kubernetes.Interface
|
||||||
|
ctrClient ctrClient.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCLIClient(clientConfig clientcmd.ClientConfig) (CLIClient, error) {
|
func NewCLIClient(clientConfig clientcmd.ClientConfig) (CLIClient, error) {
|
||||||
@@ -80,33 +94,13 @@ func newClientInternal(clientConfig clientcmd.ClientConfig) (*client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.ctrClient, err = ctrClient.New(c.config, ctrClient.Options{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &c, err
|
return &c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func setRestDefaults(config *rest.Config) *rest.Config {
|
|
||||||
if config.GroupVersion == nil || config.GroupVersion.Empty() {
|
|
||||||
config.GroupVersion = &corev1.SchemeGroupVersion
|
|
||||||
}
|
|
||||||
if len(config.APIPath) == 0 {
|
|
||||||
if len(config.GroupVersion.Group) == 0 {
|
|
||||||
config.APIPath = "/api"
|
|
||||||
} else {
|
|
||||||
config.APIPath = "/apis"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(config.ContentType) == 0 {
|
|
||||||
config.ContentType = runtime.ContentTypeJSON
|
|
||||||
}
|
|
||||||
if config.NegotiatedSerializer == nil {
|
|
||||||
// This codec factory ensures the resources are not converted. Therefore, resources
|
|
||||||
// will not be round-tripped through internal versions. Defaulting does not happen
|
|
||||||
// on the client.
|
|
||||||
config.NegotiatedSerializer = serializer.NewCodecFactory(kubescheme.Scheme).WithoutConversion()
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) RESTConfig() *rest.Config {
|
func (c *client) RESTConfig() *rest.Config {
|
||||||
if c.config == nil {
|
if c.config == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -170,3 +164,85 @@ func (c *client) PodExec(namespacedName types.NamespacedName, container string,
|
|||||||
stderr = stderrBuf.String()
|
stderr = stderrBuf.String()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteObject delete unstructured object
|
||||||
|
func (c *client) DeleteObject(obj *unstructured.Unstructured) error {
|
||||||
|
err := c.ctrClient.Delete(context.TODO(), obj, ctrClient.PropagationPolicy(metav1.DeletePropagationBackground))
|
||||||
|
if err != nil {
|
||||||
|
if !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyObject creates or updates unstructured object
|
||||||
|
func (c *client) ApplyObject(obj *unstructured.Unstructured) error {
|
||||||
|
if obj.GetKind() == "List" {
|
||||||
|
objList, err := obj.ToList()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range objList.Items {
|
||||||
|
if err := c.ApplyObject(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ctrClient.ObjectKeyFromObject(obj)
|
||||||
|
receiver := &unstructured.Unstructured{}
|
||||||
|
receiver.SetGroupVersionKind(obj.GroupVersionKind())
|
||||||
|
|
||||||
|
if err := retry.RetryOnConflict(wait.Backoff{
|
||||||
|
Duration: time.Millisecond * 10,
|
||||||
|
Factor: 2,
|
||||||
|
Steps: 3,
|
||||||
|
}, func() error {
|
||||||
|
if err := c.ctrClient.Get(context.Background(), key, receiver); err != nil {
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
if err := c.ctrClient.Create(context.Background(), obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := applyOverlay(receiver, obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.ctrClient.Update(context.Background(), receiver); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNamespace create namespace
|
||||||
|
func (c *client) CreateNamespace(namespace string) error {
|
||||||
|
key := ctrClient.ObjectKey{
|
||||||
|
Namespace: metav1.NamespaceSystem,
|
||||||
|
Name: namespace,
|
||||||
|
}
|
||||||
|
if err := c.ctrClient.Get(context.Background(), key, &corev1.Namespace{}); err != nil {
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
nsObj := &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: metav1.NamespaceSystem,
|
||||||
|
Name: namespace,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := c.ctrClient.Create(context.Background(), nsObj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to check if namespace %v exists: %v", namespace, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
157
pkg/cmd/hgctl/kubernetes/common.go
Normal file
157
pkg/cmd/hgctl/kubernetes/common.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
kubescheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// applyOverlay applies an overlay using JSON patch strategy over the current Object in place.
|
||||||
|
func applyOverlay(current, overlay *unstructured.Unstructured) error {
|
||||||
|
cj, err := runtime.Encode(unstructured.UnstructuredJSONScheme, current)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayUpdated := overlay.DeepCopy()
|
||||||
|
if strings.EqualFold(current.GetKind(), "service") {
|
||||||
|
if err := saveClusterIP(current, overlayUpdated); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
saveNodePorts(current, overlayUpdated)
|
||||||
|
}
|
||||||
|
|
||||||
|
if current.GetKind() == "PersistentVolumeClaim" {
|
||||||
|
if err := savePersistentVolumeClaim(current, overlayUpdated); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uj, err := runtime.Encode(unstructured.UnstructuredJSONScheme, overlayUpdated)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
merged, err := jsonpatch.MergePatch(cj, uj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return runtime.DecodeInto(unstructured.UnstructuredJSONScheme, merged, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createPortMap returns a map, mapping the value of the port and value of the nodePort
|
||||||
|
func createPortMap(current *unstructured.Unstructured) map[string]uint32 {
|
||||||
|
portMap := make(map[string]uint32)
|
||||||
|
svc := &corev1.Service{}
|
||||||
|
if err := scheme.Scheme.Convert(current, svc, nil); err != nil {
|
||||||
|
return portMap
|
||||||
|
}
|
||||||
|
for _, p := range svc.Spec.Ports {
|
||||||
|
portMap[strconv.Itoa(int(p.Port))] = uint32(p.NodePort)
|
||||||
|
}
|
||||||
|
return portMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// savePersistentVolumeClaim copies the storageClassName from the current cluster into the overlay
|
||||||
|
func savePersistentVolumeClaim(current, overlay *unstructured.Unstructured) error {
|
||||||
|
// Save the value of spec.storageClassName set by the cluster
|
||||||
|
if storageClassName, found, err := unstructured.NestedString(current.Object, "spec",
|
||||||
|
"storageClassName"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if found {
|
||||||
|
if _, _, err2 := unstructured.NestedString(overlay.Object, "spec",
|
||||||
|
"storageClassName"); err2 != nil {
|
||||||
|
// override when overlay storageClassName property is not existed
|
||||||
|
if err3 := unstructured.SetNestedField(overlay.Object, storageClassName, "spec",
|
||||||
|
"storageClassName"); err3 != nil {
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveNodePorts transfers the port values from the current cluster into the overlay
|
||||||
|
func saveNodePorts(current, overlay *unstructured.Unstructured) {
|
||||||
|
portMap := createPortMap(current)
|
||||||
|
ports, _, _ := unstructured.NestedFieldNoCopy(overlay.Object, "spec", "ports")
|
||||||
|
portList, ok := ports.([]any)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, port := range portList {
|
||||||
|
m, ok := port.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nodePortNum, ok := m["nodePort"]; ok && fmt.Sprintf("%v", nodePortNum) == "0" {
|
||||||
|
if portNum, ok := m["port"]; ok {
|
||||||
|
if v, ok := portMap[fmt.Sprintf("%v", portNum)]; ok {
|
||||||
|
m["nodePort"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveClusterIP copies the cluster IP from the current cluster into the overlay
|
||||||
|
func saveClusterIP(current, overlay *unstructured.Unstructured) error {
|
||||||
|
// Save the value of spec.clusterIP set by the cluster
|
||||||
|
if clusterIP, found, err := unstructured.NestedString(current.Object, "spec",
|
||||||
|
"clusterIP"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if found {
|
||||||
|
if err := unstructured.SetNestedField(overlay.Object, clusterIP, "spec",
|
||||||
|
"clusterIP"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRestDefaults(config *rest.Config) *rest.Config {
|
||||||
|
if config.GroupVersion == nil || config.GroupVersion.Empty() {
|
||||||
|
config.GroupVersion = &corev1.SchemeGroupVersion
|
||||||
|
}
|
||||||
|
if len(config.APIPath) == 0 {
|
||||||
|
if len(config.GroupVersion.Group) == 0 {
|
||||||
|
config.APIPath = "/api"
|
||||||
|
} else {
|
||||||
|
config.APIPath = "/apis"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(config.ContentType) == 0 {
|
||||||
|
config.ContentType = runtime.ContentTypeJSON
|
||||||
|
}
|
||||||
|
if config.NegotiatedSerializer == nil {
|
||||||
|
// This codec factory ensures the resources are not converted. Therefore, resources
|
||||||
|
// will not be round-tripped through internal versions. Defaulting does not happen
|
||||||
|
// on the client.
|
||||||
|
config.NegotiatedSerializer = serializer.NewCodecFactory(kubescheme.Scheme).WithoutConversion()
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
35
pkg/cmd/hgctl/manifests/manifest.go
Normal file
35
pkg/cmd/hgctl/manifests/manifest.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 manifests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FS embeds the manifests
|
||||||
|
//
|
||||||
|
//go:embed profiles/*
|
||||||
|
var FS embed.FS
|
||||||
|
|
||||||
|
// BuiltinOrDir returns a FS for the provided directory. If no directory is passed, the compiled in
|
||||||
|
// FS will be used
|
||||||
|
func BuiltinOrDir(dir string) fs.FS {
|
||||||
|
if dir == "" {
|
||||||
|
return FS
|
||||||
|
}
|
||||||
|
return os.DirFS(dir)
|
||||||
|
}
|
||||||
65
pkg/cmd/hgctl/manifests/profiles/_all.yaml
Normal file
65
pkg/cmd/hgctl/manifests/profiles/_all.yaml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
profile: kind
|
||||||
|
global:
|
||||||
|
install: local # install mode k8s/local/docker
|
||||||
|
ingressClass: higress
|
||||||
|
watchNamespace:
|
||||||
|
disableAlpnH2: true
|
||||||
|
enableStatus: true
|
||||||
|
enableIstioAPI: true
|
||||||
|
namespace: higress-system
|
||||||
|
istioNamespace: istio-system
|
||||||
|
|
||||||
|
console:
|
||||||
|
port: 8080
|
||||||
|
replicas: 1
|
||||||
|
serviceType: ClusterIP
|
||||||
|
domain: console.higress.io
|
||||||
|
tlsSecretName:
|
||||||
|
webLoginPrompt:
|
||||||
|
adminPasswordValue: admin
|
||||||
|
adminPasswordLength: 8
|
||||||
|
o11yEnabled: false
|
||||||
|
pvcRwxSupported: true
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
replicas: 1
|
||||||
|
httpPort: 80
|
||||||
|
httpsPort: 443
|
||||||
|
metricsPort: 15020
|
||||||
|
|
||||||
|
controller:
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
storage:
|
||||||
|
url: nacos://192.168.0.1:8848 # file://opt/higress/conf, buildin://127.0.0.1:8848
|
||||||
|
ns: higress-system
|
||||||
|
username:
|
||||||
|
password:
|
||||||
|
dataEncKey:
|
||||||
|
|
||||||
|
# values passed through to helm
|
||||||
|
values:
|
||||||
|
|
||||||
|
|
||||||
|
charts:
|
||||||
|
higress:
|
||||||
|
url: https://higress.io/helm-charts
|
||||||
|
name: higress
|
||||||
|
version: 1.1.2
|
||||||
|
istio:
|
||||||
|
url: https://istio-release.storage.googleapis.com/charts
|
||||||
|
name: base
|
||||||
|
version: 1.18.2
|
||||||
53
pkg/cmd/hgctl/manifests/profiles/k8s.yaml
Normal file
53
pkg/cmd/hgctl/manifests/profiles/k8s.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
profile: k8s
|
||||||
|
global:
|
||||||
|
install: k8s # install mode k8s/local-k8s/local-docker/local
|
||||||
|
ingressClass: higress
|
||||||
|
watchNamespace:
|
||||||
|
disableAlpnH2: true
|
||||||
|
enableStatus: true
|
||||||
|
enableIstioAPI: false
|
||||||
|
namespace: higress-system
|
||||||
|
istioNamespace: istio-system
|
||||||
|
|
||||||
|
console:
|
||||||
|
replicas: 1
|
||||||
|
serviceType: ClusterIP
|
||||||
|
domain: console.higress.io
|
||||||
|
tlsSecretName:
|
||||||
|
webLoginPrompt:
|
||||||
|
adminPasswordValue: admin
|
||||||
|
adminPasswordLength: 8
|
||||||
|
o11yEnabled: false
|
||||||
|
pvcRwxSupported: true
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
replicas: 2
|
||||||
|
|
||||||
|
controller:
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
# values passed through to helm
|
||||||
|
values:
|
||||||
|
|
||||||
|
charts:
|
||||||
|
higress:
|
||||||
|
url: https://higress.io/helm-charts
|
||||||
|
name: higress
|
||||||
|
version: 1.1.2
|
||||||
|
istio:
|
||||||
|
url: https://istio-release.storage.googleapis.com/charts
|
||||||
|
name: base
|
||||||
|
version: 1.18.2
|
||||||
53
pkg/cmd/hgctl/manifests/profiles/local-k8s.yaml
Normal file
53
pkg/cmd/hgctl/manifests/profiles/local-k8s.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
profile: local-k8s
|
||||||
|
global:
|
||||||
|
install: local-k8s # install mode k8s/local-k8s/local-docker/local
|
||||||
|
ingressClass: higress
|
||||||
|
watchNamespace:
|
||||||
|
disableAlpnH2: true
|
||||||
|
enableStatus: true
|
||||||
|
enableIstioAPI: true
|
||||||
|
namespace: higress-system
|
||||||
|
istioNamespace: istio-system
|
||||||
|
|
||||||
|
console:
|
||||||
|
replicas: 1
|
||||||
|
serviceType: ClusterIP
|
||||||
|
domain: console.higress.io
|
||||||
|
tlsSecretName:
|
||||||
|
webLoginPrompt:
|
||||||
|
adminPasswordValue: admin
|
||||||
|
adminPasswordLength: 8
|
||||||
|
o11yEnabled: true
|
||||||
|
pvcRwxSupported: true
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
controller:
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
# values passed through to helm
|
||||||
|
values:
|
||||||
|
|
||||||
|
charts:
|
||||||
|
higress:
|
||||||
|
url: https://higress.io/helm-charts
|
||||||
|
name: higress
|
||||||
|
version: 1.1.2
|
||||||
|
istio:
|
||||||
|
url: https://istio-release.storage.googleapis.com/charts
|
||||||
|
name: base
|
||||||
|
version: 1.18.2
|
||||||
44
pkg/cmd/hgctl/profile.go
Normal file
44
pkg/cmd/hgctl/profile.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 hgctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProfileCmd is a group of commands related to profile listing, dumping and diffing.
|
||||||
|
func newProfileCmd() *cobra.Command {
|
||||||
|
pc := &cobra.Command{
|
||||||
|
Use: "profile",
|
||||||
|
Short: "Commands related to higress configuration profiles",
|
||||||
|
Long: "The profile command lists, dumps higress configuration profiles.",
|
||||||
|
Example: "hgctl profile list\n" +
|
||||||
|
"hgctl install --set profile=local-k8s # Use a profile from the list",
|
||||||
|
}
|
||||||
|
|
||||||
|
pdArgs := &profileDumpArgs{}
|
||||||
|
plArgs := &profileListArgs{}
|
||||||
|
|
||||||
|
plc := profileListCmd(plArgs)
|
||||||
|
pdc := profileDumpCmd(pdArgs)
|
||||||
|
|
||||||
|
addProfileDumpFlags(pdc, pdArgs)
|
||||||
|
addProfileListFlags(plc, plArgs)
|
||||||
|
|
||||||
|
pc.AddCommand(plc)
|
||||||
|
pc.AddCommand(pdc)
|
||||||
|
|
||||||
|
return pc
|
||||||
|
}
|
||||||
71
pkg/cmd/hgctl/profileDump.go
Normal file
71
pkg/cmd/hgctl/profileDump.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 hgctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type profileDumpArgs struct {
|
||||||
|
// output write profile to file
|
||||||
|
output string
|
||||||
|
// manifestsPath is a path to a charts and profiles directory in the local filesystem with a release tgz.
|
||||||
|
manifestsPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func addProfileDumpFlags(cmd *cobra.Command, args *profileDumpArgs) {
|
||||||
|
cmd.PersistentFlags().StringVarP(&args.output, "output", "o", "", outputHelpstr)
|
||||||
|
cmd.PersistentFlags().StringVarP(&args.manifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileDumpCmd(pdArgs *profileDumpArgs) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "dump [<profile>]",
|
||||||
|
Short: "Dumps a higress configuration profile",
|
||||||
|
Long: "The dump subcommand dumps the values in a higress configuration profile.",
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 1 {
|
||||||
|
return fmt.Errorf("too many positional arguments")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return profileDump(cmd, args, pdArgs)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileDump(cmd *cobra.Command, args []string, pdArgs *profileDumpArgs) error {
|
||||||
|
profileName := helm.DefaultProfileName
|
||||||
|
if len(args) == 1 {
|
||||||
|
profileName = args[0]
|
||||||
|
}
|
||||||
|
yaml, err := helm.ReadProfileYAML(profileName, pdArgs.manifestsPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(pdArgs.output) > 0 {
|
||||||
|
err2 := os.WriteFile(pdArgs.output, []byte(yaml), 0644)
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmd.Println(yaml)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
63
pkg/cmd/hgctl/profileList.go
Normal file
63
pkg/cmd/hgctl/profileList.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 hgctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type profileListArgs struct {
|
||||||
|
// manifestsPath is a path to a charts and profiles directory in the local filesystem with a release tgz.
|
||||||
|
manifestsPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func addProfileListFlags(cmd *cobra.Command, args *profileListArgs) {
|
||||||
|
cmd.PersistentFlags().StringVarP(&args.manifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileListCmd(plArgs *profileListArgs) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "Lists available higress configuration profiles",
|
||||||
|
Long: "The list subcommand lists the available higress configuration profiles.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return profileList(cmd, plArgs)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// profileList list all the builtin profiles.
|
||||||
|
func profileList(cmd *cobra.Command, plArgs *profileListArgs) error {
|
||||||
|
|
||||||
|
profiles, err := helm.ListProfiles(plArgs.manifestsPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(profiles) == 0 {
|
||||||
|
cmd.Println("No profiles available.")
|
||||||
|
} else {
|
||||||
|
cmd.Println("higress configuration profiles:")
|
||||||
|
sort.Strings(profiles)
|
||||||
|
for _, profile := range profiles {
|
||||||
|
cmd.Printf(" %s\n", profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -28,6 +28,10 @@ func GetRootCommand() *cobra.Command {
|
|||||||
|
|
||||||
rootCmd.AddCommand(newVersionCommand())
|
rootCmd.AddCommand(newVersionCommand())
|
||||||
rootCmd.AddCommand(newConfigCommand())
|
rootCmd.AddCommand(newConfigCommand())
|
||||||
|
rootCmd.AddCommand(newInstallCmd())
|
||||||
|
rootCmd.AddCommand(newUninstallCmd())
|
||||||
|
rootCmd.AddCommand(newUpgradeCmd())
|
||||||
|
rootCmd.AddCommand(newProfileCmd())
|
||||||
|
|
||||||
return rootCmd
|
return rootCmd
|
||||||
}
|
}
|
||||||
|
|||||||
141
pkg/cmd/hgctl/uninstall.go
Normal file
141
pkg/cmd/hgctl/uninstall.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 hgctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/options"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type uninstallArgs struct {
|
||||||
|
// purgeIstioCRD delete all of Istio resources.
|
||||||
|
purgeIstioCRD bool
|
||||||
|
// istioNamespace is the target namespace of istio control plane.
|
||||||
|
istioNamespace string
|
||||||
|
// namespace is the namespace of higress installed .
|
||||||
|
namespace string
|
||||||
|
// verbose generates verbose output.
|
||||||
|
verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUninstallFlags(cmd *cobra.Command, args *uninstallArgs) {
|
||||||
|
cmd.PersistentFlags().StringVar(&args.istioNamespace, "istio-namespace", "istio-system",
|
||||||
|
"The namespace of Istio Control Plane.")
|
||||||
|
cmd.PersistentFlags().StringVarP(&args.namespace, "namespace", "n", "higress-system",
|
||||||
|
"The namespace of higress")
|
||||||
|
cmd.PersistentFlags().BoolVarP(&args.purgeIstioCRD, "purge-istio-crd", "p", false,
|
||||||
|
"Delete all of Istio resources")
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUninstallCmd command uninstalls Istio from a cluster
|
||||||
|
func newUninstallCmd() *cobra.Command {
|
||||||
|
uiArgs := &uninstallArgs{}
|
||||||
|
uninstallCmd := &cobra.Command{
|
||||||
|
Use: "uninstall",
|
||||||
|
Short: "Uninstall higress from a cluster",
|
||||||
|
Long: "The uninstall command uninstalls higress from a cluster",
|
||||||
|
Example: ` # Uninstall higress
|
||||||
|
hgctl uninstall
|
||||||
|
|
||||||
|
# Uninstall higress by special namespace
|
||||||
|
hgctl uninstall --namespace=higress-system
|
||||||
|
|
||||||
|
# Uninstall higress and istio CRD
|
||||||
|
hgctl uninstall --purge-istio-crd --istio-namespace=istio-system`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return uninstall(cmd.OutOrStdout(), uiArgs)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
addUninstallFlags(uninstallCmd, uiArgs)
|
||||||
|
flags := uninstallCmd.Flags()
|
||||||
|
options.AddKubeConfigFlags(flags)
|
||||||
|
return uninstallCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// uninstall uninstalls control plane by either pruning by target revision or deleting specified manifests.
|
||||||
|
func uninstall(writer io.Writer, uiArgs *uninstallArgs) error {
|
||||||
|
if !promptUninstall(writer) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(writer, "start to uninstall higress\n")
|
||||||
|
setFlags := make([]string, 0)
|
||||||
|
profileName := helm.GetUninstallProfileName()
|
||||||
|
fmt.Fprintf(writer, "start to uninstall higress profile:%s\n", profileName)
|
||||||
|
_, profile, err := helm.GenProfile(profileName, "", setFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
profile.Global.EnableIstioAPI = uiArgs.purgeIstioCRD
|
||||||
|
profile.Global.Namespace = uiArgs.namespace
|
||||||
|
profile.Global.IstioNamespace = uiArgs.istioNamespace
|
||||||
|
err = UnInstallManifests(profile, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptUninstall(writer io.Writer) bool {
|
||||||
|
answer := ""
|
||||||
|
fmt.Fprintf(writer, "Are you sure to uninstall higress?\n")
|
||||||
|
for {
|
||||||
|
fmt.Fprintf(writer, "Please input yes or no to select, input your selection:")
|
||||||
|
fmt.Scanln(&answer)
|
||||||
|
if strings.TrimSpace(answer) == "yes" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(answer) == "no" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnInstallManifests(profile *helm.Profile, writer io.Writer) error {
|
||||||
|
fmt.Fprintf(writer, "start to check kubernetes cluster enviroment ......\n")
|
||||||
|
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build kubernetes client: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to init higress installer ......\n")
|
||||||
|
op, err := installer.NewInstaller(profile, cliClient, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to run higress installer ......\n")
|
||||||
|
if err := op.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to render manifests ......\n")
|
||||||
|
manifestMap, err := op.RenderManifests()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "start to delete manifests ......\n")
|
||||||
|
if err := op.DeleteManifests(manifestMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, "uninstall higress complete!\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
49
pkg/cmd/hgctl/upgrade.go
Normal file
49
pkg/cmd/hgctl/upgrade.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 hgctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/options"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type upgradeArgs struct {
|
||||||
|
*InstallArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUpgradeFlags(cmd *cobra.Command, args *upgradeArgs) {
|
||||||
|
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
|
||||||
|
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUpgradeCmd upgrades Istio control plane in-place with eligibility checks.
|
||||||
|
func newUpgradeCmd() *cobra.Command {
|
||||||
|
upgradeArgs := &upgradeArgs{
|
||||||
|
InstallArgs: &InstallArgs{},
|
||||||
|
}
|
||||||
|
upgradeCmd := &cobra.Command{
|
||||||
|
Use: "upgrade",
|
||||||
|
Short: "Upgrade Higress in-place",
|
||||||
|
Long: "The upgrade command is an alias for the install command" +
|
||||||
|
" that performs additional upgrade-related checks.",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) (e error) {
|
||||||
|
return Install(cmd.OutOrStdout(), upgradeArgs.InstallArgs)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
addUpgradeFlags(upgradeCmd, upgradeArgs)
|
||||||
|
flags := upgradeCmd.Flags()
|
||||||
|
options.AddKubeConfigFlags(flags)
|
||||||
|
return upgradeCmd
|
||||||
|
}
|
||||||
91
pkg/cmd/hgctl/util/filter.go
Normal file
91
pkg/cmd/hgctl/util/filter.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/yamlfmt/formatters/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
formatterConfig = func() *basic.Config {
|
||||||
|
cfg := basic.DefaultConfig()
|
||||||
|
return cfg
|
||||||
|
}()
|
||||||
|
formatter = &basic.BasicFormatter{
|
||||||
|
Config: formatterConfig,
|
||||||
|
Features: basic.ConfigureFeaturesFromConfig(formatterConfig),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// FilterFunc is used to filter some contents of manifest.
|
||||||
|
type FilterFunc func(string) string
|
||||||
|
|
||||||
|
func ApplyFilters(input string, filters ...FilterFunc) string {
|
||||||
|
for _, filter := range filters {
|
||||||
|
input = filter(input)
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
// LicenseFilter assumes that license is at the beginning.
|
||||||
|
// So we just remove all the leading comments until the first non-comment line appears.
|
||||||
|
func LicenseFilter(input string) string {
|
||||||
|
var index int
|
||||||
|
buf := bufio.NewReader(strings.NewReader(input))
|
||||||
|
for {
|
||||||
|
line, err := buf.ReadString('\n')
|
||||||
|
if !strings.HasPrefix(line, "#") {
|
||||||
|
return input[index:]
|
||||||
|
}
|
||||||
|
index += len(line)
|
||||||
|
if err == io.EOF {
|
||||||
|
return input[index:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpaceFilter removes all leading and trailing space.
|
||||||
|
func SpaceFilter(input string) string {
|
||||||
|
return strings.TrimSpace(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpaceLineFilter removes all space lines.
|
||||||
|
func SpaceLineFilter(input string) string {
|
||||||
|
var builder strings.Builder
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.TrimSpace(line) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
builder.WriteString(line)
|
||||||
|
builder.WriteString("\n")
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatterFilter uses github.com/google/yamlfmt to format yaml file
|
||||||
|
func FormatterFilter(input string) string {
|
||||||
|
resBytes, err := formatter.Format([]byte(input))
|
||||||
|
// todo: think about log
|
||||||
|
if err != nil {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return string(resBytes)
|
||||||
|
}
|
||||||
98
pkg/cmd/hgctl/util/filter_test.go
Normal file
98
pkg/cmd/hgctl/util/filter_test.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLicenseFilter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: `# license line
|
||||||
|
content line`,
|
||||||
|
want: `content line`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `# license line`,
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `# license line
|
||||||
|
content line
|
||||||
|
# comment line`,
|
||||||
|
want: `content line
|
||||||
|
# comment line`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
res := LicenseFilter(test.input)
|
||||||
|
if res != test.want {
|
||||||
|
t.Errorf("want %s\n but got %s", test.want, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpaceFilter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
content line
|
||||||
|
`,
|
||||||
|
want: "content line",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
res := SpaceFilter(test.input)
|
||||||
|
if res != test.want {
|
||||||
|
t.Errorf("want %s\n but got %s", test.want, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatterFilter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: `key1: val1 `,
|
||||||
|
want: `key1: val1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `key1:
|
||||||
|
key2: val2`,
|
||||||
|
want: `key1:
|
||||||
|
key2: val2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
res := FormatterFilter(test.input)
|
||||||
|
if res != test.want {
|
||||||
|
t.Errorf("want \n%s\n but got \n%s\n", test.want, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
209
pkg/cmd/hgctl/util/path.go
Normal file
209
pkg/cmd/hgctl/util/path.go
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PathSeparator is the separator between path elements.
|
||||||
|
PathSeparator = "."
|
||||||
|
// KVSeparator is the separator between the key and value in a key/value path element,
|
||||||
|
KVSeparator = string(kvSeparatorRune)
|
||||||
|
kvSeparatorRune = ':'
|
||||||
|
|
||||||
|
// InsertIndex is the index that means "insert" when setting values
|
||||||
|
InsertIndex = -1
|
||||||
|
|
||||||
|
// PathSeparatorRune is the separator between path elements, as a rune.
|
||||||
|
pathSeparatorRune = '.'
|
||||||
|
// EscapedPathSeparator is what to use when the path shouldn't separate
|
||||||
|
EscapedPathSeparator = "\\" + PathSeparator
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidKeyRegex is a regex for a valid path key element.
|
||||||
|
var ValidKeyRegex = regexp.MustCompile("^[a-zA-Z0-9_-]*$")
|
||||||
|
|
||||||
|
// Path is a path in slice form.
|
||||||
|
type Path []string
|
||||||
|
|
||||||
|
// PathFromString converts a string path of form a.b.c to a string slice representation.
|
||||||
|
func PathFromString(path string) Path {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
path = strings.TrimPrefix(path, PathSeparator)
|
||||||
|
path = strings.TrimSuffix(path, PathSeparator)
|
||||||
|
pv := splitEscaped(path, pathSeparatorRune)
|
||||||
|
var r []string
|
||||||
|
for _, str := range pv {
|
||||||
|
if str != "" {
|
||||||
|
str = strings.ReplaceAll(str, EscapedPathSeparator, PathSeparator)
|
||||||
|
// Is str of the form node[expr], convert to "node", "[expr]"?
|
||||||
|
nBracket := strings.IndexRune(str, '[')
|
||||||
|
if nBracket > 0 {
|
||||||
|
r = append(r, str[:nBracket], str[nBracket:])
|
||||||
|
} else {
|
||||||
|
// str is "[expr]" or "node"
|
||||||
|
r = append(r, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// String converts a string slice path representation of form ["a", "b", "c"] to a string representation like "a.b.c".
|
||||||
|
func (p Path) String() string {
|
||||||
|
return strings.Join(p, PathSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Path) Equals(p2 Path) bool {
|
||||||
|
if len(p) != len(p2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, pp := range p {
|
||||||
|
if pp != p2[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToYAMLPath converts a path string to path such that the first letter of each path element is lower case.
|
||||||
|
func ToYAMLPath(path string) Path {
|
||||||
|
p := PathFromString(path)
|
||||||
|
for i := range p {
|
||||||
|
p[i] = firstCharToLowerCase(p[i])
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToYAMLPathString converts a path string such that the first letter of each path element is lower case.
|
||||||
|
func ToYAMLPathString(path string) string {
|
||||||
|
return ToYAMLPath(path).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidPathElement reports whether pe is a valid path element.
|
||||||
|
func IsValidPathElement(pe string) bool {
|
||||||
|
return ValidKeyRegex.MatchString(pe)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsKVPathElement report whether pe is a key/value path element.
|
||||||
|
func IsKVPathElement(pe string) bool {
|
||||||
|
pe, ok := RemoveBrackets(pe)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
kv := splitEscaped(pe, kvSeparatorRune)
|
||||||
|
if len(kv) != 2 || len(kv[0]) == 0 || len(kv[1]) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return IsValidPathElement(kv[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVPathElement report whether pe is a value path element.
|
||||||
|
func IsVPathElement(pe string) bool {
|
||||||
|
pe, ok := RemoveBrackets(pe)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(pe) > 1 && pe[0] == ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNPathElement report whether pe is an index path element.
|
||||||
|
func IsNPathElement(pe string) bool {
|
||||||
|
pe, ok := RemoveBrackets(pe)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.Atoi(pe)
|
||||||
|
return err == nil && n >= InsertIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathKV returns the key and value string parts of the entire key/value path element.
|
||||||
|
// It returns an error if pe is not a key/value path element.
|
||||||
|
func PathKV(pe string) (k, v string, err error) {
|
||||||
|
if !IsKVPathElement(pe) {
|
||||||
|
return "", "", fmt.Errorf("%s is not a valid key:value path element", pe)
|
||||||
|
}
|
||||||
|
pe, _ = RemoveBrackets(pe)
|
||||||
|
kv := splitEscaped(pe, kvSeparatorRune)
|
||||||
|
return kv[0], kv[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathV returns the value string part of the entire value path element.
|
||||||
|
// It returns an error if pe is not a value path element.
|
||||||
|
func PathV(pe string) (string, error) {
|
||||||
|
// For :val, return the value only
|
||||||
|
if IsVPathElement(pe) {
|
||||||
|
v, _ := RemoveBrackets(pe)
|
||||||
|
return v[1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For key:val, return the whole thing
|
||||||
|
v, _ := RemoveBrackets(pe)
|
||||||
|
if len(v) > 0 {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("%s is not a valid value path element", pe)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathN returns the index part of the entire value path element.
|
||||||
|
// It returns an error if pe is not an index path element.
|
||||||
|
func PathN(pe string) (int, error) {
|
||||||
|
if !IsNPathElement(pe) {
|
||||||
|
return -1, fmt.Errorf("%s is not a valid index path element", pe)
|
||||||
|
}
|
||||||
|
v, _ := RemoveBrackets(pe)
|
||||||
|
return strconv.Atoi(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveBrackets removes the [] around pe and returns the resulting string. It returns false if pe is not surrounded
|
||||||
|
// by [].
|
||||||
|
func RemoveBrackets(pe string) (string, bool) {
|
||||||
|
if !strings.HasPrefix(pe, "[") || !strings.HasSuffix(pe, "]") {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return pe[1 : len(pe)-1], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitEscaped splits a string using the rune r as a separator. It does not split on r if it's prefixed by \.
|
||||||
|
func splitEscaped(s string, r rune) []string {
|
||||||
|
var prev rune
|
||||||
|
if len(s) == 0 {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
prevIdx := 0
|
||||||
|
var out []string
|
||||||
|
for i, c := range s {
|
||||||
|
if c == r && (i == 0 || (i > 0 && prev != '\\')) {
|
||||||
|
out = append(out, s[prevIdx:i])
|
||||||
|
prevIdx = i + 1
|
||||||
|
}
|
||||||
|
prev = c
|
||||||
|
}
|
||||||
|
out = append(out, s[prevIdx:])
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstCharToLowerCase(s string) string {
|
||||||
|
return strings.ToLower(s[0:1]) + s[1:]
|
||||||
|
}
|
||||||
383
pkg/cmd/hgctl/util/path_test.go
Normal file
383
pkg/cmd/hgctl/util/path_test.go
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitEscaped(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
in: "",
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no match",
|
||||||
|
in: "foo",
|
||||||
|
want: []string{"foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "first",
|
||||||
|
in: ":foo",
|
||||||
|
want: []string{"", "foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "last",
|
||||||
|
in: "foo:",
|
||||||
|
want: []string{"foo", ""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "multiple",
|
||||||
|
in: "foo:bar:baz",
|
||||||
|
want: []string{"foo", "bar", "baz"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "multiple with escapes",
|
||||||
|
in: `foo\:bar:baz\:qux`,
|
||||||
|
want: []string{`foo\:bar`, `baz\:qux`},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got, want := splitEscaped(tt.in, kvSeparatorRune), tt.want; !stringSlicesEqual(got, want) {
|
||||||
|
t.Errorf("%s: got:%v, want:%v", tt.desc, got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsNPathElement(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
in: "",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "negative",
|
||||||
|
in: "[-45]",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "negative-1",
|
||||||
|
in: "[-1]",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid",
|
||||||
|
in: "[0]",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := IsNPathElement(tt.in); got != tt.expect {
|
||||||
|
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringSlicesEqual(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, aa := range a {
|
||||||
|
if aa != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathFromString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
expect Path
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no-path",
|
||||||
|
in: "",
|
||||||
|
expect: Path{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid-path",
|
||||||
|
in: "a.b.c",
|
||||||
|
expect: Path{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "surround-periods",
|
||||||
|
in: ".a.",
|
||||||
|
expect: Path{"a"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := PathFromString(tt.in); !got.Equals(tt.expect) {
|
||||||
|
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToYAMLPath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
expect Path
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "all-uppercase",
|
||||||
|
in: "A.B.C.D",
|
||||||
|
expect: Path{"a", "b", "c", "d"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := ToYAMLPath(tt.in); !got.Equals(tt.expect) {
|
||||||
|
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsKVPathElement(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid",
|
||||||
|
in: "[1:2]",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid",
|
||||||
|
in: "[:2]",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid-2",
|
||||||
|
in: "[1:]",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
in: "",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no-brackets",
|
||||||
|
in: "1:2",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one-bracket",
|
||||||
|
in: "[1:2",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := IsKVPathElement(tt.in); got != tt.expect {
|
||||||
|
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsVPathElement(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid",
|
||||||
|
in: "[:1]",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "kv-path-elem",
|
||||||
|
in: "[1:2]",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid",
|
||||||
|
in: "1:2",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
in: "",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := IsVPathElement(tt.in); got != tt.expect {
|
||||||
|
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathKV(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
wantK string
|
||||||
|
wantV string
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid",
|
||||||
|
in: "[1:2]",
|
||||||
|
wantK: "1",
|
||||||
|
wantV: "2",
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid",
|
||||||
|
in: "[1:",
|
||||||
|
wantErr: errors.New(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
in: "",
|
||||||
|
wantErr: errors.New(""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if k, v, err := PathKV(tt.in); k != tt.wantK || v != tt.wantV || errNilCheck(err, tt.wantErr) {
|
||||||
|
t.Errorf("%s: expect %v %v %v got %v %v %v", tt.desc, tt.wantK, tt.wantV, tt.wantErr, k, v, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathV(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
want string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid-kv",
|
||||||
|
in: "[1:2]",
|
||||||
|
want: "1:2",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid-v",
|
||||||
|
in: "[:1]",
|
||||||
|
want: "1",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid",
|
||||||
|
in: "083fj",
|
||||||
|
want: "",
|
||||||
|
err: errors.New(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
in: "",
|
||||||
|
want: "",
|
||||||
|
err: errors.New(""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got, err := PathV(tt.in); got != tt.want || errNilCheck(err, tt.err) {
|
||||||
|
t.Errorf("%s: expect %v %v got %v %v", tt.desc, tt.want, tt.err, got, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveBrackets(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
expect string
|
||||||
|
expectStat bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "has-brackets",
|
||||||
|
in: "[yo]",
|
||||||
|
expect: "yo",
|
||||||
|
expectStat: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one-bracket",
|
||||||
|
in: "[yo",
|
||||||
|
expect: "",
|
||||||
|
expectStat: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "other-bracket",
|
||||||
|
in: "yo]",
|
||||||
|
expect: "",
|
||||||
|
expectStat: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no-brackets",
|
||||||
|
in: "yo",
|
||||||
|
expect: "",
|
||||||
|
expectStat: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
in: "",
|
||||||
|
expect: "",
|
||||||
|
expectStat: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got, stat := RemoveBrackets(tt.in); got != tt.expect || stat != tt.expectStat {
|
||||||
|
t.Errorf("%s: expect %v %v got %v %v", tt.desc, tt.expect, tt.expectStat, got, stat)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func errNilCheck(err1, err2 error) bool {
|
||||||
|
return (err1 == nil && err2 != nil) || (err1 != nil && err2 == nil)
|
||||||
|
}
|
||||||
311
pkg/cmd/hgctl/util/reflect.go
Normal file
311
pkg/cmd/hgctl/util/reflect.go
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// kindOf returns the reflection Kind that represents the dynamic type of value.
|
||||||
|
// If value is a nil interface value, kindOf returns reflect.Invalid.
|
||||||
|
func kindOf(value any) reflect.Kind {
|
||||||
|
if value == nil {
|
||||||
|
return reflect.Invalid
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(value).Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsString reports whether value is a string type.
|
||||||
|
func IsString(value any) bool {
|
||||||
|
return kindOf(value) == reflect.String
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPtr reports whether value is a ptr type.
|
||||||
|
func IsPtr(value any) bool {
|
||||||
|
return kindOf(value) == reflect.Ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMap reports whether value is a map type.
|
||||||
|
func IsMap(value any) bool {
|
||||||
|
return kindOf(value) == reflect.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMapPtr reports whether v is a map ptr type.
|
||||||
|
func IsMapPtr(v any) bool {
|
||||||
|
t := reflect.TypeOf(v)
|
||||||
|
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSlice reports whether value is a slice type.
|
||||||
|
func IsSlice(value any) bool {
|
||||||
|
return kindOf(value) == reflect.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStruct reports whether value is a struct type
|
||||||
|
func IsStruct(value any) bool {
|
||||||
|
return kindOf(value) == reflect.Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSlicePtr reports whether v is a slice ptr type.
|
||||||
|
func IsSlicePtr(v any) bool {
|
||||||
|
return kindOf(v) == reflect.Ptr && reflect.TypeOf(v).Elem().Kind() == reflect.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSliceInterfacePtr reports whether v is a slice ptr type.
|
||||||
|
func IsSliceInterfacePtr(v any) bool {
|
||||||
|
// Must use ValueOf because Elem().Elem() type resolves dynamically.
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
return vv.Kind() == reflect.Ptr && vv.Elem().Kind() == reflect.Interface && vv.Elem().Elem().Kind() == reflect.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTypeStructPtr reports whether v is a struct ptr type.
|
||||||
|
func IsTypeStructPtr(t reflect.Type) bool {
|
||||||
|
if t == reflect.TypeOf(nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTypeSlicePtr reports whether v is a slice ptr type.
|
||||||
|
func IsTypeSlicePtr(t reflect.Type) bool {
|
||||||
|
if t == reflect.TypeOf(nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTypeMap reports whether v is a map type.
|
||||||
|
func IsTypeMap(t reflect.Type) bool {
|
||||||
|
if t == reflect.TypeOf(nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.Kind() == reflect.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTypeInterface reports whether v is an interface.
|
||||||
|
func IsTypeInterface(t reflect.Type) bool {
|
||||||
|
if t == reflect.TypeOf(nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.Kind() == reflect.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTypeSliceOfInterface reports whether v is a slice of interface.
|
||||||
|
func IsTypeSliceOfInterface(t reflect.Type) bool {
|
||||||
|
if t == reflect.TypeOf(nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNilOrInvalidValue reports whether v is nil or reflect.Zero.
|
||||||
|
func IsNilOrInvalidValue(v reflect.Value) bool {
|
||||||
|
return !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) || IsValueNil(v.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueNil returns true if either value is nil, or has dynamic type {ptr,
|
||||||
|
// map, slice} with value nil.
|
||||||
|
func IsValueNil(value any) bool {
|
||||||
|
if value == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch kindOf(value) {
|
||||||
|
case reflect.Slice, reflect.Ptr, reflect.Map:
|
||||||
|
return reflect.ValueOf(value).IsNil()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueNilOrDefault returns true if either IsValueNil(value) or the default
|
||||||
|
// value for the type.
|
||||||
|
func IsValueNilOrDefault(value any) bool {
|
||||||
|
if IsValueNil(value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !IsValueScalar(reflect.ValueOf(value)) {
|
||||||
|
// Default value is nil for non-scalar types.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return value == reflect.New(reflect.TypeOf(value)).Elem().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValuePtr reports whether v is a ptr type.
|
||||||
|
func IsValuePtr(v reflect.Value) bool {
|
||||||
|
return v.Kind() == reflect.Ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueInterface reports whether v is an interface type.
|
||||||
|
func IsValueInterface(v reflect.Value) bool {
|
||||||
|
return v.Kind() == reflect.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueStruct reports whether v is a struct type.
|
||||||
|
func IsValueStruct(v reflect.Value) bool {
|
||||||
|
return v.Kind() == reflect.Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueStructPtr reports whether v is a struct ptr type.
|
||||||
|
func IsValueStructPtr(v reflect.Value) bool {
|
||||||
|
return v.Kind() == reflect.Ptr && IsValueStruct(v.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueMap reports whether v is a map type.
|
||||||
|
func IsValueMap(v reflect.Value) bool {
|
||||||
|
return v.Kind() == reflect.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueSlice reports whether v is a slice type.
|
||||||
|
func IsValueSlice(v reflect.Value) bool {
|
||||||
|
return v.Kind() == reflect.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueScalar reports whether v is a scalar type.
|
||||||
|
func IsValueScalar(v reflect.Value) bool {
|
||||||
|
if IsNilOrInvalidValue(v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if IsValuePtr(v) {
|
||||||
|
if v.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return !IsValueStruct(v) && !IsValueMap(v) && !IsValueSlice(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValuesAreSameType returns true if v1 and v2 has the same reflect.Type,
|
||||||
|
// otherwise it returns false.
|
||||||
|
func ValuesAreSameType(v1 reflect.Value, v2 reflect.Value) bool {
|
||||||
|
return v1.Type() == v2.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmptyString returns true if value is an empty string.
|
||||||
|
func IsEmptyString(value any) bool {
|
||||||
|
if value == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch kindOf(value) {
|
||||||
|
case reflect.String:
|
||||||
|
if _, ok := value.(string); ok {
|
||||||
|
return value.(string) == ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFromSlicePtr deletes an entry at index from the parent, which must be a slice ptr.
|
||||||
|
func DeleteFromSlicePtr(parentSlice any, index int) error {
|
||||||
|
pv := reflect.ValueOf(parentSlice)
|
||||||
|
|
||||||
|
if !IsSliceInterfacePtr(parentSlice) {
|
||||||
|
return fmt.Errorf("deleteFromSlicePtr parent type is %T, must be *[]interface{}", parentSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
pvv := pv.Elem()
|
||||||
|
if pvv.Kind() == reflect.Interface {
|
||||||
|
pvv = pvv.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
pv.Elem().Set(reflect.AppendSlice(pvv.Slice(0, index), pvv.Slice(index+1, pvv.Len())))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSlicePtr updates an entry at index in the parent, which must be a slice ptr, with the given value.
|
||||||
|
func UpdateSlicePtr(parentSlice any, index int, value any) error {
|
||||||
|
pv := reflect.ValueOf(parentSlice)
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
|
||||||
|
if !IsSliceInterfacePtr(parentSlice) {
|
||||||
|
return fmt.Errorf("updateSlicePtr parent type is %T, must be *[]interface{}", parentSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
pvv := pv.Elem()
|
||||||
|
if pvv.Kind() == reflect.Interface {
|
||||||
|
pv.Elem().Elem().Index(index).Set(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pv.Elem().Index(index).Set(v)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertIntoMap inserts value with key into parent which must be a map, map ptr, or interface to map.
|
||||||
|
func InsertIntoMap(parentMap any, key any, value any) error {
|
||||||
|
v := reflect.ValueOf(parentMap)
|
||||||
|
kv := reflect.ValueOf(key)
|
||||||
|
vv := reflect.ValueOf(value)
|
||||||
|
|
||||||
|
if v.Type().Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if v.Type().Kind() == reflect.Interface {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Type().Kind() != reflect.Map {
|
||||||
|
return fmt.Errorf("insertIntoMap parent type is %T, must be map", parentMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.SetMapIndex(kv, vv)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFromMap deletes an entry with the given key parent, which must be a map.
|
||||||
|
func DeleteFromMap(parentMap any, key any) error {
|
||||||
|
pv := reflect.ValueOf(parentMap)
|
||||||
|
|
||||||
|
if !IsMap(parentMap) {
|
||||||
|
return fmt.Errorf("deleteFromMap parent type is %T, must be map", parentMap)
|
||||||
|
}
|
||||||
|
pv.SetMapIndex(reflect.ValueOf(key), reflect.Value{})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToIntValue returns 0, false if val is not a number type, otherwise it returns the int value of val.
|
||||||
|
func ToIntValue(val any) (int, bool) {
|
||||||
|
if IsValueNil(val) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(val)
|
||||||
|
switch {
|
||||||
|
case IsIntKind(v.Kind()):
|
||||||
|
return int(v.Int()), true
|
||||||
|
case IsUintKind(v.Kind()):
|
||||||
|
return int(v.Uint()), true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIntKind reports whether k is an integer kind of any size.
|
||||||
|
func IsIntKind(k reflect.Kind) bool {
|
||||||
|
switch k {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUintKind reports whether k is an unsigned integer kind of any size.
|
||||||
|
func IsUintKind(k reflect.Kind) bool {
|
||||||
|
switch k {
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
78
pkg/cmd/hgctl/util/util.go
Normal file
78
pkg/cmd/hgctl/util/util.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StripPrefix removes the given prefix from prefix.
|
||||||
|
func StripPrefix(path, prefix string) string {
|
||||||
|
pl := len(strings.Split(prefix, "/"))
|
||||||
|
pv := strings.Split(path, "/")
|
||||||
|
return strings.Join(pv[pl:], "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitSetFlag(flag string) (string, string) {
|
||||||
|
items := strings.Split(flag, "=")
|
||||||
|
if len(items) != 2 {
|
||||||
|
return flag, ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(items[0]), strings.TrimSpace(items[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFilePath reports whether the given URL is a local file path.
|
||||||
|
func IsFilePath(path string) bool {
|
||||||
|
return strings.Contains(path, "/") || strings.Contains(path, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHTTPURL checks whether the given URL is a HTTP URL.
|
||||||
|
func IsHTTPURL(path string) (bool, error) {
|
||||||
|
u, err := url.Parse(path)
|
||||||
|
valid := err == nil && u.Host != "" && (u.Scheme == "http" || u.Scheme == "https")
|
||||||
|
if strings.HasPrefix(path, "http") && !valid {
|
||||||
|
return false, fmt.Errorf("%s starts with http but is not a valid URL: %s", path, err)
|
||||||
|
}
|
||||||
|
return valid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringBoolMapToSlice creates and returns a slice of all the map keys with true.
|
||||||
|
func StringBoolMapToSlice(m map[string]bool) []string {
|
||||||
|
s := make([]string, 0, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
if v {
|
||||||
|
s = append(s, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseValue parses string into a value
|
||||||
|
func ParseValue(valueStr string) any {
|
||||||
|
var value any
|
||||||
|
if v, err := strconv.Atoi(valueStr); err == nil {
|
||||||
|
value = v
|
||||||
|
} else if v, err := strconv.ParseFloat(valueStr, 64); err == nil {
|
||||||
|
value = v
|
||||||
|
} else if v, err := strconv.ParseBool(valueStr); err == nil {
|
||||||
|
value = v
|
||||||
|
} else {
|
||||||
|
value = strings.ReplaceAll(valueStr, "\\,", ",")
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
318
pkg/cmd/hgctl/util/yaml.go
Normal file
318
pkg/cmd/hgctl/util/yaml.go
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
jsonpatch "github.com/evanphx/json-patch/v5" // nolint: staticcheck
|
||||||
|
"github.com/kylelemons/godebug/diff"
|
||||||
|
yaml3 "k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
//func ToYAMLGeneric(root any) ([]byte, error) {
|
||||||
|
// var vs []byte
|
||||||
|
// if proto, ok := root.(proto.Message); ok {
|
||||||
|
// v, err := protomarshal.ToYAML(proto)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// vs = []byte(v)
|
||||||
|
// } else {
|
||||||
|
// v, err := yaml.Marshal(root)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// vs = v
|
||||||
|
// }
|
||||||
|
// return vs, nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func MustToYAMLGeneric(root any) string {
|
||||||
|
// var vs []byte
|
||||||
|
// if proto, ok := root.(proto.Message); ok {
|
||||||
|
// v, err := protomarshal.ToYAML(proto)
|
||||||
|
// if err != nil {
|
||||||
|
// return err.Error()
|
||||||
|
// }
|
||||||
|
// vs = []byte(v)
|
||||||
|
// } else {
|
||||||
|
// v, err := yaml.Marshal(root)
|
||||||
|
// if err != nil {
|
||||||
|
// return err.Error()
|
||||||
|
// }
|
||||||
|
// vs = v
|
||||||
|
// }
|
||||||
|
// return string(vs)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// ToYAML returns a YAML string representation of val, or the error string if an error occurs.
|
||||||
|
func ToYAML(val any) string {
|
||||||
|
y, err := yaml.Marshal(val)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return string(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//// ToYAMLWithJSONPB returns a YAML string representation of val (using jsonpb), or the error string if an error occurs.
|
||||||
|
//func ToYAMLWithJSONPB(val proto.Message) string {
|
||||||
|
// v := reflect.ValueOf(val)
|
||||||
|
// if val == nil || (v.Kind() == reflect.Ptr && v.IsNil()) {
|
||||||
|
// return "null"
|
||||||
|
// }
|
||||||
|
// js, err := protomarshal.ToJSONWithOptions(val, "", true)
|
||||||
|
// if err != nil {
|
||||||
|
// return err.Error()
|
||||||
|
// }
|
||||||
|
// yb, err := yaml.JSONToYAML([]byte(js))
|
||||||
|
// if err != nil {
|
||||||
|
// return err.Error()
|
||||||
|
// }
|
||||||
|
// return string(yb)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// MarshalWithJSONPB returns a YAML string representation of val (using jsonpb).
|
||||||
|
//func MarshalWithJSONPB(val proto.Message) (string, error) {
|
||||||
|
// return protomarshal.ToYAML(val)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// UnmarshalWithJSONPB unmarshals y into out using gogo jsonpb (required for many proto defined structs).
|
||||||
|
//func UnmarshalWithJSONPB(y string, out proto.Message, allowUnknownField bool) error {
|
||||||
|
// // Treat nothing as nothing. If we called jsonpb.Unmarshaler it would return the same.
|
||||||
|
// if y == "" {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// jb, err := yaml.YAMLToJSON([]byte(y))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if allowUnknownField {
|
||||||
|
// err = protomarshal.UnmarshalAllowUnknown(jb, out)
|
||||||
|
// } else {
|
||||||
|
// err = protomarshal.Unmarshal(jb, out)
|
||||||
|
// }
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
// OverlayTrees performs a sequential JSON strategic of overlays over base.
|
||||||
|
func OverlayTrees(base map[string]any, overlays ...map[string]any) (map[string]any, error) {
|
||||||
|
needsOverlay := false
|
||||||
|
for _, o := range overlays {
|
||||||
|
if len(o) > 0 {
|
||||||
|
needsOverlay = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !needsOverlay {
|
||||||
|
// Avoid expensive overlay if possible
|
||||||
|
return base, nil
|
||||||
|
}
|
||||||
|
bby, err := yaml.Marshal(base)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
by := string(bby)
|
||||||
|
|
||||||
|
for _, o := range overlays {
|
||||||
|
oy, err := yaml.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
by, err = OverlayYAML(by, string(oy))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string]any)
|
||||||
|
err = yaml.Unmarshal([]byte(by), &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverlayYAML patches the overlay tree over the base tree and returns the result. All trees are expressed as YAML
|
||||||
|
// strings.
|
||||||
|
func OverlayYAML(base, overlay string) (string, error) {
|
||||||
|
if strings.TrimSpace(base) == "" {
|
||||||
|
return overlay, nil
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(overlay) == "" {
|
||||||
|
return base, nil
|
||||||
|
}
|
||||||
|
bj, err := yaml.YAMLToJSON([]byte(base))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("yamlToJSON error in base: %s\n%s", err, bj)
|
||||||
|
}
|
||||||
|
oj, err := yaml.YAMLToJSON([]byte(overlay))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("yamlToJSON error in overlay: %s\n%s", err, oj)
|
||||||
|
}
|
||||||
|
if base == "" {
|
||||||
|
bj = []byte("{}")
|
||||||
|
}
|
||||||
|
if overlay == "" {
|
||||||
|
oj = []byte("{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
merged, err := jsonpatch.MergePatch(bj, oj)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("json merge error (%s) for base object: \n%s\n override object: \n%s", err, bj, oj)
|
||||||
|
}
|
||||||
|
my, err := yaml.JSONToYAML(merged)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("jsonToYAML error (%s) for merged object: \n%s", err, merged)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(my), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// yamlDiff compares single YAML file
|
||||||
|
func yamlDiff(a, b string) string {
|
||||||
|
ao, bo := make(map[string]any), make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(a), &ao); err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
if err := yaml.Unmarshal([]byte(b), &bo); err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
ay, err := yaml.Marshal(ao)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
by, err := yaml.Marshal(bo)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff.Diff(string(ay), string(by))
|
||||||
|
}
|
||||||
|
|
||||||
|
// yamlStringsToList yaml string parse to string list
|
||||||
|
func yamlStringsToList(str string) []string {
|
||||||
|
reader := bufio.NewReader(strings.NewReader(str))
|
||||||
|
decoder := yaml3.NewYAMLReader(reader)
|
||||||
|
res := make([]string, 0)
|
||||||
|
for {
|
||||||
|
doc, err := decoder.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk := bytes.TrimSpace(doc)
|
||||||
|
res = append(res, string(chunk))
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiYamlDiffOutput multi yaml diff output format
|
||||||
|
func multiYamlDiffOutput(res, diff string) string {
|
||||||
|
if res == "" {
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
if diff == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return res + "\n" + diff
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffStringList(l1, l2 []string) string {
|
||||||
|
var maxLen int
|
||||||
|
var minLen int
|
||||||
|
var l1Max bool
|
||||||
|
res := ""
|
||||||
|
if len(l1)-len(l2) > 0 {
|
||||||
|
maxLen = len(l1)
|
||||||
|
minLen = len(l2)
|
||||||
|
l1Max = true
|
||||||
|
} else {
|
||||||
|
maxLen = len(l2)
|
||||||
|
minLen = len(l1)
|
||||||
|
l1Max = false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < maxLen; i++ {
|
||||||
|
d := ""
|
||||||
|
if i >= minLen {
|
||||||
|
if l1Max {
|
||||||
|
d = yamlDiff(l1[i], "")
|
||||||
|
} else {
|
||||||
|
d = yamlDiff("", l2[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d = yamlDiff(l1[i], l2[i])
|
||||||
|
}
|
||||||
|
res = multiYamlDiffOutput(res, d)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLDiff compares multiple YAML files and single YAML file
|
||||||
|
func YAMLDiff(a, b string) string {
|
||||||
|
al := yamlStringsToList(a)
|
||||||
|
bl := yamlStringsToList(b)
|
||||||
|
res := diffStringList(al, bl)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsYAMLEqual reports whether the YAML in strings a and b are equal.
|
||||||
|
func IsYAMLEqual(a, b string) bool {
|
||||||
|
if strings.TrimSpace(a) == "" && strings.TrimSpace(b) == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
ajb, err := yaml.YAMLToJSON([]byte(a))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bjb, err := yaml.YAMLToJSON([]byte(b))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.Equal(ajb, bjb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsYAMLEmpty reports whether the YAML string y is logically empty.
|
||||||
|
func IsYAMLEmpty(y string) bool {
|
||||||
|
var yc []string
|
||||||
|
for _, l := range strings.Split(y, "\n") {
|
||||||
|
yt := strings.TrimSpace(l)
|
||||||
|
if !strings.HasPrefix(yt, "#") && !strings.HasPrefix(yt, "---") {
|
||||||
|
yc = append(yc, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res := strings.TrimSpace(strings.Join(yc, "\n"))
|
||||||
|
return res == "{}" || res == ""
|
||||||
|
}
|
||||||
363
pkg/cmd/hgctl/util/yaml_test.go
Normal file
363
pkg/cmd/hgctl/util/yaml_test.go
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToYAML(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
inVals any
|
||||||
|
expectedOut string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid-yaml",
|
||||||
|
inVals: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
"yo": map[string]any{
|
||||||
|
"istio": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOut: `foo: bar
|
||||||
|
yo:
|
||||||
|
istio: bar
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "alphabetical",
|
||||||
|
inVals: map[string]any{
|
||||||
|
"foo": "yaml",
|
||||||
|
"abc": "f",
|
||||||
|
},
|
||||||
|
expectedOut: `abc: f
|
||||||
|
foo: yaml
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "expected-err-nil",
|
||||||
|
inVals: nil,
|
||||||
|
expectedOut: "null\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := ToYAML(tt.inVals); got != tt.expectedOut {
|
||||||
|
t.Errorf("%s: expected out %v got %s", tt.desc, tt.expectedOut, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverlayTrees(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
inBase map[string]any
|
||||||
|
inOverlays map[string]any
|
||||||
|
expectedOverlay map[string]any
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "overlay-valid",
|
||||||
|
inBase: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "naz",
|
||||||
|
},
|
||||||
|
inOverlays: map[string]any{
|
||||||
|
"foo": "laz",
|
||||||
|
},
|
||||||
|
expectedOverlay: map[string]any{
|
||||||
|
"baz": "naz",
|
||||||
|
"foo": "laz",
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "overlay-key-does-not-exist",
|
||||||
|
inBase: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "naz",
|
||||||
|
},
|
||||||
|
inOverlays: map[string]any{
|
||||||
|
"i-dont-exist": "i-really-dont-exist",
|
||||||
|
},
|
||||||
|
expectedOverlay: map[string]any{
|
||||||
|
"baz": "naz",
|
||||||
|
"foo": "bar",
|
||||||
|
"i-dont-exist": "i-really-dont-exist",
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "remove-key-val",
|
||||||
|
inBase: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
inOverlays: map[string]any{
|
||||||
|
"foo": nil,
|
||||||
|
},
|
||||||
|
expectedOverlay: map[string]any{},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if gotOverlays, err := OverlayTrees(tt.inBase, tt.inOverlays); !reflect.DeepEqual(gotOverlays, tt.expectedOverlay) ||
|
||||||
|
((err != nil && tt.expectedErr == nil) || (err == nil && tt.expectedErr != nil)) {
|
||||||
|
t.Errorf("%s: expected overlay & err %v %v got %v %v", tt.desc, tt.expectedOverlay, tt.expectedErr,
|
||||||
|
gotOverlays, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverlayYAML(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
base string
|
||||||
|
overlay string
|
||||||
|
expect string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "overlay-yaml",
|
||||||
|
base: `foo: bar
|
||||||
|
yo: lo
|
||||||
|
`,
|
||||||
|
overlay: `yo: go`,
|
||||||
|
expect: `foo: bar
|
||||||
|
yo: go
|
||||||
|
`,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "combine-yaml",
|
||||||
|
base: `foo: bar`,
|
||||||
|
overlay: `baz: razmatazz`,
|
||||||
|
expect: `baz: razmatazz
|
||||||
|
foo: bar
|
||||||
|
`,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "blank",
|
||||||
|
base: `R#)*J#FN`,
|
||||||
|
overlay: `FM#)M#F(*#M`,
|
||||||
|
expect: "",
|
||||||
|
err: errors.New("invalid json"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got, err := OverlayYAML(tt.base, tt.overlay); got != tt.expect || ((tt.err != nil && err == nil) || (tt.err == nil && err != nil)) {
|
||||||
|
t.Errorf("%s: expected overlay&err %v %v got %v %v", tt.desc, tt.expect, tt.err, got, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLDiff(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
diff1 string
|
||||||
|
diff2 string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "1-line-diff",
|
||||||
|
diff1: `hola: yo
|
||||||
|
foo: bar
|
||||||
|
goo: tar
|
||||||
|
`,
|
||||||
|
diff2: `hola: yo
|
||||||
|
foo: bar
|
||||||
|
notgoo: nottar
|
||||||
|
`,
|
||||||
|
expect: ` foo: bar
|
||||||
|
-goo: tar
|
||||||
|
hola: yo
|
||||||
|
+notgoo: nottar
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no-diff",
|
||||||
|
diff1: `foo: bar`,
|
||||||
|
diff2: `foo: bar`,
|
||||||
|
expect: ``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid-yaml",
|
||||||
|
diff1: `Ij#**#f#`,
|
||||||
|
diff2: `fm*##)n`,
|
||||||
|
expect: "error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := YAMLDiff(tt.diff1, tt.diff2); got != tt.expect {
|
||||||
|
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleYAMLDiff(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
diff1 string
|
||||||
|
diff2 string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "1-line-diff",
|
||||||
|
diff1: `hola: yo
|
||||||
|
foo: bar
|
||||||
|
goo: tar
|
||||||
|
---
|
||||||
|
hola: yo1
|
||||||
|
foo: bar1
|
||||||
|
goo: tar1
|
||||||
|
`,
|
||||||
|
diff2: `hola: yo
|
||||||
|
foo: bar
|
||||||
|
notgoo: nottar
|
||||||
|
`,
|
||||||
|
expect: ` foo: bar
|
||||||
|
-goo: tar
|
||||||
|
hola: yo
|
||||||
|
+notgoo: nottar
|
||||||
|
|
||||||
|
-foo: bar1
|
||||||
|
-goo: tar1
|
||||||
|
-hola: yo1
|
||||||
|
+{}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no-diff",
|
||||||
|
diff1: `foo: bar
|
||||||
|
---
|
||||||
|
foo: bar1
|
||||||
|
`,
|
||||||
|
diff2: `foo: bar
|
||||||
|
---
|
||||||
|
foo: bar1
|
||||||
|
`,
|
||||||
|
expect: ``,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := YAMLDiff(tt.diff1, tt.diff2); got != tt.expect {
|
||||||
|
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsYAMLEqual(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in1 string
|
||||||
|
in2 string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "yaml-equal",
|
||||||
|
in1: `foo: bar`,
|
||||||
|
in2: `foo: bar`,
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "bad-yaml-1",
|
||||||
|
in1: "O#JF*()#",
|
||||||
|
in2: `foo: bar`,
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "bad-yaml-2",
|
||||||
|
in1: `foo: bar`,
|
||||||
|
in2: "#OHJ*#()F",
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "yaml-not-equal",
|
||||||
|
in1: `zinc: iron
|
||||||
|
stoichiometry: avagadro
|
||||||
|
`,
|
||||||
|
in2: `i-swear: i-am
|
||||||
|
definitely-not: in1
|
||||||
|
`,
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := IsYAMLEqual(tt.in1, tt.in2); got != tt.expect {
|
||||||
|
t.Errorf("%v: got %v want %v", tt.desc, got, tt.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsYAMLEmpty(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "completely-empty",
|
||||||
|
in: "",
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "comment-logically-empty",
|
||||||
|
in: `# this is a comment
|
||||||
|
# this is another comment that serves no purpose
|
||||||
|
# (like all comments usually do)
|
||||||
|
`,
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "start-yaml",
|
||||||
|
in: `--- I dont mean anything`,
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "combine-comments-and-yaml",
|
||||||
|
in: `#this is another comment
|
||||||
|
foo: bar
|
||||||
|
# ^ that serves purpose
|
||||||
|
`,
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "yaml-not-empty",
|
||||||
|
in: `foo: bar`,
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if got := IsYAMLEmpty(tt.in); got != tt.expect {
|
||||||
|
t.Errorf("%v: expect %v got %v", tt.desc, tt.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,8 +34,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
yamlOutput = "yaml"
|
|
||||||
jsonOutput = "json"
|
|
||||||
higressCoreContainerName = "higress-core"
|
higressCoreContainerName = "higress-core"
|
||||||
higressGatewayContainerName = "higress-gateway"
|
higressGatewayContainerName = "higress-gateway"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user