diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 9db2e7d2..6515f8ea 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -38,7 +38,7 @@ jobs: - name: Run golangci-lint run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b . v1.52.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b . v1.54.2 ./golangci-lint run --sort-results codespell: diff --git a/.golangci.yaml b/.golangci.yaml index c79f1bed..8ba4a2ae 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -37,12 +37,15 @@ linters-settings: goimports: local-prefixes: github.com/flant/ depguard: - list-type: blacklist - include-go-root: true - packages: - - github.com/evanphx/json-patch - packages-with-error-message: - - github.com/evanphx/json-patch: "The 'github.com/evanphx/json-patch' package is superseded. Use pkg/utils/jsonpatch.go instead." + rules: + Main: + files: + - $all + deny: + - pkg: "github.com/evanphx/json-patch" + desc: "The 'github.com/evanphx/json-patch' package is superseded. Use pkg/utils/jsonpatch.go instead." + - pkg: "gopkg.in/satori/go.uuid.v1" + desc: "Use https://github.com/gofrs/uuid instead. Satori/go.uuid is no longer maintained and has critical vulnerabilities." issues: exclude: # Using underscores is a common practice, refactor in the future diff --git a/cmd/addon-operator/main.go b/cmd/addon-operator/main.go index fd515d05..a8baf5d7 100644 --- a/cmd/addon-operator/main.go +++ b/cmd/addon-operator/main.go @@ -7,10 +7,12 @@ import ( "os" "time" + log "github.com/sirupsen/logrus" "gopkg.in/alecthomas/kingpin.v2" addon_operator "github.com/flant/addon-operator/pkg/addon-operator" "github.com/flant/addon-operator/pkg/app" + "github.com/flant/addon-operator/pkg/kube_config_manager/backend/configmap" "github.com/flant/addon-operator/pkg/utils/stdliblogtologrus" "github.com/flant/kube-client/klogtologrus" sh_app "github.com/flant/shell-operator/pkg/app" @@ -46,14 +48,23 @@ func main() { rand.Seed(time.Now().UnixNano()) operator := addon_operator.NewAddonOperator(context.Background()) - err := operator.Start() + + bk := configmap.New(log.StandardLogger(), operator.KubeClient(), app.Namespace, app.ConfigMapName) + operator.SetupKubeConfigManager(bk) + + err := operator.Setup() + if err != nil { + os.Exit(1) + } + + err = operator.Start() if err != nil { os.Exit(1) } // Block action by waiting signals from OS. utils_signal.WaitForProcessInterruption(func() { - operator.Shutdown() + operator.Stop() os.Exit(1) }) diff --git a/go.mod b/go.mod index 52f17628..0899ffd0 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,21 @@ go 1.19 require ( github.com/davecgh/go-spew v1.1.1 - github.com/flant/kube-client v0.26.1 - github.com/flant/shell-operator v0.0.0-20231013105726-aa38dfcd70d1 + github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 + github.com/flant/kube-client v1.0.1 + github.com/flant/shell-operator v1.4.1 github.com/go-chi/chi/v5 v5.0.10 github.com/go-openapi/loads v0.19.5 github.com/go-openapi/spec v0.19.8 github.com/go-openapi/strfmt v0.19.5 - github.com/go-openapi/swag v0.19.14 + github.com/go-openapi/swag v0.21.1 github.com/go-openapi/validate v0.19.12 + github.com/gofrs/uuid/v5 v5.0.0 github.com/hashicorp/go-multierror v1.1.1 github.com/kennygrant/sanitize v1.2.4 - github.com/onsi/gomega v1.27.10 + github.com/onsi/gomega v1.28.1 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_golang v1.17.0 github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 @@ -24,20 +26,17 @@ require ( github.com/tidwall/sjson v1.2.5 go.uber.org/goleak v1.2.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 - gopkg.in/satori/go.uuid.v1 v1.2.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.3 - k8s.io/api v0.25.5 - k8s.io/apimachinery v0.25.5 + k8s.io/api v0.26.9 + k8s.io/apimachinery v0.26.9 k8s.io/cli-runtime v0.25.5 - k8s.io/client-go v0.25.5 - k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed + k8s.io/client-go v0.26.9 + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d sigs.k8s.io/yaml v1.3.0 ) require ( - cloud.google.com/go/compute v1.19.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.1.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect @@ -45,8 +44,6 @@ require ( github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect @@ -62,11 +59,10 @@ require ( 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/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.13.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect github.com/flant/libjq-go v1.6.3-0.20201126171326-c46a40ff22ee // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-gorp/gorp/v3 v3.0.2 // indirect @@ -74,7 +70,7 @@ require ( github.com/go-openapi/analysis v0.19.10 // indirect github.com/go-openapi/errors v0.19.7 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/runtime v0.19.16 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -82,7 +78,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect @@ -92,7 +88,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.15 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -101,7 +97,7 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.6 // 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.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect @@ -112,7 +108,7 @@ require ( 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/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect @@ -122,14 +118,14 @@ require ( github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect github.com/rubenv/sql-migrate v1.1.2 // indirect github.com/russross/blackfriday v1.5.2 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.4.1 // indirect - github.com/spf13/cobra v1.5.0 // indirect + github.com/spf13/cobra v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect @@ -137,13 +133,14 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect - go.etcd.io/etcd/api/v3 v3.5.4 // indirect + go.etcd.io/etcd/api/v3 v3.5.5 // indirect go.mongodb.org/mongo-driver v1.5.4 // indirect + go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sync v0.2.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect @@ -151,15 +148,15 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.25.4 // indirect - k8s.io/apiserver v0.25.4 // indirect - k8s.io/component-base v0.25.5 // indirect - k8s.io/klog/v2 v2.70.1 // indirect - k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect + k8s.io/apiextensions-apiserver v0.26.9 // indirect + k8s.io/apiserver v0.26.9 // indirect + k8s.io/component-base v0.26.9 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect k8s.io/kubectl v0.25.5 // indirect oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect diff --git a/go.sum b/go.sum index 51ec28c6..1cfe8fa3 100644 --- a/go.sum +++ b/go.sum @@ -24,10 +24,6 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -64,9 +60,7 @@ github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -99,6 +93,7 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= @@ -110,6 +105,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns= github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= @@ -128,6 +124,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= +github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= @@ -146,14 +143,15 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -163,15 +161,14 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flant/go-openapi-validate v0.19.12-flant.0 h1:xk6kvc9fHKMgUdB6J7kbpbLR5vJOUzKAK8p3nrD7mDk= github.com/flant/go-openapi-validate v0.19.12-flant.0/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= -github.com/flant/kube-client v0.26.1 h1:QIT/Skmdv5Mpu8OWBzncmZdtlKXDKzkyhte7wlXnbVE= -github.com/flant/kube-client v0.26.1/go.mod h1:BPNhiy57FgJmnSBXy0l8AwBN6F9SHOfgFgHi3UG2Jrw= +github.com/flant/kube-client v1.0.1 h1:W2cUtu07VF8eWpdv49FomTJzREBHprkFzZgz/UbVp5A= +github.com/flant/kube-client v1.0.1/go.mod h1:YVcJ8PxomM0iYsK7nj2I3lmog2D0j/GloJfLYnhjlls= github.com/flant/libjq-go v1.6.3-0.20201126171326-c46a40ff22ee h1:evii83J+/6QGNvyf6tjQ/p27DPY9iftxIBb37ALJRTg= github.com/flant/libjq-go v1.6.3-0.20201126171326-c46a40ff22ee/go.mod h1:f+REaGl/+pZR97rbTcwHEka/MAipoQQ2Mc0iQUj4ak0= -github.com/flant/shell-operator v0.0.0-20231013105726-aa38dfcd70d1 h1:IBVnUuPA4j7NQIiH1AhnE67bSMbCVoM5agrgpyg/PdU= -github.com/flant/shell-operator v0.0.0-20231013105726-aa38dfcd70d1/go.mod h1:0Sb2td567MG7qRrPMz5Mqx4Y/6JWot/pKJMYFUGlikU= +github.com/flant/shell-operator v1.4.1 h1:nhgI9izYCzY51sdU+PgVnxYSoPK/VrSzNnrrr9GNUkM= +github.com/flant/shell-operator v1.4.1/go.mod h1:s5u0p4BtKMQAGoYU7x8qwwN/bD/eiY1gZ50XPGqHA+E= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -188,7 +185,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -209,8 +205,8 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls= github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= @@ -229,8 +225,8 @@ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= @@ -272,6 +268,8 @@ 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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= +github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= +github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -327,8 +325,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -346,7 +344,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -399,8 +397,9 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= @@ -458,8 +457,9 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -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.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/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/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= @@ -509,8 +509,8 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ 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/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -529,9 +529,9 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= +github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= @@ -558,22 +558,22 @@ github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhF github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -604,15 +604,14 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -622,13 +621,18 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -669,8 +673,8 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1 github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc= -go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= +go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -686,6 +690,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -802,8 +808,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -816,8 +822,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -958,7 +964,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1057,6 +1063,7 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1071,8 +1078,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1087,8 +1094,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 h1:E846t8CnR+lv5nE+VuiKTDG/v1U2stad0QzddfJC7kY= gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE= -gopkg.in/satori/go.uuid.v1 v1.2.0 h1:AH9uksa7bGe9rluapecRKBCpZvxaBEyu0RepitcD0Hw= -gopkg.in/satori/go.uuid.v1 v1.2.0/go.mod h1:kjjdhYBBaa5W5DYP+OcVG3fRM6VWu14hqDYST4Zvw+E= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1114,29 +1119,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.25.5 h1:mqyHf7aoaYMpdvO87mqpol+Qnsmo+y09S0PMIXwiZKo= -k8s.io/api v0.25.5/go.mod h1:RzplZX0Z8rV/WhSTfEvnyd91bBhBQTRWo85qBQwRmb8= -k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U= -k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ= -k8s.io/apimachinery v0.25.5 h1:SQomYHvv+aO43qdu3QKRf9YuI0oI8w3RrOQ1qPbAUGY= -k8s.io/apimachinery v0.25.5/go.mod h1:1S2i1QHkmxc8+EZCIxe/fX5hpldVXk4gvnJInMEb8D4= -k8s.io/apiserver v0.25.4 h1:/3TwZcgLqX7wUxq7TtXOUqXeBTwXIblVMQdhR5XZ7yo= -k8s.io/apiserver v0.25.4/go.mod h1:rPcm567XxjOnnd7jedDUnGJGmDGAo+cT6H7QHAN+xV0= +k8s.io/api v0.26.9 h1:s8Y+G1u2JM55b90+Yo2RVb3PGT/hkWNVPN4idPERxJg= +k8s.io/api v0.26.9/go.mod h1:W/W4fEWRVzPD36820LlVUQfNBiSbiq0VPWRFJKwzmUg= +k8s.io/apiextensions-apiserver v0.26.9 h1:aJqWRuBj9i9J6tIDniqUDYM5QCRajTKXK/GO+zEccGQ= +k8s.io/apiextensions-apiserver v0.26.9/go.mod h1:L1uysxOP2kC1vkZTlHGUlUl5WSpa7e4GHJmGEZY7yLg= +k8s.io/apimachinery v0.26.9 h1:5yAV9cFR7Z4gIorKcAjWnx4uxtxiFsERwq4Pvmx0CCg= +k8s.io/apimachinery v0.26.9/go.mod h1:qYzLkrQ9lhrZRh0jNKo2cfvf/R1/kQONnSiyB7NUJU0= +k8s.io/apiserver v0.26.9 h1:G8D5XIXbhLzqdRY3FajzkKE2lt8hnAW5Vjq67mzEeR8= +k8s.io/apiserver v0.26.9/go.mod h1:HY2TzNkDgq71jsNLyk61ZoDrpiyvujdY6kHyT9DwvtU= k8s.io/cli-runtime v0.25.5 h1:5Q37ITYtPtSw2JQcN6EBsdOQBnGvvo/D1g93Da4ceYI= k8s.io/cli-runtime v0.25.5/go.mod h1:o7lT2rFyfbLrQOzTFsV828OyxKsTE/FmVc3ag1nx0IU= -k8s.io/client-go v0.25.5 h1:7QWVK0Ph4bLn0UwotPTc2FTgm8shreQXyvXnnHDd8rE= -k8s.io/client-go v0.25.5/go.mod h1:bOeoaUUdpyz3WDFGo+Xm3nOQFh2KuYXRDwrvbAPtFQA= -k8s.io/component-base v0.25.5 h1:tVni0kgpceq71MDMBSixp8Y621YGvTS/1zq3RABgX9A= -k8s.io/component-base v0.25.5/go.mod h1:9J+e9uIUwUOG2x5q5+aaOR0b8QI5OIqwqPAbeODkYpc= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= -k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= +k8s.io/client-go v0.26.9 h1:TGWi/6guEjIgT0Hg871Gsmx0qFuoGyGFjlFedrk7It0= +k8s.io/client-go v0.26.9/go.mod h1:tU1FZS0bwAmAFyPYpZycUQrQnUMzQ5MHloop7EbX6ow= +k8s.io/component-base v0.26.9 h1:qQVdQgyEIUe8EUkB3EEuQ9l5sgVlG2KgOB519yWEBGw= +k8s.io/component-base v0.26.9/go.mod h1:3WmW9lH9tbjpuvpAc22cPF/6C3VxCjMxkOU1j2mpzr8= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kubectl v0.25.5 h1:m3Y3ysrlRR+wAD0TYoFCrngcYtwVH7UPCqubEwbe6oo= k8s.io/kubectl v0.25.5/go.mod h1:EeHEydYE+pR8EUo5LkO27CP9ONxZpdmE2BWPEqTS8hY= -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-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/addon-operator/bootstrap.go b/pkg/addon-operator/bootstrap.go index ec35f6a6..d34acf96 100644 --- a/pkg/addon-operator/bootstrap.go +++ b/pkg/addon-operator/bootstrap.go @@ -1,13 +1,11 @@ package addon_operator import ( - "fmt" - log "github.com/sirupsen/logrus" "github.com/flant/addon-operator/pkg/app" - "github.com/flant/addon-operator/pkg/helm" "github.com/flant/addon-operator/pkg/kube_config_manager" + "github.com/flant/addon-operator/pkg/kube_config_manager/backend" "github.com/flant/addon-operator/pkg/module_manager" sh_app "github.com/flant/shell-operator/pkg/app" "github.com/flant/shell-operator/pkg/config" @@ -17,42 +15,24 @@ import ( // Bootstrap inits all dependencies for a full-fledged AddonOperator instance. func (op *AddonOperator) bootstrap() error { - runtimeConfig := config.NewConfig() + op.runtimeConfig = config.NewConfig() // Init logging subsystem. - sh_app.SetupLogging(runtimeConfig) + sh_app.SetupLogging(op.runtimeConfig) log.Infof(sh_app.AppStartMessage) log.Infof("Search modules in: %s", app.ModulesDir) - globalHooksDir, err := shell_operator.RequireExistingDirectory(app.GlobalHooksDir) - if err != nil { - log.Errorf("Fatal: global hooks directory: %s", err) - return err - } - log.Infof("Global hooks directory: %s", globalHooksDir) - - tempDir, err := shell_operator.EnsureTempDirectory(sh_app.TempDir) - if err != nil { - log.Errorf("Fatal: temp directory: %s", err) - return err - } - log.Infof("Addon-operator namespace: %s", app.Namespace) // Debug server. - debugServer, err := shell_operator.InitDefaultDebugServer() + // TODO: rewrite sh_app global variables to the addon-operator ones + debugServer, err := shell_operator.RunDefaultDebugServer(sh_app.DebugUnixSocket, sh_app.DebugHttpServerAddr) if err != nil { log.Errorf("Fatal: start Debug server: %s", err) return err } - err = shell_operator.AssembleCommonOperator(op.ShellOperator) - if err != nil { - log.Errorf("Fatal: %s", err) - return err - } - - err = op.Assemble(app.ModulesDir, globalHooksDir, tempDir, debugServer, runtimeConfig) + err = op.Assemble(debugServer) if err != nil { log.Errorf("Fatal: %s", err) return err @@ -61,39 +41,24 @@ func (op *AddonOperator) bootstrap() error { return nil } -func (op *AddonOperator) Assemble(modulesDir string, globalHooksDir string, tempDir string, debugServer *debug.Server, runtimeConfig *config.Config) (err error) { - op.RegisterDefaultRoutes() +func (op *AddonOperator) Assemble(debugServer *debug.Server) (err error) { + op.registerDefaultRoutes() if app.AdmissionServerEnabled { op.AdmissionServer.start(op.ctx) } - RegisterAddonOperatorMetrics(op.MetricStorage) - StartLiveTicksUpdater(op.MetricStorage) - StartTasksQueueLengthUpdater(op.MetricStorage, op.TaskQueues) + RegisterAddonOperatorMetrics(op.engine.MetricStorage) + StartLiveTicksUpdater(op.engine.MetricStorage) + StartTasksQueueLengthUpdater(op.engine.MetricStorage, op.engine.TaskQueues) // Register routes in debug server. - shell_operator.RegisterDebugQueueRoutes(debugServer, op.ShellOperator) - shell_operator.RegisterDebugConfigRoutes(debugServer, runtimeConfig) + op.engine.RegisterDebugQueueRoutes(debugServer) + op.engine.RegisterDebugConfigRoutes(debugServer, op.runtimeConfig) op.RegisterDebugGlobalRoutes(debugServer) op.RegisterDebugModuleRoutes(debugServer) // service (kubernetes object) does not have endpoints before addon-operator is ready // we have to wait readiness probe (linked on the first-converge) before manipulating in-cluster objects covered with validation webhook op.OnFirstConvergeDone() - // Helm client factory. - op.Helm, err = helm.InitHelmClientFactory(op.KubeClient) - if err != nil { - return fmt.Errorf("initialize Helm: %s", err) - } - - // Helm resources monitor. - // It uses a separate client-go instance. (Metrics are registered when 'main' client is initialized). - op.HelmResourcesManager, err = InitDefaultHelmResourcesManager(op.ctx, op.MetricStorage) - if err != nil { - return fmt.Errorf("initialize Helm resources manager: %s", err) - } - - op.SetupModuleManager(modulesDir, globalHooksDir, tempDir, runtimeConfig) - err = op.InitModuleManager() if err != nil { return err @@ -102,17 +67,17 @@ func (op *AddonOperator) Assemble(modulesDir string, globalHooksDir string, temp return nil } -func (op *AddonOperator) SetupModuleManager(modulesDir string, globalHooksDir string, tempDir string, runtimeConfig *config.Config) { - // Create manager to check values in ConfigMap. - kcfg := kube_config_manager.Config{ - Namespace: app.Namespace, - ConfigMapName: app.ConfigMapName, - KubeClient: op.KubeClient, - RuntimeConfig: runtimeConfig, +// SetupKubeConfigManager sets manager, which reads configuration for Modules from a cluster +func (op *AddonOperator) SetupKubeConfigManager(bk backend.ConfigHandler) { + if op.KubeConfigManager != nil { + // return if kube config manager is already set + return } - manager := kube_config_manager.NewKubeConfigManager(op.ctx, &kcfg) - op.KubeConfigManager = manager + op.KubeConfigManager = kube_config_manager.NewKubeConfigManager(op.ctx, bk, op.runtimeConfig) +} + +func (op *AddonOperator) SetupModuleManager(modulesDir string, globalHooksDir string, tempDir string) { // Create manager that runs modules and hooks. dirConfig := module_manager.DirectoryConfig{ ModulesDir: modulesDir, @@ -120,14 +85,14 @@ func (op *AddonOperator) SetupModuleManager(modulesDir string, globalHooksDir st TempDir: tempDir, } deps := module_manager.ModuleManagerDependencies{ - KubeObjectPatcher: op.ObjectPatcher, - KubeEventsManager: op.KubeEventsManager, - KubeConfigManager: manager, - ScheduleManager: op.ScheduleManager, + KubeObjectPatcher: op.engine.ObjectPatcher, + KubeEventsManager: op.engine.KubeEventsManager, + KubeConfigManager: op.KubeConfigManager, + ScheduleManager: op.engine.ScheduleManager, Helm: op.Helm, HelmResourcesManager: op.HelmResourcesManager, - MetricStorage: op.MetricStorage, - HookMetricStorage: op.HookMetricStorage, + MetricStorage: op.engine.MetricStorage, + HookMetricStorage: op.engine.HookMetricStorage, } cfg := module_manager.ModuleManagerConfig{ diff --git a/pkg/addon-operator/debug_server.go b/pkg/addon-operator/debug_server.go index a48b1872..43276df6 100644 --- a/pkg/addon-operator/debug_server.go +++ b/pkg/addon-operator/debug_server.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "os" + "sort" "strconv" "github.com/go-chi/chi/v5" @@ -14,25 +15,25 @@ import ( ) func (op *AddonOperator) RegisterDebugGlobalRoutes(dbgSrv *debug.Server) { - dbgSrv.Route("/global/list.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/global/list.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { return map[string]interface{}{ "globalHooks": op.ModuleManager.GetGlobalHooksNames(), }, nil }) - dbgSrv.Route("/global/values.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/global/values.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { return op.ModuleManager.GlobalValues() }) - dbgSrv.Route("/global/config.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/global/config.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { return op.ModuleManager.GlobalConfigValues(), nil }) - dbgSrv.Route("/global/patches.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/global/patches.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { return op.ModuleManager.GlobalValuesPatches(), nil }) - dbgSrv.Route("/global/snapshots.{format:(json|yaml)}", func(r *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/global/snapshots.{format:(json|yaml)}", func(r *http.Request) (interface{}, error) { kubeHookNames := op.ModuleManager.GetGlobalHooksInOrder(types.OnKubernetesEvent) snapshots := make(map[string]interface{}) for _, hName := range kubeHookNames { @@ -45,11 +46,13 @@ func (op *AddonOperator) RegisterDebugGlobalRoutes(dbgSrv *debug.Server) { } func (op *AddonOperator) RegisterDebugModuleRoutes(dbgSrv *debug.Server) { - dbgSrv.Route("/module/list.{format:(json|yaml|text)}", func(_ *http.Request) (interface{}, error) { - return map[string][]string{"enabledModules": op.ModuleManager.GetEnabledModuleNames()}, nil + dbgSrv.RegisterHandler(http.MethodGet, "/module/list.{format:(json|yaml|text)}", func(_ *http.Request) (interface{}, error) { + modules := op.ModuleManager.GetEnabledModuleNames() + sort.Strings(modules) + return map[string][]string{"enabledModules": modules}, nil }) - dbgSrv.Route("/module/{name}/{type:(config|values)}.{format:(json|yaml)}", func(r *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/module/{name}/{type:(config|values)}.{format:(json|yaml)}", func(r *http.Request) (interface{}, error) { modName := chi.URLParam(r, "name") valType := chi.URLParam(r, "type") @@ -67,11 +70,12 @@ func (op *AddonOperator) RegisterDebugModuleRoutes(dbgSrv *debug.Server) { return "no values", nil }) - dbgSrv.Route("/module/{name}/render", func(r *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/module/{name}/render", func(r *http.Request) (interface{}, error) { modName := chi.URLParam(r, "name") - debug, err := strconv.ParseBool(r.URL.Query().Get("debug")) + dbg, err := strconv.ParseBool(r.URL.Query().Get("debug")) if err != nil { - return nil, fmt.Errorf("can't parse debug query parameter: %w", err) + // if empty or unparsable - set false + dbg = false } m := op.ModuleManager.GetModule(modName) @@ -85,10 +89,10 @@ func (op *AddonOperator) RegisterDebugModuleRoutes(dbgSrv *debug.Server) { } defer os.Remove(valuesPath) - return op.Helm.NewClient().Render(m.Name, m.Path, []string{valuesPath}, nil, app.Namespace, debug) + return op.Helm.NewClient().Render(m.Name, m.Path, []string{valuesPath}, nil, app.Namespace, dbg) }) - dbgSrv.Route("/module/{name}/patches.json", func(r *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/module/{name}/patches.json", func(r *http.Request) (interface{}, error) { modName := chi.URLParam(r, "name") m := op.ModuleManager.GetModule(modName) @@ -99,7 +103,7 @@ func (op *AddonOperator) RegisterDebugModuleRoutes(dbgSrv *debug.Server) { return op.ModuleManager.ModuleDynamicValuesPatches(modName), nil }) - dbgSrv.Route("/module/resource-monitor.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/module/resource-monitor.{format:(json|yaml)}", func(_ *http.Request) (interface{}, error) { dump := map[string]interface{}{} for _, moduleName := range op.ModuleManager.GetEnabledModuleNames() { @@ -115,7 +119,7 @@ func (op *AddonOperator) RegisterDebugModuleRoutes(dbgSrv *debug.Server) { return dump, nil }) - dbgSrv.Route("/module/{name}/snapshots.{format:(json|yaml)}", func(r *http.Request) (interface{}, error) { + dbgSrv.RegisterHandler(http.MethodGet, "/module/{name}/snapshots.{format:(json|yaml)}", func(r *http.Request) (interface{}, error) { modName := chi.URLParam(r, "name") m := op.ModuleManager.GetModule(modName) diff --git a/pkg/addon-operator/http_server.go b/pkg/addon-operator/http_server.go index 7173d27b..76911625 100644 --- a/pkg/addon-operator/http_server.go +++ b/pkg/addon-operator/http_server.go @@ -6,30 +6,33 @@ import ( "strings" "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/flant/addon-operator/pkg/app" ) -func (op *AddonOperator) RegisterDefaultRoutes() { - http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { - _, _ = writer.Write([]byte(` +func (op *AddonOperator) registerDefaultRoutes() { + op.engine.APIServer.RegisterRoute(http.MethodGet, "/", func(writer http.ResponseWriter, request *http.Request) { + _, _ = writer.Write([]byte(fmt.Sprintf(` Addon-operator

Addon-operator

-
go tool pprof goprofex http://ADDON_OPERATOR_IP:9115/debug/pprof/profile
+
go tool pprof http://ADDON_OPERATOR_IP:%s/debug/pprof/profile

+ show all possible routes prometheus metrics health url ready url

- `)) + `, app.ListenPort))) }) - http.Handle("/metrics", promhttp.Handler()) + op.engine.APIServer.RegisterRoute(http.MethodGet, "/metrics", promhttp.Handler().ServeHTTP) - http.HandleFunc("/healthz", func(writer http.ResponseWriter, request *http.Request) { + op.engine.APIServer.RegisterRoute(http.MethodGet, "/healthz", func(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(http.StatusOK) }) - http.HandleFunc("/readyz", func(w http.ResponseWriter, request *http.Request) { + op.engine.APIServer.RegisterRoute(http.MethodGet, "/readyz", func(w http.ResponseWriter, request *http.Request) { if op.IsStartupConvergeDone() { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("Startup converge done.\n")) @@ -39,8 +42,8 @@ func (op *AddonOperator) RegisterDefaultRoutes() { } }) - http.HandleFunc("/status/converge", func(writer http.ResponseWriter, request *http.Request) { - convergeTasks := ConvergeTasksInQueue(op.TaskQueues.GetMain()) + op.engine.APIServer.RegisterRoute(http.MethodGet, "/status/converge", func(writer http.ResponseWriter, request *http.Request) { + convergeTasks := ConvergeTasksInQueue(op.engine.TaskQueues.GetMain()) statusLines := make([]string, 0) switch op.ConvergeState.firstRunPhase { diff --git a/pkg/addon-operator/kube_client.go b/pkg/addon-operator/kube_client.go index 22f62485..28645471 100644 --- a/pkg/addon-operator/kube_client.go +++ b/pkg/addon-operator/kube_client.go @@ -9,26 +9,26 @@ import ( klient "github.com/flant/kube-client/client" sh_app "github.com/flant/shell-operator/pkg/app" "github.com/flant/shell-operator/pkg/metric_storage" - shell_operator "github.com/flant/shell-operator/pkg/shell-operator" + utils "github.com/flant/shell-operator/pkg/utils/labels" ) // DefaultHelmMonitorKubeClientMetricLabels are labels that indicates go client metrics producer. // Important! These labels should be consistent with similar labels in ShellOperator! var DefaultHelmMonitorKubeClientMetricLabels = map[string]string{"component": "helm_monitor"} -// DefaultHelmMonitorKubeClient initializes a Kubernetes client for helm monitor. -func DefaultHelmMonitorKubeClient(metricStorage *metric_storage.MetricStorage, metricLabels map[string]string) klient.Client { +// defaultHelmMonitorKubeClient initializes a Kubernetes client for helm monitor. +func defaultHelmMonitorKubeClient(metricStorage *metric_storage.MetricStorage, metricLabels map[string]string) *klient.Client { client := klient.New() client.WithContextName(sh_app.KubeContext) client.WithConfigPath(sh_app.KubeConfig) client.WithRateLimiterSettings(app.HelmMonitorKubeClientQps, app.HelmMonitorKubeClientBurst) client.WithMetricStorage(metricStorage) - client.WithMetricLabels(shell_operator.DefaultIfEmpty(metricLabels, DefaultHelmMonitorKubeClientMetricLabels)) + client.WithMetricLabels(utils.DefaultIfEmpty(metricLabels, DefaultHelmMonitorKubeClientMetricLabels)) return client } func InitDefaultHelmResourcesManager(ctx context.Context, metricStorage *metric_storage.MetricStorage) (helm_resources_manager.HelmResourcesManager, error) { - kubeClient := DefaultHelmMonitorKubeClient(metricStorage, DefaultHelmMonitorKubeClientMetricLabels) + kubeClient := defaultHelmMonitorKubeClient(metricStorage, DefaultHelmMonitorKubeClientMetricLabels) if err := kubeClient.Init(); err != nil { return nil, fmt.Errorf("initialize Kubernetes client for Helm resources manager: %s\n", err) } diff --git a/pkg/addon-operator/operator.go b/pkg/addon-operator/operator.go index 4bb49226..2eb13714 100644 --- a/pkg/addon-operator/operator.go +++ b/pkg/addon-operator/operator.go @@ -3,23 +3,26 @@ package addon_operator import ( "context" "fmt" - _ "net/http/pprof" // Webserver pprof endpoint injection "path" "runtime/trace" "strings" "time" + "github.com/gofrs/uuid/v5" log "github.com/sirupsen/logrus" - uuid "gopkg.in/satori/go.uuid.v1" "github.com/flant/addon-operator/pkg/app" "github.com/flant/addon-operator/pkg/helm" "github.com/flant/addon-operator/pkg/helm_resources_manager" hookTypes "github.com/flant/addon-operator/pkg/hook/types" "github.com/flant/addon-operator/pkg/kube_config_manager" + "github.com/flant/addon-operator/pkg/kube_config_manager/config" "github.com/flant/addon-operator/pkg/module_manager" "github.com/flant/addon-operator/pkg/task" "github.com/flant/addon-operator/pkg/utils" + "github.com/flant/kube-client/client" + sh_app "github.com/flant/shell-operator/pkg/app" + runtimeConfig "github.com/flant/shell-operator/pkg/config" bc "github.com/flant/shell-operator/pkg/hook/binding_context" "github.com/flant/shell-operator/pkg/hook/controller" htypes "github.com/flant/shell-operator/pkg/hook/types" @@ -27,16 +30,19 @@ import ( shell_operator "github.com/flant/shell-operator/pkg/shell-operator" sh_task "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" + fileUtils "github.com/flant/shell-operator/pkg/utils/file" "github.com/flant/shell-operator/pkg/utils/measure" ) // AddonOperator extends ShellOperator with modules and global hooks // and with a value storage. type AddonOperator struct { - *shell_operator.ShellOperator + engine *shell_operator.ShellOperator ctx context.Context cancel context.CancelFunc + runtimeConfig *runtimeConfig.Config + // KubeConfigManager monitors changes in ConfigMap. KubeConfigManager *kube_config_manager.KubeConfigManager @@ -53,7 +59,7 @@ type AddonOperator struct { ConvergeState *ConvergeState // Initial KubeConfig to bypass initial loading from the ConfigMap. - InitialKubeConfig *kube_config_manager.KubeConfig + InitialKubeConfig *config.KubeConfig // AdmissionServer handles validation and mutation admission webhooks AdmissionServer *AdmissionServer @@ -65,13 +71,18 @@ type AddonOperator struct { func NewAddonOperator(ctx context.Context) *AddonOperator { cctx, cancel := context.WithCancel(ctx) - so := shell_operator.NewShellOperator() - so.WithContext(cctx) + so := shell_operator.NewShellOperator(cctx) + + // Have to initialize common operator to have all common dependencies below + err := so.AssembleCommonOperator(app.ListenAddress, app.ListenPort) + if err != nil { + panic(err) + } ao := &AddonOperator{ ctx: cctx, cancel: cancel, - ShellOperator: so, + engine: so, ConvergeState: NewConvergeState(), ExplicitlyPurgeModules: make([]string, 0), } @@ -81,12 +92,92 @@ func NewAddonOperator(ctx context.Context) *AddonOperator { return ao } +func (op *AddonOperator) Setup() error { + // Helm client factory. + helmClient, err := helm.InitHelmClientFactory(op.engine.KubeClient) + if err != nil { + return fmt.Errorf("initialize Helm: %s", err) + } + + // Helm resources monitor. + // It uses a separate client-go instance. (Metrics are registered when 'main' client is initialized). + helmResourcesManager, err := InitDefaultHelmResourcesManager(op.ctx, op.engine.MetricStorage) + if err != nil { + return fmt.Errorf("initialize Helm resources manager: %s", err) + } + + op.Helm = helmClient + op.HelmResourcesManager = helmResourcesManager + + globalHooksDir, err := fileUtils.RequireExistingDirectory(app.GlobalHooksDir) + if err != nil { + return fmt.Errorf("global hooks directory: %s", err) + } + log.Infof("Global hooks directory: %s", globalHooksDir) + + tempDir, err := fileUtils.EnsureTempDirectory(sh_app.TempDir) + if err != nil { + return fmt.Errorf("temp directory: %s", err) + } + + if op.KubeConfigManager == nil { + return fmt.Errorf("KubeConfigManager must be set before Setup") + } + + op.SetupModuleManager(app.ModulesDir, globalHooksDir, tempDir) + + return nil +} + +// Start runs all managers, event and queue handlers. +func (op *AddonOperator) Start() error { + if err := op.bootstrap(); err != nil { + return err + } + + // start http server with metrics + op.engine.APIServer.Start(op.ctx) + + log.Info("Start first converge for modules") + // Loading the onStartup hooks into the queue and running all modules. + // Turning tracking changes on only after startup ends. + + // Bootstrap main queue with tasks to run Startup process. + op.BootstrapMainQueue(op.engine.TaskQueues) + // Start main task queue handler + op.engine.TaskQueues.StartMain() + + // Global hooks are registered, initialize their queues. + op.CreateAndStartQueuesForGlobalHooks() + + // ManagerEventsHandler handle events for kubernetes and schedule bindings. + // Start it before start all informers to catch all kubernetes events (#42) + op.engine.ManagerEventsHandler.Start() + + // Enable events from schedule manager. + op.engine.ScheduleManager.Start() + + op.KubeConfigManager.Start() + op.ModuleManager.Start() + op.StartModuleManagerEventHandler() + + return nil +} + func (op *AddonOperator) Stop() { + op.KubeConfigManager.Stop() + op.engine.Shutdown() + if op.cancel != nil { op.cancel() } } +// KubeClient returns default common kubernetes client initialized by shell-operator +func (op *AddonOperator) KubeClient() *client.Client { + return op.engine.KubeClient +} + func (op *AddonOperator) IsStartupConvergeDone() bool { return op.ConvergeState.firstRunPhase == firstDone } @@ -112,11 +203,11 @@ func (op *AddonOperator) InitModuleManager() error { // Also, it is possible to override initial KubeConfig to give global hooks a chance // to handle the ConfigMap content later. if op.InitialKubeConfig == nil { - op.KubeConfigManager.SafeReadConfig(func(config *kube_config_manager.KubeConfig) { + op.KubeConfigManager.SafeReadConfig(func(config *config.KubeConfig) { _, err = op.ModuleManager.HandleNewKubeConfig(config) }) if err != nil { - return fmt.Errorf("init module manager: load config from ConfigMap: %s", err) + return fmt.Errorf("init module manager: load initial config for KubeConfigManager: %s", err) } } else { _, err = op.ModuleManager.HandleNewKubeConfig(op.InitialKubeConfig) @@ -147,9 +238,9 @@ func (op *AddonOperator) AllowHandleScheduleEvent(hook *module_manager.CommonHoo } func (op *AddonOperator) RegisterManagerEventsHandlers() { - op.ManagerEventsHandler.WithScheduleEventHandler(func(crontab string) []sh_task.Task { + op.engine.ManagerEventsHandler.WithScheduleEventHandler(func(crontab string) []sh_task.Task { logLabels := map[string]string{ - "event.id": uuid.NewV4().String(), + "event.id": uuid.Must(uuid.NewV4()).String(), "binding": string(htypes.Schedule), } logEntry := log.WithFields(utils.LabelsToLogFields(logLabels)) @@ -223,9 +314,9 @@ func (op *AddonOperator) RegisterManagerEventsHandlers() { return tasks }) - op.ManagerEventsHandler.WithKubeEventHandler(func(kubeEvent types.KubeEvent) []sh_task.Task { + op.engine.ManagerEventsHandler.WithKubeEventHandler(func(kubeEvent types.KubeEvent) []sh_task.Task { logLabels := map[string]string{ - "event.id": uuid.NewV4().String(), + "event.id": uuid.Must(uuid.NewV4()).String(), "binding": string(htypes.OnKubernetesEvent), } logEntry := log.WithFields(utils.LabelsToLogFields(logLabels)) @@ -292,38 +383,6 @@ func (op *AddonOperator) RegisterManagerEventsHandlers() { }) } -// Start runs all managers, event and queue handlers. -func (op *AddonOperator) Start() error { - if err := op.bootstrap(); err != nil { - return err - } - - log.Info("Start first converge for modules") - // Loading the onStartup hooks into the queue and running all modules. - // Turning tracking changes on only after startup ends. - - // Bootstrap main queue with tasks to run Startup process. - op.BootstrapMainQueue(op.TaskQueues) - // Start main task queue handler - op.TaskQueues.StartMain() - - // Global hooks are registered, initialize their queues. - op.CreateAndStartQueuesForGlobalHooks() - - // ManagerEventsHandler handle events for kubernetes and schedule bindings. - // Start it before start all informers to catch all kubernetes events (#42) - op.ManagerEventsHandler.Start() - - // Enable events from schedule manager. - op.ScheduleManager.Start() - - op.KubeConfigManager.Start() - op.ModuleManager.Start() - op.StartModuleManagerEventHandler() - - return nil -} - // BootstrapMainQueue adds tasks to initiate Startup sequence: // // - Run onStartup hooks. @@ -346,7 +405,7 @@ func (op *AddonOperator) BootstrapMainQueue(tqs *queue.TaskQueueSet) { tasks := op.CreateBootstrapTasks(logLabels) op.logTaskAdd(logEntry, "append", tasks...) for _, tsk := range tasks { - op.TaskQueues.GetMain().AddLast(tsk) + op.engine.TaskQueues.GetMain().AddLast(tsk) } } @@ -502,7 +561,7 @@ func (op *AddonOperator) HandleConvergeModules(t sh_task.Task, logLabels map[str if taskEvent == KubeConfigChanged { logEntry.Debugf("ConvergeModules: handle KubeConfigChanged") var state *module_manager.ModulesState - op.KubeConfigManager.SafeReadConfig(func(config *kube_config_manager.KubeConfig) { + op.KubeConfigManager.SafeReadConfig(func(config *config.KubeConfig) { state, handleErr = op.ModuleManager.HandleNewKubeConfig(config) }) if handleErr == nil { @@ -537,7 +596,7 @@ func (op *AddonOperator) HandleConvergeModules(t sh_task.Task, logLabels map[str // If Converge is in progress, drain converge tasks after current task // and put ConvergeModules at start. // Else put ConvergeModules to the end of the main queue. - convergeDrained := RemoveCurrentConvergeTasks(op.TaskQueues.GetMain(), t.GetId()) + convergeDrained := RemoveCurrentConvergeTasks(op.engine.TaskQueues.GetMain(), t.GetId()) if convergeDrained { logEntry.Infof("ConvergeModules: kube config modification detected, restart current converge process (%s)", op.ConvergeState.Phase) res.AfterTasks = []sh_task.Task{ @@ -561,7 +620,7 @@ func (op *AddonOperator) HandleConvergeModules(t sh_task.Task, logLabels map[str logEntry.Debugf("ConvergeModules: start") // Deduplicate tasks: remove ConvergeModules tasks right after the current task. - RemoveAdjacentConvergeModules(op.TaskQueues.GetByName(t.GetQueueName()), t.GetId()) + RemoveAdjacentConvergeModules(op.engine.TaskQueues.GetByName(t.GetQueueName()), t.GetId()) op.ConvergeState.Phase = RunBeforeAll } @@ -632,7 +691,7 @@ func (op *AddonOperator) HandleConvergeModules(t sh_task.Task, logLabels map[str if handleErr != nil { res.Status = queue.Fail logEntry.Errorf("ConvergeModules failed in phase '%s', requeue task to retry after delay. Failed count is %d. Error: %s", op.ConvergeState.Phase, t.GetFailureCount()+1, handleErr) - op.MetricStorage.CounterAdd("{PREFIX}modules_discover_errors_total", 1.0, map[string]string{}) + op.engine.MetricStorage.CounterAdd("{PREFIX}modules_discover_errors_total", 1.0, map[string]string{}) t.UpdateFailureMessage(handleErr.Error()) t.WithQueuedAt(time.Now()) return res @@ -720,11 +779,8 @@ func (op *AddonOperator) CreateAfterAllTasks(logLabels map[string]string, eventD if err != nil { return nil, err } - taskMetadata.ValuesChecksum, err = globalValues.Checksum() + taskMetadata.ValuesChecksum = globalValues.Checksum() taskMetadata.DynamicEnabledChecksum = op.ModuleManager.DynamicEnabledChecksum() - if err != nil { - return nil, err - } } newTask := sh_task.NewTask(task.GlobalHookRun). @@ -740,11 +796,11 @@ func (op *AddonOperator) CreateAfterAllTasks(logLabels map[string]string, eventD // CreateAndStartQueue creates a named queue and starts it. // It returns false is queue is already created func (op *AddonOperator) CreateAndStartQueue(queueName string) bool { - if op.TaskQueues.GetByName(queueName) != nil { + if op.engine.TaskQueues.GetByName(queueName) != nil { return false } - op.TaskQueues.NewNamedQueue(queueName, op.TaskHandler) - op.TaskQueues.GetByName(queueName).Start() + op.engine.TaskQueues.NewNamedQueue(queueName, op.TaskHandler) + op.engine.TaskQueues.GetByName(queueName).Start() return true } @@ -799,10 +855,10 @@ func (op *AddonOperator) DrainModuleQueues(modName string) { for _, hookName := range op.ModuleManager.GetModuleHookNames(modName) { h := op.ModuleManager.GetModuleHook(hookName) for _, hookBinding := range h.Config.Schedules { - DrainNonMainQueue(op.TaskQueues.GetByName(hookBinding.Queue)) + DrainNonMainQueue(op.engine.TaskQueues.GetByName(hookBinding.Queue)) } for _, hookBinding := range h.Config.OnKubernetesEvents { - DrainNonMainQueue(op.TaskQueues.GetByName(hookBinding.Queue)) + DrainNonMainQueue(op.engine.TaskQueues.GetByName(hookBinding.Queue)) } } } @@ -814,16 +870,16 @@ func (op *AddonOperator) StartModuleManagerEventHandler() { select { case kubeConfigEvent := <-op.KubeConfigManager.KubeConfigEventCh(): logLabels := map[string]string{ - "event.id": uuid.NewV4().String(), + "event.id": uuid.Must(uuid.NewV4()).String(), } eventLogEntry := logEntry.WithFields(utils.LabelsToLogFields(logLabels)) - if kubeConfigEvent == kube_config_manager.KubeConfigInvalid { + if kubeConfigEvent == config.KubeConfigInvalid { op.ModuleManager.SetKubeConfigValid(false) eventLogEntry.Infof("KubeConfig become invalid") } - if kubeConfigEvent == kube_config_manager.KubeConfigChanged { + if kubeConfigEvent == config.KubeConfigChanged { if !op.ModuleManager.GetKubeConfigValid() { eventLogEntry.Infof("KubeConfig become valid") } @@ -835,21 +891,21 @@ func (op *AddonOperator) StartModuleManagerEventHandler() { KubeConfigChanged, logLabels, ) - op.TaskQueues.GetMain().AddFirst(convergeTask) + op.engine.TaskQueues.GetMain().AddFirst(convergeTask) // Cancel delay in case the head task is stuck in the error loop. - op.TaskQueues.GetMain().CancelTaskDelay() + op.engine.TaskQueues.GetMain().CancelTaskDelay() op.logTaskAdd(eventLogEntry, "KubeConfig is changed, put first", convergeTask) } case absentResourcesEvent := <-op.HelmResourcesManager.Ch(): logLabels := map[string]string{ - "event.id": uuid.NewV4().String(), + "event.id": uuid.Must(uuid.NewV4()).String(), "module": absentResourcesEvent.ModuleName, } eventLogEntry := logEntry.WithFields(utils.LabelsToLogFields(logLabels)) // Do not add ModuleRun task if it is already queued. - hasTask := QueueHasPendingModuleRunTask(op.TaskQueues.GetMain(), absentResourcesEvent.ModuleName) + hasTask := QueueHasPendingModuleRunTask(op.engine.TaskQueues.GetMain(), absentResourcesEvent.ModuleName) if !hasTask { newTask := sh_task.NewTask(task.ModuleRun). WithLogLabels(logLabels). @@ -858,7 +914,7 @@ func (op *AddonOperator) StartModuleManagerEventHandler() { EventDescription: "DetectAbsentHelmResources", ModuleName: absentResourcesEvent.ModuleName, }) - op.TaskQueues.GetMain().AddLast(newTask.WithQueuedAt(time.Now())) + op.engine.TaskQueues.GetMain().AddLast(newTask.WithQueuedAt(time.Now())) taskAddDescription := fmt.Sprintf("got %d absent module resources, append", len(absentResourcesEvent.Absent)) op.logTaskAdd(logEntry, taskAddDescription, newTask) } else { @@ -978,7 +1034,7 @@ func (op *AddonOperator) UpdateWaitInQueueMetric(t sh_task.Task) { } taskWaitTime := time.Since(t.GetQueuedAt()).Seconds() - op.MetricStorage.CounterAdd("{PREFIX}task_wait_in_queue_seconds_total", taskWaitTime, metricLabels) + op.engine.MetricStorage.CounterAdd("{PREFIX}task_wait_in_queue_seconds_total", taskWaitTime, metricLabels) } // HandleGlobalHookEnableKubernetesBindings add Synchronization tasks. @@ -1011,7 +1067,7 @@ func (op *AddonOperator) HandleGlobalHookEnableKubernetesBindings(t sh_task.Task } delete(taskLogLabels, "task.id") - kubernetesBindingID := uuid.NewV4().String() + kubernetesBindingID := uuid.Must(uuid.NewV4()).String() newTask := sh_task.NewTask(task.GlobalHookRun). WithLogLabels(taskLogLabels). WithQueueName(info.QueueName). @@ -1045,7 +1101,7 @@ func (op *AddonOperator) HandleGlobalHookEnableKubernetesBindings(t sh_task.Task if err != nil { hookLabel := path.Base(globalHook.Path) // TODO use separate metric, as in shell-operator? - op.MetricStorage.CounterAdd("{PREFIX}global_hook_errors_total", 1.0, map[string]string{ + op.engine.MetricStorage.CounterAdd("{PREFIX}global_hook_errors_total", 1.0, map[string]string{ "hook": hookLabel, "binding": "GlobalEnableKubernetesBindings", "queue": t.GetQueueName(), @@ -1064,7 +1120,7 @@ func (op *AddonOperator) HandleGlobalHookEnableKubernetesBindings(t sh_task.Task // "Wait" tasks are queued first for _, tsk := range parallelSyncTasksToWait { - q := op.TaskQueues.GetByName(tsk.GetQueueName()) + q := op.engine.TaskQueues.GetByName(tsk.GetQueueName()) if q == nil { log.Errorf("Queue %s is not created while run GlobalHookEnableKubernetesBindings task!", tsk.GetQueueName()) } else { @@ -1077,7 +1133,7 @@ func (op *AddonOperator) HandleGlobalHookEnableKubernetesBindings(t sh_task.Task op.logTaskAdd(logEntry, "append", parallelSyncTasksToWait...) for _, tsk := range parallelSyncTasks { - q := op.TaskQueues.GetByName(tsk.GetQueueName()) + q := op.engine.TaskQueues.GetByName(tsk.GetQueueName()) if q == nil { log.Errorf("Queue %s is not created while run GlobalHookEnableKubernetesBindings task!", tsk.GetQueueName()) } else { @@ -1165,7 +1221,7 @@ func (op *AddonOperator) HandleModuleDelete(t sh_task.Task, labels map[string]st module.State.LastModuleErr = err if err != nil { - op.MetricStorage.CounterAdd("{PREFIX}module_delete_errors_total", 1.0, map[string]string{"module": hm.ModuleName}) + op.engine.MetricStorage.CounterAdd("{PREFIX}module_delete_errors_total", 1.0, map[string]string{"module": hm.ModuleName}) logEntry.Errorf("Module delete failed, requeue task to retry after delay. Failed count is %d. Error: %s", t.GetFailureCount()+1, err) t.UpdateFailureMessage(err.Error()) t.WithQueuedAt(time.Now()) @@ -1211,7 +1267,7 @@ func (op *AddonOperator) HandleModuleRun(t sh_task.Task, labels map[string]strin } defer measure.Duration(func(d time.Duration) { - op.MetricStorage.HistogramObserve("{PREFIX}module_run_seconds", d.Seconds(), metricLabels, nil) + op.engine.MetricStorage.HistogramObserve("{PREFIX}module_run_seconds", d.Seconds(), metricLabels, nil) })() var moduleRunErr error @@ -1278,7 +1334,7 @@ func (op *AddonOperator) HandleModuleRun(t sh_task.Task, labels map[string]strin } delete(taskLogLabels, "task.id") - kubernetesBindingID := uuid.NewV4().String() + kubernetesBindingID := uuid.Must(uuid.NewV4()).String() taskMeta := task.HookMetadata{ EventDescription: hm.EventDescription, ModuleName: hm.ModuleName, @@ -1316,7 +1372,7 @@ func (op *AddonOperator) HandleModuleRun(t sh_task.Task, labels map[string]strin } else { // Queue parallel tasks that should be waited. for _, tsk := range parallelSyncTasksToWait { - q := op.TaskQueues.GetByName(tsk.GetQueueName()) + q := op.engine.TaskQueues.GetByName(tsk.GetQueueName()) if q == nil { logEntry.Errorf("queue %s is not found while EnableKubernetesBindings task", tsk.GetQueueName()) } else { @@ -1329,7 +1385,7 @@ func (op *AddonOperator) HandleModuleRun(t sh_task.Task, labels map[string]strin // Queue regular parallel tasks. for _, tsk := range parallelSyncTasks { - q := op.TaskQueues.GetByName(tsk.GetQueueName()) + q := op.engine.TaskQueues.GetByName(tsk.GetQueueName()) if q == nil { logEntry.Errorf("queue %s is not found while EnableKubernetesBindings task", tsk.GetQueueName()) } else { @@ -1396,7 +1452,7 @@ func (op *AddonOperator) HandleModuleRun(t sh_task.Task, labels map[string]strin if moduleRunErr != nil { res.Status = queue.Fail logEntry.Errorf("ModuleRun failed in phase '%s'. Requeue task to retry after delay. Failed count is %d. Error: %s", module.State.Phase, t.GetFailureCount()+1, moduleRunErr) - op.MetricStorage.CounterAdd("{PREFIX}module_run_errors_total", 1.0, map[string]string{"module": hm.ModuleName}) + op.engine.MetricStorage.CounterAdd("{PREFIX}module_run_errors_total", 1.0, map[string]string{"module": hm.ModuleName}) t.UpdateFailureMessage(moduleRunErr.Error()) t.WithQueuedAt(time.Now()) } else { @@ -1454,7 +1510,7 @@ func (op *AddonOperator) HandleModuleHookRun(t sh_task.Task, labels map[string]s } defer measure.Duration(func(d time.Duration) { - op.MetricStorage.HistogramObserve("{PREFIX}module_hook_run_seconds", d.Seconds(), metricLabels, nil) + op.engine.MetricStorage.HistogramObserve("{PREFIX}module_hook_run_seconds", d.Seconds(), metricLabels, nil) })() shouldRunHook := true @@ -1475,7 +1531,7 @@ func (op *AddonOperator) HandleModuleHookRun(t sh_task.Task, labels map[string]s // Combine tasks in the queue and compact binding contexts for v1 hooks. if shouldRunHook && taskHook.Config.Version == "v1" { - combineResult := op.CombineBindingContextForHook(op.TaskQueues.GetByName(t.GetQueueName()), t, func(tsk sh_task.Task) bool { + combineResult := op.engine.CombineBindingContextForHook(op.engine.TaskQueues.GetByName(t.GetQueueName()), t, func(tsk sh_task.Task) bool { thm := task.HookMetadataAccessor(tsk) // Stop combine process when Synchronization tasks have different // values in WaitForSynchronization or ExecuteOnSynchronization fields. @@ -1579,7 +1635,7 @@ func (op *AddonOperator) HandleModuleHookRun(t sh_task.Task, labels map[string]s delete(logLabels, "watchEvent") // Do not add ModuleRun task if it is already queued. - hasTask := QueueHasPendingModuleRunTask(op.TaskQueues.GetMain(), hm.ModuleName) + hasTask := QueueHasPendingModuleRunTask(op.engine.TaskQueues.GetMain(), hm.ModuleName) if !hasTask { newTask := sh_task.NewTask(task.ModuleRun). WithLogLabels(logLabels). @@ -1590,7 +1646,7 @@ func (op *AddonOperator) HandleModuleHookRun(t sh_task.Task, labels map[string]s }) newTask.SetProp("triggered-by", triggeredBy) - op.TaskQueues.GetMain().AddLast(newTask.WithQueuedAt(time.Now())) + op.engine.TaskQueues.GetMain().AddLast(newTask.WithQueuedAt(time.Now())) op.logTaskAdd(logEntry, "module values are changed, append", newTask) } else { logEntry.WithField("task.flow", "noop").Infof("module values are changed, ModuleRun task already queued") @@ -1598,9 +1654,9 @@ func (op *AddonOperator) HandleModuleHookRun(t sh_task.Task, labels map[string]s } } - op.MetricStorage.CounterAdd("{PREFIX}module_hook_allowed_errors_total", allowed, metricLabels) - op.MetricStorage.CounterAdd("{PREFIX}module_hook_errors_total", errors, metricLabels) - op.MetricStorage.CounterAdd("{PREFIX}module_hook_success_total", success, metricLabels) + op.engine.MetricStorage.CounterAdd("{PREFIX}module_hook_allowed_errors_total", allowed, metricLabels) + op.engine.MetricStorage.CounterAdd("{PREFIX}module_hook_errors_total", errors, metricLabels) + op.engine.MetricStorage.CounterAdd("{PREFIX}module_hook_success_total", success, metricLabels) } if isSynchronization && res.Status == queue.Success { @@ -1641,7 +1697,7 @@ func (op *AddonOperator) HandleGlobalHookRun(t sh_task.Task, labels map[string]s } defer measure.Duration(func(d time.Duration) { - op.MetricStorage.HistogramObserve("{PREFIX}global_hook_run_seconds", d.Seconds(), metricLabels, nil) + op.engine.MetricStorage.HistogramObserve("{PREFIX}global_hook_run_seconds", d.Seconds(), metricLabels, nil) })() isSynchronization := hm.IsSynchronization() @@ -1663,7 +1719,7 @@ func (op *AddonOperator) HandleGlobalHookRun(t sh_task.Task, labels map[string]s if shouldRunHook && taskHook.Config.Version == "v1" { // Combine binding contexts in the queue. - combineResult := op.CombineBindingContextForHook(op.TaskQueues.GetByName(t.GetQueueName()), t, func(tsk sh_task.Task) bool { + combineResult := op.engine.CombineBindingContextForHook(op.engine.TaskQueues.GetByName(t.GetQueueName()), t, func(tsk sh_task.Task) bool { thm := task.HookMetadataAccessor(tsk) // Stop combine process when Synchronization tasks have different // values in WaitForSynchronization or ExecuteOnSynchronization fields. @@ -1808,7 +1864,7 @@ func (op *AddonOperator) HandleGlobalHookRun(t sh_task.Task, labels map[string]s // Reload all using "ConvergeModules" task. newTask := NewConvergeModulesTask(eventDescription, GlobalValuesChanged, logLabels) newTask.SetProp("triggered-by", triggeredBy) - op.TaskQueues.GetMain().AddLast(newTask) + op.engine.TaskQueues.GetMain().AddLast(newTask) op.logTaskAdd(logEntry, "global values are changed, append", newTask) } // TODO rethink helm monitors pause-resume. It is not working well with parallel hooks without locks. But locks will destroy parallelization. @@ -1817,9 +1873,9 @@ func (op *AddonOperator) HandleGlobalHookRun(t sh_task.Task, labels map[string]s //} } - op.MetricStorage.CounterAdd("{PREFIX}global_hook_allowed_errors_total", allowed, metricLabels) - op.MetricStorage.CounterAdd("{PREFIX}global_hook_errors_total", errors, metricLabels) - op.MetricStorage.CounterAdd("{PREFIX}global_hook_success_total", success, metricLabels) + op.engine.MetricStorage.CounterAdd("{PREFIX}global_hook_allowed_errors_total", allowed, metricLabels) + op.engine.MetricStorage.CounterAdd("{PREFIX}global_hook_errors_total", errors, metricLabels) + op.engine.MetricStorage.CounterAdd("{PREFIX}global_hook_success_total", success, metricLabels) } if isSynchronization && res.Status == queue.Success { @@ -1838,7 +1894,7 @@ func (op *AddonOperator) CreateReloadModulesTasks(moduleNames []string, logLabel newTasks := make([]sh_task.Task, 0, len(moduleNames)) queuedAt := time.Now() - queuedModuleNames := ModulesWithPendingModuleRun(op.TaskQueues.GetMain()) + queuedModuleNames := ModulesWithPendingModuleRun(op.engine.TaskQueues.GetMain()) // Add ModuleRun tasks to reload only specific modules. for _, moduleName := range moduleNames { @@ -1916,7 +1972,7 @@ func (op *AddonOperator) CreateConvergeModulesTasks(state *module_manager.Module // CheckConvergeStatus detects if converge process is started and // updates ConvergeState. It updates metrics on converge finish. func (op *AddonOperator) CheckConvergeStatus(t sh_task.Task) { - convergeTasks := ConvergeTasksInQueue(op.TaskQueues.GetMain()) + convergeTasks := ConvergeTasksInQueue(op.engine.TaskQueues.GetMain()) // Converge state is 'Started'. Update StartedAt and // Activation if the converge process is just started. @@ -1929,8 +1985,8 @@ func (op *AddonOperator) CheckConvergeStatus(t sh_task.Task) { // reset StartedAt and Activation if the converge process is just stopped. if convergeTasks == 0 && op.ConvergeState.StartedAt != 0 { convergeSeconds := time.Duration(time.Now().UnixNano() - op.ConvergeState.StartedAt).Seconds() - op.MetricStorage.CounterAdd("{PREFIX}convergence_seconds", convergeSeconds, map[string]string{"activation": op.ConvergeState.Activation}) - op.MetricStorage.CounterAdd("{PREFIX}convergence_total", 1.0, map[string]string{"activation": op.ConvergeState.Activation}) + op.engine.MetricStorage.CounterAdd("{PREFIX}convergence_seconds", convergeSeconds, map[string]string{"activation": op.ConvergeState.Activation}) + op.engine.MetricStorage.CounterAdd("{PREFIX}convergence_total", 1.0, map[string]string{"activation": op.ConvergeState.Activation}) op.ConvergeState.StartedAt = 0 op.ConvergeState.Activation = "" } @@ -1940,7 +1996,7 @@ func (op *AddonOperator) CheckConvergeStatus(t sh_task.Task) { // Report modules left to process. if convergeTasks > 0 && (t.GetType() == task.ModuleRun || t.GetType() == task.ModuleDelete) { - moduleTasks := ConvergeModulesInQueue(op.TaskQueues.GetMain()) + moduleTasks := ConvergeModulesInQueue(op.engine.TaskQueues.GetMain()) log.Infof("Converge modules in progress: %d modules left to process in queue 'main'", moduleTasks) } } @@ -1965,11 +2021,6 @@ func (op *AddonOperator) UpdateFirstConvergeStatus(convergeTasks int) { } } -func (op *AddonOperator) Shutdown() { - op.KubeConfigManager.Stop() - op.ShellOperator.Shutdown() -} - // taskDescriptionForTaskFlowLog returns a human friendly description of the task. func taskDescriptionForTaskFlowLog(tsk sh_task.Task, action string, phase string, status string) string { hm := task.HookMetadataAccessor(tsk) @@ -2139,7 +2190,7 @@ func (op *AddonOperator) OnFirstConvergeDone() { // Let's add a short pause so that the service gets online and we don't get a rejection from the validation webhook (timeout reason) time.Sleep(3 * time.Second) // wait for service to be resolved - err := op.ModuleManager.SyncModulesCR(op.KubeConfigManager.KubeClient) + err := op.ModuleManager.SyncModulesCR(op.KubeClient()) if err != nil { log.Errorf("Modules CR registration failed: %s", err) } @@ -2152,7 +2203,7 @@ func (op *AddonOperator) EmitModulesSync() { return } - err := op.ModuleManager.SyncModulesCR(op.KubeConfigManager.KubeClient) + err := op.ModuleManager.SyncModulesCR(op.KubeClient()) if err != nil { log.Errorf("Modules CR registration failed: %s", err) } diff --git a/pkg/addon-operator/operator_test.go b/pkg/addon-operator/operator_test.go index b7829721..cde8eb4a 100644 --- a/pkg/addon-operator/operator_test.go +++ b/pkg/addon-operator/operator_test.go @@ -21,11 +21,11 @@ import ( mockhelmresmgr "github.com/flant/addon-operator/pkg/helm_resources_manager/test/mock" . "github.com/flant/addon-operator/pkg/hook/types" "github.com/flant/addon-operator/pkg/kube_config_manager" + "github.com/flant/addon-operator/pkg/kube_config_manager/backend/configmap" "github.com/flant/addon-operator/pkg/module_manager" "github.com/flant/addon-operator/pkg/task" "github.com/flant/kube-client/fake" . "github.com/flant/shell-operator/pkg/hook/types" - shell_operator "github.com/flant/shell-operator/pkg/shell-operator" sh_task "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" file_utils "github.com/flant/shell-operator/pkg/utils/file" @@ -103,7 +103,7 @@ func assembleTestAddonOperator(t *testing.T, configPath string) (*AddonOperator, // Assemble AddonOperator. op := NewAddonOperator(context.Background()) - op.KubeClient = kubeClient + op.engine.KubeClient = kubeClient // Mock helm client for ModuleManager result.helmClient = &mockhelm.Client{} op.Helm = mockhelm.NewClientFactory(result.helmClient) @@ -111,15 +111,10 @@ func assembleTestAddonOperator(t *testing.T, configPath string) (*AddonOperator, result.helmResourcesManager = &mockhelmresmgr.MockHelmResourcesManager{} op.HelmResourcesManager = result.helmResourcesManager - shell_operator.SetupEventManagers(op.ShellOperator) + op.engine.SetupEventManagers() - kcfg := kube_config_manager.Config{ - Namespace: result.cmNamespace, - ConfigMapName: result.cmName, - KubeClient: op.KubeClient, - RuntimeConfig: nil, - } - manager := kube_config_manager.NewKubeConfigManager(op.ctx, &kcfg) + bk := configmap.New(nil, op.engine.KubeClient, result.cmNamespace, result.cmName) + manager := kube_config_manager.NewKubeConfigManager(op.ctx, bk, op.runtimeConfig) op.KubeConfigManager = manager dirs := module_manager.DirectoryConfig{ @@ -129,9 +124,9 @@ func assembleTestAddonOperator(t *testing.T, configPath string) (*AddonOperator, } deps := module_manager.ModuleManagerDependencies{ KubeObjectPatcher: nil, - KubeEventsManager: op.KubeEventsManager, + KubeEventsManager: op.engine.KubeEventsManager, KubeConfigManager: manager, - ScheduleManager: op.ScheduleManager, + ScheduleManager: op.engine.ScheduleManager, Helm: op.Helm, HelmResourcesManager: op.HelmResourcesManager, MetricStorage: nil, @@ -154,7 +149,7 @@ func convergeDone(op *AddonOperator) func(g Gomega) bool { if op.IsStartupConvergeDone() { return true } - mainQueue := op.TaskQueues.GetMain() + mainQueue := op.engine.TaskQueues.GetMain() g.Expect(func() bool { if mainQueue.IsEmpty() { return true @@ -173,7 +168,7 @@ func Test_Operator_startup_tasks(t *testing.T) { op, _ := assembleTestAddonOperator(t, "startup_tasks") - op.BootstrapMainQueue(op.TaskQueues) + op.BootstrapMainQueue(op.engine.TaskQueues) expectTasks := []struct { taskType sh_task.TaskType @@ -197,7 +192,7 @@ func Test_Operator_startup_tasks(t *testing.T) { } i := 0 - op.TaskQueues.GetMain().Iterate(func(tsk sh_task.Task) { + op.engine.TaskQueues.GetMain().Iterate(func(tsk sh_task.Task) { // Stop checking if no expects left. if i >= len(expectTasks) { return @@ -221,7 +216,8 @@ func Test_Operator_ConvergeModules_main_queue_only(t *testing.T) { log.SetLevel(log.ErrorLevel) op, res := assembleTestAddonOperator(t, "converge__main_queue_only") - op.BootstrapMainQueue(op.TaskQueues) + + op.BootstrapMainQueue(op.engine.TaskQueues) // Fill mocked helm with two releases: one to purge and one to disable during converge process. moduleToPurge := "moduleToPurge" @@ -238,7 +234,7 @@ func Test_Operator_ConvergeModules_main_queue_only(t *testing.T) { } taskHandleHistory := make([]taskInfo, 0) - op.TaskQueues.GetMain().WithHandler(func(tsk sh_task.Task) queue.TaskResult { + op.engine.TaskQueues.GetMain().WithHandler(func(tsk sh_task.Task) queue.TaskResult { // Put task info to history. hm := task.HookMetadataAccessor(tsk) phase := "" @@ -260,7 +256,7 @@ func Test_Operator_ConvergeModules_main_queue_only(t *testing.T) { return op.TaskHandler(tsk) }) - op.TaskQueues.StartMain() + op.engine.TaskQueues.StartMain() // Wait until converge is done. g.Eventually(convergeDone(op), "30s", "200ms").Should(BeTrue()) @@ -355,7 +351,7 @@ func Test_HandleConvergeModules_global_changed_during_converge(t *testing.T) { op, res := assembleTestAddonOperator(t, "converge__main_queue_only") // Prefill main queue and start required managers. - op.BootstrapMainQueue(op.TaskQueues) + op.BootstrapMainQueue(op.engine.TaskQueues) op.KubeConfigManager.Start() op.ModuleManager.Start() @@ -377,7 +373,7 @@ func Test_HandleConvergeModules_global_changed_during_converge(t *testing.T) { historyMu := new(sync.Mutex) taskHandleHistory := make([]taskInfo, 0) - op.TaskQueues.GetMain().WithHandler(func(tsk sh_task.Task) queue.TaskResult { + op.engine.TaskQueues.GetMain().WithHandler(func(tsk sh_task.Task) queue.TaskResult { // Put task info to history. hm := task.HookMetadataAccessor(tsk) phase := "" @@ -410,7 +406,7 @@ func Test_HandleConvergeModules_global_changed_during_converge(t *testing.T) { }) // Start 'main' queue and wait for first converge. - op.TaskQueues.StartMain() + op.engine.TaskQueues.StartMain() // Emulate changing ConfigMap during converge. go func() { @@ -420,7 +416,7 @@ func Test_HandleConvergeModules_global_changed_during_converge(t *testing.T) { "path": "/data/global", "value": "param: newValue"}]` - cmPatched, err := op.KubeClient.CoreV1().ConfigMaps(res.cmNamespace).Patch(context.TODO(), + cmPatched, err := op.engine.KubeClient.CoreV1().ConfigMaps(res.cmNamespace).Patch(context.TODO(), res.cmName, k8types.JSONPatchType, []byte(globalValuesChangePatch), @@ -465,7 +461,7 @@ func Test_HandleConvergeModules_global_changed(t *testing.T) { op, res := assembleTestAddonOperator(t, "converge__main_queue_only") - op.BootstrapMainQueue(op.TaskQueues) + op.BootstrapMainQueue(op.engine.TaskQueues) op.KubeConfigManager.Start() op.ModuleManager.Start() @@ -482,7 +478,7 @@ func Test_HandleConvergeModules_global_changed(t *testing.T) { historyMu := new(sync.Mutex) taskHandleHistory := make([]taskInfo, 0) - op.TaskQueues.GetMain().WithHandler(func(tsk sh_task.Task) queue.TaskResult { + op.engine.TaskQueues.GetMain().WithHandler(func(tsk sh_task.Task) queue.TaskResult { // Put task info to history. hm := task.HookMetadataAccessor(tsk) phase := "" @@ -509,7 +505,7 @@ func Test_HandleConvergeModules_global_changed(t *testing.T) { return op.TaskHandler(tsk) }) - op.TaskQueues.StartMain() + op.engine.TaskQueues.StartMain() g.Eventually(convergeDone(op), "30s", "200ms").Should(BeTrue()) @@ -523,7 +519,7 @@ func Test_HandleConvergeModules_global_changed(t *testing.T) { "path": "/data/global", "value": "param: newValue"}]` - cmPatched, err := op.KubeClient.CoreV1().ConfigMaps(res.cmNamespace).Patch(context.TODO(), + cmPatched, err := op.engine.KubeClient.CoreV1().ConfigMaps(res.cmNamespace).Patch(context.TODO(), res.cmName, k8types.JSONPatchType, []byte(globalValuesChangePatch), @@ -587,8 +583,8 @@ func Test_Operator_logTask(t *testing.T) { log.AddHook(logHook) op, _ := assembleTestAddonOperator(t, "log_task__wait_for_synchronization") - op.BootstrapMainQueue(op.TaskQueues) - op.TaskQueues.StartMain() + op.BootstrapMainQueue(op.engine.TaskQueues) + op.engine.TaskQueues.StartMain() op.CreateAndStartQueuesForGlobalHooks() // Wait until converge is done. diff --git a/pkg/app/app.go b/pkg/app/app.go index 1faa5501..47fdd876 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -14,8 +14,8 @@ var ( AppDescription = "" Version = "dev" - DefaultListenAddress = "0.0.0.0" - DefaultListenPort = "9650" + ListenAddress = "0.0.0.0" + ListenPort = "9650" DefaultPrometheusMetricsPrefix = "addon_operator_" @@ -76,21 +76,16 @@ func DefineStartCommandFlags(kpApp *kingpin.Application, cmd *kingpin.CmdClause) cmd.Flag("prometheus-listen-address", "Address to use to serve metrics to Prometheus."). Envar("ADDON_OPERATOR_LISTEN_ADDRESS"). - Default(DefaultListenAddress). - StringVar(&sh_app.ListenAddress) + Default(ListenAddress). + StringVar(&ListenAddress) cmd.Flag("prometheus-listen-port", "Port to use to serve metrics to Prometheus."). Envar("ADDON_OPERATOR_LISTEN_PORT"). - Default(DefaultListenPort). - StringVar(&sh_app.ListenPort) + Default(ListenPort). + StringVar(&ListenPort) cmd.Flag("prometheus-metrics-prefix", "Prefix for Prometheus metrics."). Envar("ADDON_OPERATOR_PROMETHEUS_METRICS_PREFIX"). Default(DefaultPrometheusMetricsPrefix). StringVar(&sh_app.PrometheusMetricsPrefix) - cmd.Flag("hook-metrics-listen-port", "Port to use to serve hooks’ custom metrics to Prometheus. Can be set with $ADDON_OPERATOR_HOOK_METRICS_LISTEN_PORT. Equal to prometheus-listen-port if empty."). - Envar("ADDON_OPERATOR_HOOK_METRICS_LISTEN_PORT"). - Default(""). - StringVar(&sh_app.HookMetricsListenPort) - cmd.Flag("helm-history-max", "Helm: limit the maximum number of revisions saved per release. Use 0 for no limit."). Envar("HELM_HISTORY_MAX"). Default(strconv.Itoa(int(Helm3HistoryMax))). diff --git a/pkg/app/debug-cmd.go b/pkg/app/debug-cmd.go index 33d1fae9..8a306e65 100644 --- a/pkg/app/debug-cmd.go +++ b/pkg/app/debug-cmd.go @@ -13,90 +13,82 @@ var outputFormat = "text" func DefineDebugCommands(kpApp *kingpin.Application) { globalCmd := sh_app.CommandWithDefaultUsageTemplate(kpApp, "global", "manage global values") + globalCmd.Flag("output", "Output format: json|yaml.").Short('o'). + Default("yaml"). + EnumVar(&outputFormat, "json", "yaml", "text") + sh_app.DefineDebugUnixSocketFlag(globalCmd) + // -o json|yaml|text - globalListCmd := globalCmd.Command("list", "List global hooks."). + globalCmd.Command("list", "List global hooks."). Action(func(c *kingpin.ParseContext) error { - dump, err := Global(sh_debug.DefaultClient()).List(outputFormat) + dump, err := globalRequest(sh_debug.DefaultClient()).List(outputFormat) if err != nil { return err } fmt.Println(string(dump)) return nil }) - // -o json|yaml and --debug-unix-socket - AddOutputJsonYamlFlag(globalListCmd) - sh_app.DefineDebugUnixSocketFlag(globalListCmd) - globalValuesCmd := globalCmd.Command("values", "Dump current global values."). + globalCmd.Command("values", "Dump current global values."). Action(func(c *kingpin.ParseContext) error { - dump, err := Global(sh_debug.DefaultClient()).Values(outputFormat) + dump, err := globalRequest(sh_debug.DefaultClient()).Values(outputFormat) if err != nil { return err } fmt.Println(string(dump)) return nil }) - // -o json|yaml and --debug-unix-socket - AddOutputJsonYamlFlag(globalValuesCmd) - sh_app.DefineDebugUnixSocketFlag(globalValuesCmd) - globalConfigCmd := globalCmd.Command("config", "Dump global config values."). + globalCmd.Command("config", "Dump global config values."). Action(func(c *kingpin.ParseContext) error { - dump, err := Global(sh_debug.DefaultClient()).Config(outputFormat) + dump, err := globalRequest(sh_debug.DefaultClient()).Config(outputFormat) if err != nil { return err } fmt.Println(string(dump)) return nil }) - // -o json|yaml and --debug-unix-socket - AddOutputJsonYamlFlag(globalConfigCmd) - sh_app.DefineDebugUnixSocketFlag(globalConfigCmd) - globalPatchesCmd := globalCmd.Command("patches", "Dump global value patches."). + globalCmd.Command("patches", "Dump global value patches."). Action(func(c *kingpin.ParseContext) error { - dump, err := Global(sh_debug.DefaultClient()).Patches() + dump, err := globalRequest(sh_debug.DefaultClient()).Patches() if err != nil { return err } fmt.Println(string(dump)) return nil }) - // --debug-unix-socket - sh_app.DefineDebugUnixSocketFlag(globalPatchesCmd) - globalSnapshotsCmd := globalCmd.Command("snapshots", "Dump snapshots for all global hooks."). + globalCmd.Command("snapshots", "Dump snapshots for all global hooks."). Action(func(c *kingpin.ParseContext) error { - out, err := Global(sh_debug.DefaultClient()).Snapshots(outputFormat) + out, err := globalRequest(sh_debug.DefaultClient()).Snapshots(outputFormat) if err != nil { return err } fmt.Println(string(out)) return nil }) - // -o json|yaml and --debug-unix-socket - AddOutputJsonYamlFlag(globalSnapshotsCmd) - sh_app.DefineDebugUnixSocketFlag(globalSnapshotsCmd) moduleCmd := sh_app.CommandWithDefaultUsageTemplate(kpApp, "module", "List modules and dump their values") + moduleCmd.Flag("output", "Output format: json|yaml.").Short('o'). + Default("yaml"). + EnumVar(&outputFormat, "json", "yaml", "text") + sh_app.DefineDebugUnixSocketFlag(moduleCmd) - moduleListCmd := moduleCmd.Command("list", "List available modules and their enabled status."). + moduleCmd.Command("list", "List available modules and their enabled status."). Action(func(c *kingpin.ParseContext) error { - modules, err := Module(sh_debug.DefaultClient()).List(outputFormat) + modules, err := moduleRequest(sh_debug.DefaultClient()).List(outputFormat) if err != nil { return err } fmt.Println(string(modules)) return nil }) - // -o json|yaml|text and --debug-unix-socket - sh_debug.AddOutputJsonYamlTextFlag(moduleListCmd) - sh_app.DefineDebugUnixSocketFlag(moduleListCmd) var moduleName string moduleValuesCmd := moduleCmd.Command("values", "Dump module values by name."). Action(func(c *kingpin.ParseContext) error { - dump, err := Module(sh_debug.DefaultClient()).Name(moduleName).Values(outputFormat) + dump, err := moduleRequest(sh_debug.DefaultClient()).Name(moduleName).Values(outputFormat) if err != nil { return err } @@ -104,14 +96,11 @@ func DefineDebugCommands(kpApp *kingpin.Application) { return nil }) moduleValuesCmd.Arg("module_name", "").Required().StringVar(&moduleName) - // -o json|yaml and --debug-unix-socket - AddOutputJsonYamlFlag(moduleValuesCmd) - sh_app.DefineDebugUnixSocketFlag(moduleValuesCmd) var debug bool moduleRenderCmd := moduleCmd.Command("render", "Render module manifests."). Action(func(c *kingpin.ParseContext) error { - dump, err := Module(sh_debug.DefaultClient()).Name(moduleName).Render(debug) + dump, err := moduleRequest(sh_debug.DefaultClient()).Name(moduleName).Render(debug) if err != nil { return err } @@ -120,12 +109,10 @@ func DefineDebugCommands(kpApp *kingpin.Application) { }) moduleRenderCmd.Arg("module_name", "").Required().StringVar(&moduleName) moduleRenderCmd.Flag("debug", "enable debug mode").Default("false").BoolVar(&debug) - AddOutputJsonYamlFlag(moduleRenderCmd) - sh_app.DefineDebugUnixSocketFlag(moduleRenderCmd) moduleConfigCmd := moduleCmd.Command("config", "Dump module config values by name."). Action(func(c *kingpin.ParseContext) error { - dump, err := Module(sh_debug.DefaultClient()).Name(moduleName).Config(outputFormat) + dump, err := moduleRequest(sh_debug.DefaultClient()).Name(moduleName).Config(outputFormat) if err != nil { return err } @@ -133,13 +120,10 @@ func DefineDebugCommands(kpApp *kingpin.Application) { return nil }) moduleConfigCmd.Arg("module_name", "").Required().StringVar(&moduleName) - // -o json|yaml and --debug-unix-socket - AddOutputJsonYamlFlag(moduleConfigCmd) - sh_app.DefineDebugUnixSocketFlag(moduleConfigCmd) modulePatchesCmd := moduleCmd.Command("patches", "Dump module value patches by name."). Action(func(c *kingpin.ParseContext) error { - dump, err := Module(sh_debug.DefaultClient()).Name(moduleName).Patches() + dump, err := moduleRequest(sh_debug.DefaultClient()).Name(moduleName).Patches() if err != nil { return err } @@ -147,12 +131,10 @@ func DefineDebugCommands(kpApp *kingpin.Application) { return nil }) modulePatchesCmd.Arg("module_name", "").Required().StringVar(&moduleName) - // --debug-unix-socket - sh_app.DefineDebugUnixSocketFlag(modulePatchesCmd) moduleResourceMonitorCmd := moduleCmd.Command("resource-monitor", "Dump resource monitors."). Action(func(c *kingpin.ParseContext) error { - out, err := Module(sh_debug.DefaultClient()).Name(moduleName).ResourceMonitor(outputFormat) + out, err := moduleRequest(sh_debug.DefaultClient()).Name(moduleName).ResourceMonitor(outputFormat) if err != nil { return err } @@ -160,13 +142,10 @@ func DefineDebugCommands(kpApp *kingpin.Application) { return nil }) moduleResourceMonitorCmd.Arg("module_name", "").StringVar(&moduleName) - // -o json|yaml and --debug-unix-socket - AddOutputJsonYamlFlag(moduleResourceMonitorCmd) - sh_app.DefineDebugUnixSocketFlag(moduleResourceMonitorCmd) moduleSnapshotsCmd := moduleCmd.Command("snapshots", "Dump snapshots for all hooks."). Action(func(c *kingpin.ParseContext) error { - out, err := Module(sh_debug.DefaultClient()).Name(moduleName).Snapshots(outputFormat) + out, err := moduleRequest(sh_debug.DefaultClient()).Name(moduleName).Snapshots(outputFormat) if err != nil { return err } @@ -174,94 +153,85 @@ func DefineDebugCommands(kpApp *kingpin.Application) { return nil }) moduleSnapshotsCmd.Arg("module_name", "").Required().StringVar(&moduleName) - // -o json|yaml and --debug-unix-socket - AddOutputJsonYamlFlag(moduleSnapshotsCmd) - sh_app.DefineDebugUnixSocketFlag(moduleSnapshotsCmd) -} - -func AddOutputJsonYamlFlag(cmd *kingpin.CmdClause) { - cmd.Flag("output", "Output format: json|yaml.").Short('o'). - Default("yaml"). - EnumVar(&outputFormat, "json", "yaml") } -type GlobalRequest struct { +type cliGlobalSectionRequest struct { client *sh_debug.Client } -func Global(client *sh_debug.Client) *GlobalRequest { - return &GlobalRequest{client: client} +func globalRequest(client *sh_debug.Client) *cliGlobalSectionRequest { + return &cliGlobalSectionRequest{client: client} } -func (gr *GlobalRequest) List(format string) ([]byte, error) { +func (gr *cliGlobalSectionRequest) List(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/global/list.%s", format) return gr.client.Get(url) } -func (gr *GlobalRequest) Values(format string) ([]byte, error) { +func (gr *cliGlobalSectionRequest) Values(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/global/values.%s", format) return gr.client.Get(url) } -func (gr *GlobalRequest) Config(format string) ([]byte, error) { +func (gr *cliGlobalSectionRequest) Config(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/global/config.%s", format) return gr.client.Get(url) } -func (gr *GlobalRequest) Patches() ([]byte, error) { +func (gr *cliGlobalSectionRequest) Patches() ([]byte, error) { return gr.client.Get("http://unix/global/patches.json") } -func (gr *GlobalRequest) Snapshots(format string) ([]byte, error) { +func (gr *cliGlobalSectionRequest) Snapshots(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/global/snapshots.%s", format) return gr.client.Get(url) } -type ModuleRequest struct { +type cliModuleSectionRequest struct { client *sh_debug.Client name string } -func Module(client *sh_debug.Client) *ModuleRequest { - return &ModuleRequest{client: client} +func moduleRequest(client *sh_debug.Client) *cliModuleSectionRequest { + return &cliModuleSectionRequest{client: client} } -func (mr *ModuleRequest) List(format string) ([]byte, error) { +func (mr *cliModuleSectionRequest) List(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/module/list.%s", format) return mr.client.Get(url) } -func (mr *ModuleRequest) ResourceMonitor(format string) ([]byte, error) { +func (mr *cliModuleSectionRequest) ResourceMonitor(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/module/resource-monitor.%s", format) return mr.client.Get(url) } -func (mr *ModuleRequest) Name(name string) *ModuleRequest { +func (mr *cliModuleSectionRequest) Name(name string) *cliModuleSectionRequest { mr.name = name return mr } -func (mr *ModuleRequest) Values(format string) ([]byte, error) { +func (mr *cliModuleSectionRequest) Values(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/module/%s/values.%s", mr.name, format) return mr.client.Get(url) } -func (mr *ModuleRequest) Render(debug bool) ([]byte, error) { +func (mr *cliModuleSectionRequest) Render(debug bool) ([]byte, error) { url := fmt.Sprintf("http://unix/module/%s/render?debug=%t", mr.name, debug) return mr.client.Get(url) } -func (mr *ModuleRequest) Patches() ([]byte, error) { +func (mr *cliModuleSectionRequest) Patches() ([]byte, error) { url := fmt.Sprintf("http://unix/module/%s/patches.json", mr.name) return mr.client.Get(url) } -func (mr *ModuleRequest) Config(format string) ([]byte, error) { +func (mr *cliModuleSectionRequest) Config(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/module/%s/config.%s", mr.name, format) return mr.client.Get(url) } -func (mr *ModuleRequest) Snapshots(format string) ([]byte, error) { +func (mr *cliModuleSectionRequest) Snapshots(format string) ([]byte, error) { url := fmt.Sprintf("http://unix/module/%s/snapshots.%s", mr.name, format) return mr.client.Get(url) } diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go index 2fde0172..6940f040 100644 --- a/pkg/helm/helm.go +++ b/pkg/helm/helm.go @@ -21,7 +21,7 @@ func (f *ClientFactory) NewClient(logLabels ...map[string]string) client.HelmCli return nil } -func InitHelmClientFactory(kubeClient klient.Client) (*ClientFactory, error) { +func InitHelmClientFactory(kubeClient *klient.Client) (*ClientFactory, error) { helmVersion, err := DetectHelmVersion() if err != nil { return nil, err diff --git a/pkg/helm/helm3/helm3.go b/pkg/helm/helm3/helm3.go index 544f07fe..cf43628a 100644 --- a/pkg/helm/helm3/helm3.go +++ b/pkg/helm/helm3/helm3.go @@ -27,7 +27,7 @@ type Helm3Options struct { Namespace string HistoryMax int32 Timeout time.Duration - KubeClient klient.Client + KubeClient *klient.Client } var Options *Helm3Options @@ -46,7 +46,7 @@ func Init(options *Helm3Options) error { } type Helm3Client struct { - KubeClient klient.Client + KubeClient *klient.Client LogEntry *log.Entry Namespace string } @@ -66,7 +66,7 @@ func NewClient(logLabels ...map[string]string) client.HelmClient { } } -func (h *Helm3Client) WithKubeClient(client klient.Client) { +func (h *Helm3Client) WithKubeClient(client *klient.Client) { h.KubeClient = client } diff --git a/pkg/helm/helm3lib/helm3lib.go b/pkg/helm/helm3lib/helm3lib.go index b54b78c1..daa5b303 100644 --- a/pkg/helm/helm3lib/helm3lib.go +++ b/pkg/helm/helm3lib/helm3lib.go @@ -37,7 +37,7 @@ func Init(opts *Options) { // LibClient use helm3 package as Go library. type LibClient struct { - KubeClient klient.Client + KubeClient *klient.Client LogEntry *log.Entry Namespace string } @@ -46,7 +46,7 @@ type Options struct { Namespace string HistoryMax int32 Timeout time.Duration - KubeClient klient.Client + KubeClient *klient.Client } var ( diff --git a/pkg/helm_resources_manager/helm_resources_manager.go b/pkg/helm_resources_manager/helm_resources_manager.go index 3654ac7f..0b88d4f6 100644 --- a/pkg/helm_resources_manager/helm_resources_manager.go +++ b/pkg/helm_resources_manager/helm_resources_manager.go @@ -12,7 +12,7 @@ import ( type HelmResourcesManager interface { WithContext(ctx context.Context) - WithKubeClient(client klient.Client) + WithKubeClient(client *klient.Client) WithDefaultNamespace(namespace string) Stop() StopMonitors() @@ -35,7 +35,7 @@ type helmResourcesManager struct { Namespace string - kubeClient klient.Client + kubeClient *klient.Client monitors map[string]*ResourcesMonitor @@ -51,7 +51,7 @@ func NewHelmResourcesManager() HelmResourcesManager { } } -func (hm *helmResourcesManager) WithKubeClient(client klient.Client) { +func (hm *helmResourcesManager) WithKubeClient(client *klient.Client) { hm.kubeClient = client } diff --git a/pkg/helm_resources_manager/resources_monitor.go b/pkg/helm_resources_manager/resources_monitor.go index 20cdbd65..a19240d6 100644 --- a/pkg/helm_resources_manager/resources_monitor.go +++ b/pkg/helm_resources_manager/resources_monitor.go @@ -28,7 +28,7 @@ type ResourcesMonitor struct { manifests []manifest.Manifest defaultNamespace string - kubeClient klient.Client + kubeClient *klient.Client logLabels map[string]string absentCb func(moduleName string, absent []manifest.Manifest, defaultNs string) @@ -52,7 +52,7 @@ func (r *ResourcesMonitor) Stop() { } } -func (r *ResourcesMonitor) WithKubeClient(client klient.Client) { +func (r *ResourcesMonitor) WithKubeClient(client *klient.Client) { r.kubeClient = client } diff --git a/pkg/helm_resources_manager/test/mock/mock.go b/pkg/helm_resources_manager/test/mock/mock.go index 3da4f82b..6778f161 100644 --- a/pkg/helm_resources_manager/test/mock/mock.go +++ b/pkg/helm_resources_manager/test/mock/mock.go @@ -15,7 +15,7 @@ type MockHelmResourcesManager struct { func (h *MockHelmResourcesManager) WithContext(_ context.Context) {} -func (h *MockHelmResourcesManager) WithKubeClient(_ klient.Client) {} +func (h *MockHelmResourcesManager) WithKubeClient(_ *klient.Client) {} func (h *MockHelmResourcesManager) WithDefaultNamespace(_ string) {} diff --git a/pkg/kube_config_manager/access_config_map.go b/pkg/kube_config_manager/access_config_map.go deleted file mode 100644 index 818d8317..00000000 --- a/pkg/kube_config_manager/access_config_map.go +++ /dev/null @@ -1,76 +0,0 @@ -package kube_config_manager - -import ( - "context" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/flant/addon-operator/pkg/utils" - klient "github.com/flant/kube-client/client" -) - -// ConfigMapGet gets the ConfigMap object from the cluster. -func ConfigMapGet(kubeClient klient.Client, namespace string, name string) (*v1.ConfigMap, error) { - obj, err := kubeClient.CoreV1(). - ConfigMaps(namespace). - Get(context.TODO(), name, metav1.GetOptions{}) - - if errors.IsNotFound(err) { - return nil, nil - } - if err != nil { - return nil, err - } - - return obj, err -} - -// ConfigMapUpdate gets the ConfigMap object from the cluster, -// call transformation callback and save modified object. -func ConfigMapUpdate(kubeClient klient.Client, namespace string, name string, transformFn func(*v1.ConfigMap) error) error { - var err error - - obj, err := ConfigMapGet(kubeClient, namespace, name) - if err != nil { - return nil - } - - isUpdate := true - if obj == nil { - obj = &v1.ConfigMap{} - obj.Name = name - isUpdate = false - } - - if obj.Data == nil { - obj.Data = make(map[string]string) - } - - err = transformFn(obj) - if err != nil { - return err - } - - if isUpdate { - _, err = kubeClient.CoreV1().ConfigMaps(namespace).Update(context.TODO(), obj, metav1.UpdateOptions{}) - } else { - _, err = kubeClient.CoreV1().ConfigMaps(namespace).Create(context.TODO(), obj, metav1.CreateOptions{}) - } - return err -} - -// ConfigMapMergeValues is a helper to use ConfigMapUpdate to save Values object in the ConfigMap. -func ConfigMapMergeValues(kubeClient klient.Client, namespace string, name string, values utils.Values) error { - cmData, err := values.AsConfigMapData() - if err != nil { - return err - } - return ConfigMapUpdate(kubeClient, namespace, name, func(obj *v1.ConfigMap) error { - for k, v := range cmData { - obj.Data[k] = v - } - return nil - }) -} diff --git a/pkg/kube_config_manager/backend/backend.go b/pkg/kube_config_manager/backend/backend.go new file mode 100644 index 00000000..93206dcc --- /dev/null +++ b/pkg/kube_config_manager/backend/backend.go @@ -0,0 +1,21 @@ +package backend + +import ( + "context" + + "github.com/flant/addon-operator/pkg/kube_config_manager/config" + "github.com/flant/addon-operator/pkg/utils" +) + +// ConfigHandler load and saves(optional) configuration for module +type ConfigHandler interface { + // StartInformer starts backend watcher which follows a resource, parse and send module/modules values for kube_config_manager + StartInformer(ctx context.Context, eventC chan config.Event) + + // LoadConfig loads initial modules config before starting the informer + LoadConfig(ctx context.Context) (*config.KubeConfig, error) + + // SaveConfigValues saves patches for modules in backend (if supported), overriding the configuration + // Deprecated: saving values in the values source is not recommended and shouldn't be used anymore + SaveConfigValues(ctx context.Context, key string, values utils.Values) ( /*checksum*/ string, error) +} diff --git a/pkg/kube_config_manager/backend/configmap/configmap.go b/pkg/kube_config_manager/backend/configmap/configmap.go new file mode 100644 index 00000000..f4fdcc53 --- /dev/null +++ b/pkg/kube_config_manager/backend/configmap/configmap.go @@ -0,0 +1,389 @@ +package configmap + +import ( + "context" + "fmt" + "strings" + "time" + + dlogger "github.com/distribution/distribution/v3/context" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + corev1 "k8s.io/client-go/informers/core/v1" + "k8s.io/client-go/tools/cache" + + "github.com/flant/addon-operator/pkg/kube_config_manager/config" + "github.com/flant/addon-operator/pkg/utils" + "github.com/flant/kube-client/client" +) + +// Backend implements ConfigMap backend for kube_config_manager +type Backend struct { + namespace string + name string + + logger dlogger.Logger + client *client.Client +} + +// New initializes backend for kube_config_manager based on ConfigMap with modules values +func New(logger dlogger.Logger, kubeClient *client.Client, namespace, name string) *Backend { + if logger == nil { + logger = log.WithField("operator.component", "ConfigHandler").WithField("backend", "configmap") + } + + backend := &Backend{ + logger: logger, + namespace: namespace, + name: name, + client: kubeClient, + } + + return backend +} + +// LoadConfig gets config from ConfigMap before starting informer. +// Set checksums for global section and modules. +func (b Backend) LoadConfig(ctx context.Context) (*config.KubeConfig, error) { + obj, err := b.getConfigMap(ctx) + if err != nil { + return nil, err + } + + if obj == nil { + b.logger.Infof("Initial config from ConfigMap/%s: resource is not found", b.name) + return nil, nil + } + + return parseConfigMapData(obj.Data) +} + +// SaveConfigValues saves patches in the ConfigMap +func (b Backend) SaveConfigValues(ctx context.Context, key string, values utils.Values) ( /*checksum*/ string, error) { + if key == utils.GlobalValuesKey { + return b.saveGlobalConfigValues(ctx, values) + } + + return b.saveModuleConfigValues(ctx, key, values) +} + +func (b Backend) saveGlobalConfigValues(ctx context.Context, values utils.Values) ( /*checksum*/ string, error) { + globalKubeConfig, err := config.ParseGlobalKubeConfigFromValues(values) + if err != nil { + return "", err + } + if globalKubeConfig == nil { + return "", nil + } + + if b.isDebugEnabled(ctx) { + b.logger.Infof("Save global values to ConfigMap/%s:\n%s", b.name, values.DebugString()) + } else { + b.logger.Infof("Save global values to ConfigMap/%s", b.name) + } + + err = b.mergeValues(ctx, globalKubeConfig.GetValues()) + + return globalKubeConfig.Checksum, err +} + +func (b Backend) isDebugEnabled(ctx context.Context) bool { + debug, ok := ctx.Value("kube-config-manager-debug").(bool) + if !ok { + return false + } + + return debug +} + +// saveModuleConfigValues updates module section in ConfigMap. +// It uses knownChecksums to prevent KubeConfigChanged event on self-update. +func (b Backend) saveModuleConfigValues(ctx context.Context, moduleName string, values utils.Values) ( /*checksum*/ string, error) { + moduleKubeConfig := config.ParseModuleKubeConfigFromValues(moduleName, values) + + if moduleKubeConfig == nil { + return "", nil + } + + if b.isDebugEnabled(ctx) { + b.logger.Infof("Save module '%s' values to ConfigMap/%s:\n%s", moduleName, b.name, values.DebugString()) + } else { + b.logger.Infof("Save module '%s' values to ConfigMap/%s", moduleName, b.name) + } + + err := b.mergeValues(ctx, moduleKubeConfig.GetValues()) + + return moduleKubeConfig.Checksum, err +} + +func (b Backend) getConfigMap(ctx context.Context) (*v1.ConfigMap, error) { + obj, err := b.client.CoreV1(). + ConfigMaps(b.namespace). + Get(ctx, b.name, metav1.GetOptions{}) + + if errors.IsNotFound(err) { + return nil, nil + } + if err != nil { + return nil, err + } + + return obj, err +} + +func parseConfigMapData(data map[string]string) (cfg *config.KubeConfig, err error) { + cfg = config.NewConfig() + // Parse values in global section. + cfg.Global, err = getGlobalKubeConfigFromConfigData(data) + if err != nil { + return nil, err + } + + moduleNames, err := getModulesNamesFromConfigData(data) + if err != nil { + return nil, err + } + + for moduleName := range moduleNames { + cfg.Modules[moduleName], err = extractModuleKubeConfig(moduleName, data) + if err != nil { + return nil, err + } + } + + return cfg, nil +} + +func getGlobalKubeConfigFromConfigData(configData map[string]string) (*config.GlobalKubeConfig, error) { + yamlData, hasKey := configData[utils.GlobalValuesKey] + if !hasKey { + return nil, nil + } + + values, err := utils.NewGlobalValues(yamlData) + if err != nil { + return nil, fmt.Errorf("ConfigMap: bad yaml at key '%s': %s:\n%s", utils.GlobalValuesKey, err, yamlData) + } + + checksum := values.Checksum() + + return &config.GlobalKubeConfig{ + Values: values, + Checksum: checksum, + }, nil +} + +// getModulesNamesFromConfigData returns all keys in kube config except global +// modNameEnabled keys are also handled +func getModulesNamesFromConfigData(configData map[string]string) (map[string]bool, error) { + res := make(map[string]bool) + + for key := range configData { + // Ignore global section. + if key == utils.GlobalValuesKey { + continue + } + + // Treat Enabled flags as module section. + key = strings.TrimSuffix(key, "Enabled") + + modName := utils.ModuleNameFromValuesKey(key) + + if utils.ModuleNameToValuesKey(modName) != key { + return nil, fmt.Errorf("bad module name '%s': should be camelCased", key) + } + res[modName] = true + } + + return res, nil +} + +// extractModuleKubeConfig returns ModuleKubeConfig with values loaded from ConfigMap +func extractModuleKubeConfig(moduleName string, configData map[string]string) (*config.ModuleKubeConfig, error) { + moduleConfig, err := fromConfigMapData(moduleName, configData) + if err != nil { + return nil, fmt.Errorf("bad yaml at key '%s': %s", utils.ModuleNameToValuesKey(moduleName), err) + } + // NOTE this should never happen because of GetModulesNamesFromConfigData + if moduleConfig == nil { + return nil, fmt.Errorf("possible bug!!! No section '%s' for module '%s'", utils.ModuleNameToValuesKey(moduleName), moduleName) + } + + return &config.ModuleKubeConfig{ + ModuleConfig: *moduleConfig, + Checksum: moduleConfig.Checksum(), + }, nil +} + +// fromConfigMapData loads module config from a structure with string keys and yaml string values (ConfigMap) +// +// Example: +// +// simpleModule: | +// +// param1: 10 +// param2: 120 +// +// simpleModuleEnabled: "true" +func fromConfigMapData(moduleName string, configData map[string]string) (*utils.ModuleConfig, error) { + mc := utils.NewModuleConfig(moduleName, nil) + // create Values with moduleNameKey and moduleEnabled keys + configValues := make(utils.Values) + + // if there is data for module, unmarshal it and put into configValues + valuesYaml, hasKey := configData[mc.ModuleConfigKey()] + if hasKey { + var moduleValues interface{} + + err := yaml.Unmarshal([]byte(valuesYaml), &moduleValues) + if err != nil { + return nil, fmt.Errorf("unmarshal yaml data in a module config key '%s': %v", mc.ModuleConfigKey(), err) + } + + configValues[mc.ModuleConfigKey()] = moduleValues + } + + // if there is enabled key, treat it as boolean + enabledString, hasKey := configData[mc.ModuleEnabledKey()] + if hasKey { + var enabled bool + + switch enabledString { + case "true": + enabled = true + case "false": + enabled = false + default: + return nil, fmt.Errorf("module enabled key '%s' should have a boolean value, got '%v'", mc.ModuleEnabledKey(), enabledString) + } + + configValues[mc.ModuleEnabledKey()] = enabled + } + + if len(configValues) == 0 { + return mc, nil + } + + return mc.LoadFromValues(configValues) +} + +func (b Backend) mergeValues(ctx context.Context, values utils.Values) error { + cmData, err := values.AsConfigMapData() + if err != nil { + return err + } + return b.updateConfigMap(ctx, func(obj *v1.ConfigMap) error { + for k, v := range cmData { + obj.Data[k] = v + } + return nil + }) +} + +func (b Backend) updateConfigMap(ctx context.Context, transformFn func(*v1.ConfigMap) error) error { + var err error + + obj, err := b.getConfigMap(ctx) + if err != nil { + return nil + } + + isUpdate := true + if obj == nil { + obj = &v1.ConfigMap{} + obj.Name = b.name + isUpdate = false + } + + if obj.Data == nil { + obj.Data = make(map[string]string) + } + + err = transformFn(obj) + if err != nil { + return err + } + + if isUpdate { + _, err = b.client.CoreV1().ConfigMaps(b.namespace).Update(ctx, obj, metav1.UpdateOptions{}) + } else { + _, err = b.client.CoreV1().ConfigMaps(b.namespace).Create(ctx, obj, metav1.CreateOptions{}) + } + return err +} + +func (b Backend) StartInformer(ctx context.Context, eventC chan config.Event) { + // define resyncPeriod for informer + resyncPeriod := time.Duration(5) * time.Minute + + // define indexers for informer + indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc} + + // define tweakListOptions for informer + tweakListOptions := func(options *metav1.ListOptions) { + options.FieldSelector = fields.OneTermEqualSelector("metadata.name", b.name).String() + } + + cmInformer := corev1.NewFilteredConfigMapInformer(b.client, b.namespace, resyncPeriod, indexers, tweakListOptions) + _, _ = cmInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + b.logConfigMapEvent(ctx, obj, "add") + err := b.handleConfigMapEvent(obj.(*v1.ConfigMap), eventC) + if err != nil { + b.logger.Errorf("Handle ConfigMap/%s 'add' error: %s", b.name, err) + } + }, + UpdateFunc: func(prevObj interface{}, obj interface{}) { + b.logConfigMapEvent(ctx, obj, "update") + err := b.handleConfigMapEvent(obj.(*v1.ConfigMap), eventC) + if err != nil { + b.logger.Errorf("Handle ConfigMap/%s 'update' error: %s", b.name, err) + } + }, + DeleteFunc: func(obj interface{}) { + b.logConfigMapEvent(ctx, obj, "delete") + _ = b.handleConfigMapEvent(nil, eventC) + }, + }) + + go func() { + cmInformer.Run(ctx.Done()) + }() +} + +func (b Backend) logConfigMapEvent(ctx context.Context, obj interface{}, eventName string) { + if !b.isDebugEnabled(ctx) { + return + } + + objYaml, err := yaml.Marshal(obj) + if err != nil { + b.logger.Infof("Dump ConfigMap/%s '%s' error: %s", b.name, eventName, err) + return + } + b.logger.Infof("Dump ConfigMap/%s '%s':\n%s", b.name, eventName, objYaml) +} + +func (b Backend) handleConfigMapEvent(obj *v1.ConfigMap, eventC chan config.Event) error { + // ConfigMap is deleted, reset cached config and fire event. + if obj == nil { + eventC <- config.Event{Key: ""} + return nil + } + + newConfig, err := parseConfigMapData(obj.Data) + if err != nil { + eventC <- config.Event{Key: "batch", Err: err} + // Do not update caches to detect changes on next update. + b.logger.Errorf("ConfigMap/%s invalid: %v", b.name, err) + return err + } + + eventC <- config.Event{Key: "batch", Config: newConfig} + + return nil +} diff --git a/pkg/kube_config_manager/config_test.go b/pkg/kube_config_manager/backend/configmap/configmap_test.go similarity index 82% rename from pkg/kube_config_manager/config_test.go rename to pkg/kube_config_manager/backend/configmap/configmap_test.go index 7c51dc97..b8aaab61 100644 --- a/pkg/kube_config_manager/config_test.go +++ b/pkg/kube_config_manager/backend/configmap/configmap_test.go @@ -1,4 +1,4 @@ -package kube_config_manager +package configmap import ( "testing" @@ -6,41 +6,28 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_ParseCM_Nil(t *testing.T) { - cfg, err := ParseConfigMapData(nil) - assert.NoError(t, err, "Should parse nil data correctly") - assert.NotNil(t, cfg, "Config should not be nil for nil data") - assert.Nil(t, cfg.Global, "No global config should present for nil data") - assert.NotNil(t, cfg.Modules, "Modules should not be nil for nil data") - assert.Len(t, cfg.Modules, 0, "No module configs should present for nil data") -} - -func Test_ParseCM_Empty(t *testing.T) { - cfg, err := ParseConfigMapData(map[string]string{}) - assert.NoError(t, err, "Should parse empty data correctly") - assert.NotNil(t, cfg, "Config should not be nil for empty data") - assert.Nil(t, cfg.Global, "No global config should present for empty data") - assert.NotNil(t, cfg.Modules, "Modules should not be nil for empty data") - assert.Len(t, cfg.Modules, 0, "No module configs should present for empty data") -} +func Test_ParseCM_Malformed_Data(t *testing.T) { + var err error -func Test_ParseCM_only_Global(t *testing.T) { - cfg, err := ParseConfigMapData(map[string]string{ - "global": ` + _, err = parseConfigMapData(map[string]string{ + "Malformed-section-name": ` param1: val1 param2: val2 `, }) - assert.NoError(t, err, "Should parse global only data correctly") - assert.NotNil(t, cfg, "Config should not be nil for global only data") - assert.NotNil(t, cfg.Global, "Global config should present for global only data") - assert.True(t, cfg.Global.Values.HasGlobal(), "Config should have global values for global only data") - assert.NotNil(t, cfg.Modules, "Modules should not be nil for global only data") - assert.Len(t, cfg.Modules, 0, "No module configs should present for global only data") + assert.Error(t, err, "Should parse malformed module name with error") + + _, err = parseConfigMapData(map[string]string{ + "invalidYAML": ` +param1: val1 + param2: val2 +`, + }) + assert.Error(t, err, "Should parse bad module values with error") } func Test_ParseCM_only_Modules(t *testing.T) { - cfg, err := ParseConfigMapData(map[string]string{ + cfg, err := parseConfigMapData(map[string]string{ "modOne": ` param1: val1 param2: val2 @@ -60,37 +47,50 @@ param2: val2 assert.Containsf(t, cfg.Modules, "mod-one", "Config for modOne should present for modules only data") mod := cfg.Modules["mod-one"] - assert.Equal(t, "modOneEnabled", mod.ModuleEnabledKey) - assert.Equal(t, "modOne", mod.ModuleConfigKey) + assert.Equal(t, "modOneEnabled", mod.ModuleEnabledKey()) + assert.Equal(t, "modOne", mod.ModuleConfigKey()) assert.Equal(t, "false", mod.GetEnabled()) assert.Containsf(t, cfg.Modules, "mod-two", "Config for modOne should present for modules only data") mod = cfg.Modules["mod-two"] - assert.Equal(t, "modTwoEnabled", mod.ModuleEnabledKey) - assert.Equal(t, "modTwo", mod.ModuleConfigKey) + assert.Equal(t, "modTwoEnabled", mod.ModuleEnabledKey()) + assert.Equal(t, "modTwo", mod.ModuleConfigKey()) assert.Equal(t, "n/d", mod.GetEnabled()) assert.Containsf(t, cfg.Modules, "mod-three", "Config for modOne should present for modules only data") mod = cfg.Modules["mod-three"] - assert.Equal(t, "modThreeEnabled", mod.ModuleEnabledKey) - assert.Equal(t, "modThree", mod.ModuleConfigKey) + assert.Equal(t, "modThreeEnabled", mod.ModuleEnabledKey()) + assert.Equal(t, "modThree", mod.ModuleConfigKey()) assert.Equal(t, "true", mod.GetEnabled()) } -func Test_ParseCM_Malformed_Data(t *testing.T) { - var err error - - _, err = ParseConfigMapData(map[string]string{ - "Malformed-section-name": ` +func Test_ParseCM_only_Global(t *testing.T) { + cfg, err := parseConfigMapData(map[string]string{ + "global": ` param1: val1 param2: val2 `, }) - assert.Error(t, err, "Should parse malformed module name with error") + assert.NoError(t, err, "Should parse global only data correctly") + assert.NotNil(t, cfg, "Config should not be nil for global only data") + assert.NotNil(t, cfg.Global, "Global config should present for global only data") + assert.True(t, cfg.Global.Values.HasGlobal(), "Config should have global values for global only data") + assert.NotNil(t, cfg.Modules, "Modules should not be nil for global only data") + assert.Len(t, cfg.Modules, 0, "No module configs should present for global only data") +} - _, err = ParseConfigMapData(map[string]string{ - "invalidYAML": ` -param1: val1 - param2: val2 -`, - }) - assert.Error(t, err, "Should parse bad module values with error") +func Test_ParseCM_Nil(t *testing.T) { + cfg, err := parseConfigMapData(nil) + assert.NoError(t, err, "Should parse nil data correctly") + assert.NotNil(t, cfg, "Config should not be nil for nil data") + assert.Nil(t, cfg.Global, "No global config should present for nil data") + assert.NotNil(t, cfg.Modules, "Modules should not be nil for nil data") + assert.Len(t, cfg.Modules, 0, "No module configs should present for nil data") +} + +func Test_ParseCM_Empty(t *testing.T) { + cfg, err := parseConfigMapData(map[string]string{}) + assert.NoError(t, err, "Should parse empty data correctly") + assert.NotNil(t, cfg, "Config should not be nil for empty data") + assert.Nil(t, cfg.Global, "No global config should present for empty data") + assert.NotNil(t, cfg.Modules, "Modules should not be nil for empty data") + assert.Len(t, cfg.Modules, 0, "No module configs should present for empty data") } diff --git a/pkg/kube_config_manager/config.go b/pkg/kube_config_manager/config.go deleted file mode 100644 index bfe792d0..00000000 --- a/pkg/kube_config_manager/config.go +++ /dev/null @@ -1,42 +0,0 @@ -package kube_config_manager - -type KubeConfig struct { - Global *GlobalKubeConfig - Modules map[string]*ModuleKubeConfig -} - -func NewConfig() *KubeConfig { - return &KubeConfig{ - Modules: make(map[string]*ModuleKubeConfig), - } -} - -type KubeConfigEvent string - -const ( - KubeConfigChanged KubeConfigEvent = "Changed" - KubeConfigInvalid KubeConfigEvent = "Invalid" -) - -func ParseConfigMapData(data map[string]string) (cfg *KubeConfig, err error) { - cfg = NewConfig() - // Parse values in global section. - cfg.Global, err = GetGlobalKubeConfigFromConfigData(data) - if err != nil { - return nil, err - } - - moduleNames, err := GetModulesNamesFromConfigData(data) - if err != nil { - return nil, err - } - - for moduleName := range moduleNames { - cfg.Modules[moduleName], err = ExtractModuleKubeConfig(moduleName, data) - if err != nil { - return nil, err - } - } - - return cfg, nil -} diff --git a/pkg/kube_config_manager/config/config.go b/pkg/kube_config_manager/config/config.go new file mode 100644 index 00000000..45a64bb2 --- /dev/null +++ b/pkg/kube_config_manager/config/config.go @@ -0,0 +1,79 @@ +package config + +import ( + "github.com/flant/addon-operator/pkg/utils" +) + +type KubeConfig struct { + Global *GlobalKubeConfig + Modules map[string]*ModuleKubeConfig +} + +type GlobalKubeConfig struct { + Values utils.Values + Checksum string +} + +// GetValues returns global values, enrich them with top level key 'global' +/* TODO: since we have specified struct for global values, we don't need to encapsulate them into the map {"global": ... } +but we have to change this behavior somewhere in the module-manager */ +func (gkc GlobalKubeConfig) GetValues() utils.Values { + if len(gkc.Values) == 0 { + return gkc.Values + } + + if gkc.Values.HasKey("global") { + return gkc.Values + } + + return utils.Values{"global": gkc.Values} +} + +type ModuleKubeConfig struct { + utils.ModuleConfig + Checksum string +} + +func NewConfig() *KubeConfig { + return &KubeConfig{ + Modules: make(map[string]*ModuleKubeConfig), + } +} + +type KubeConfigEvent string + +const ( + KubeConfigChanged KubeConfigEvent = "Changed" + KubeConfigInvalid KubeConfigEvent = "Invalid" +) + +func ParseGlobalKubeConfigFromValues(values utils.Values) (*GlobalKubeConfig, error) { + if !values.HasGlobal() { + return nil, nil + } + + globalValues := values.Global() + + checksum := globalValues.Checksum() + + return &GlobalKubeConfig{ + Values: globalValues, + Checksum: checksum, + }, nil +} + +func ParseModuleKubeConfigFromValues(moduleName string, values utils.Values) *ModuleKubeConfig { + valuesKey := utils.ModuleNameToValuesKey(moduleName) + if !values.HasKey(valuesKey) { + return nil + } + + moduleValues := values.SectionByKey(valuesKey) + + checksum := moduleValues.Checksum() + + return &ModuleKubeConfig{ + ModuleConfig: *utils.NewModuleConfig(moduleName, moduleValues), + Checksum: checksum, + } +} diff --git a/pkg/kube_config_manager/config/event.go b/pkg/kube_config_manager/config/event.go new file mode 100644 index 00000000..e480c438 --- /dev/null +++ b/pkg/kube_config_manager/config/event.go @@ -0,0 +1,12 @@ +package config + +type Event struct { + // Key possible values + // "" - reset the whole config + // "batch" - set global and modules config at once + // "global" - set only global config + // " - set only config for the module + Key string + Config *KubeConfig + Err error +} diff --git a/pkg/kube_config_manager/global_kube_config.go b/pkg/kube_config_manager/global_kube_config.go deleted file mode 100644 index b9c9a6a1..00000000 --- a/pkg/kube_config_manager/global_kube_config.go +++ /dev/null @@ -1,60 +0,0 @@ -package kube_config_manager - -import ( - "fmt" - - "github.com/flant/addon-operator/pkg/utils" -) - -type GlobalKubeConfig struct { - Values utils.Values - Checksum string - ConfigData map[string]string -} - -func GetGlobalKubeConfigFromValues(values utils.Values) (*GlobalKubeConfig, error) { - if !values.HasGlobal() { - return nil, nil - } - - globalValues := values.Global() - - configData, err := globalValues.AsConfigMapData() - if err != nil { - return nil, fmt.Errorf("cannot dump yaml for global kube config: %s. Failed values data: %#v", err, globalValues.DebugString()) - } - - checksum, err := globalValues.Checksum() - if err != nil { - return nil, fmt.Errorf("global kube config checksum: %s", err) - } - - return &GlobalKubeConfig{ - Values: globalValues, - Checksum: checksum, - ConfigData: configData, - }, nil -} - -func GetGlobalKubeConfigFromConfigData(configData map[string]string) (*GlobalKubeConfig, error) { - yamlData, hasKey := configData[utils.GlobalValuesKey] - if !hasKey { - return nil, nil - } - - values, err := utils.NewGlobalValues(yamlData) - if err != nil { - return nil, fmt.Errorf("ConfigMap: bad yaml at key '%s': %s:\n%s", utils.GlobalValuesKey, err, yamlData) - } - - checksum, err := values.Checksum() - if err != nil { - return nil, fmt.Errorf("ConfigMap: global kube config checksum: %s", err) - } - - return &GlobalKubeConfig{ - ConfigData: map[string]string{utils.GlobalValuesKey: yamlData}, - Values: values, - Checksum: checksum, - }, nil -} diff --git a/pkg/kube_config_manager/kube_config_manager.go b/pkg/kube_config_manager/kube_config_manager.go index 75a12aaf..5ff9c45c 100644 --- a/pkg/kube_config_manager/kube_config_manager.go +++ b/pkg/kube_config_manager/kube_config_manager.go @@ -3,30 +3,18 @@ package kube_config_manager import ( "context" "fmt" + "reflect" "strconv" "sync" - "time" log "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - corev1 "k8s.io/client-go/informers/core/v1" - "k8s.io/client-go/tools/cache" + "github.com/flant/addon-operator/pkg/kube_config_manager/backend" + "github.com/flant/addon-operator/pkg/kube_config_manager/config" "github.com/flant/addon-operator/pkg/utils" - klient "github.com/flant/kube-client/client" - "github.com/flant/shell-operator/pkg/config" + runtimeConfig "github.com/flant/shell-operator/pkg/config" ) -type Config struct { - Namespace string - ConfigMapName string - KubeClient klient.Client - RuntimeConfig *config.Config -} - // KubeConfigManager watches for changes in ConfigMap/addon-operator and provides // methods to change its content. // It stores values parsed from ConfigMap data. OpenAPI validation of these config values @@ -35,166 +23,111 @@ type KubeConfigManager struct { ctx context.Context cancel context.CancelFunc - KubeClient klient.Client - Namespace string - ConfigMapName string + logEntry *log.Entry // Checksums to ignore self-initiated updates. knownChecksums *Checksums // Channel to emit events. - configEventCh chan KubeConfigEvent - - // Runtime config to enable logging all events from the ConfigMap at runtime. - runtimeConfig *config.Config - logConfigMapEvents bool - logEntry *log.Entry + configEventCh chan config.KubeConfigEvent + backend backend.ConfigHandler m sync.Mutex - currentConfig *KubeConfig + currentConfig *config.KubeConfig } -// kubeConfigManager should implement KubeConfigManager - -func NewKubeConfigManager(ctx context.Context, cfg *Config) *KubeConfigManager { +func NewKubeConfigManager(ctx context.Context, bk backend.ConfigHandler, runtimeConfig *runtimeConfig.Config) *KubeConfigManager { cctx, cancel := context.WithCancel(ctx) + logger := log.WithField("component", "KubeConfigManager") + logger.WithField("backend", reflect.TypeOf(bk)).Infof("Setup KubeConfigManager backend") + + // Runtime config to enable logging all events from the ConfigMap at runtime. + if runtimeConfig != nil { + runtimeConfig.Register( + "log.configmap.events", + fmt.Sprintf("Set to true to log all operations with Configuration manager/%s", reflect.TypeOf(bk)), + "false", + func(oldValue string, newValue string) error { + val, err := strconv.ParseBool(newValue) + if err != nil { + return err + } + //nolint: revive,staticcheck // basic type is enough here + cctx = context.WithValue(cctx, "kube-config-manager-debug", val) + return nil + }, + nil, + ) + } + return &KubeConfigManager{ ctx: cctx, cancel: cancel, - KubeClient: cfg.KubeClient, - Namespace: cfg.Namespace, - ConfigMapName: cfg.ConfigMapName, - runtimeConfig: cfg.RuntimeConfig, - - currentConfig: NewConfig(), + currentConfig: config.NewConfig(), knownChecksums: NewChecksums(), - configEventCh: make(chan KubeConfigEvent, 1), - logEntry: log.WithField("component", "KubeConfigManager"), + configEventCh: make(chan config.KubeConfigEvent, 1), + logEntry: logger, + backend: bk, } } -func (kcm *KubeConfigManager) SaveGlobalConfigValues(values utils.Values) error { - globalKubeConfig, err := GetGlobalKubeConfigFromValues(values) - if err != nil { - return err - } - if globalKubeConfig == nil { - return nil - } - - if kcm.logConfigMapEvents { - kcm.logEntry.Infof("Save global values to ConfigMap/%s:\n%s", kcm.ConfigMapName, values.DebugString()) - } else { - kcm.logEntry.Infof("Save global values to ConfigMap/%s", kcm.ConfigMapName) - } - - // Put checksum to known to ignore self-update. - kcm.withLock(func() { - kcm.knownChecksums.Add(utils.GlobalValuesKey, globalKubeConfig.Checksum) - }) +func (kcm *KubeConfigManager) Init() error { + kcm.logEntry.Debug("Init: KubeConfigManager") - err = ConfigMapMergeValues(kcm.KubeClient, kcm.Namespace, kcm.ConfigMapName, globalKubeConfig.Values) + // Load config and calculate checksums at start. No locking required. + err := kcm.loadConfig() if err != nil { - // Remove known checksum on error. - kcm.withLock(func() { - kcm.knownChecksums.Remove(utils.GlobalValuesKey, globalKubeConfig.Checksum) - }) return err } return nil } -// SaveModuleConfigValues updates module section in ConfigMap. +// SaveConfigValues updates `global` or `module` section in ConfigMap. // It uses knownChecksums to prevent KubeConfigChanged event on self-update. -func (kcm *KubeConfigManager) SaveModuleConfigValues(moduleName string, values utils.Values) error { - moduleKubeConfig, err := GetModuleKubeConfigFromValues(moduleName, values) +func (kcm *KubeConfigManager) SaveConfigValues(key string, values utils.Values) error { + checksum, err := kcm.backend.SaveConfigValues(kcm.ctx, key, values) if err != nil { - return err - } - if moduleKubeConfig == nil { - return nil - } + kcm.withLock(func() { + kcm.knownChecksums.Remove(key, checksum) + }) - if kcm.logConfigMapEvents { - kcm.logEntry.Infof("Save module '%s' values to ConfigMap/%s:\n%s", moduleName, kcm.ConfigMapName, values.DebugString()) - } else { - kcm.logEntry.Infof("Save module '%s' values to ConfigMap/%s", moduleName, kcm.ConfigMapName) + return err } - // Put checksum to known to ignore self-update. kcm.withLock(func() { - kcm.knownChecksums.Add(moduleName, moduleKubeConfig.Checksum) + kcm.knownChecksums.Add(key, checksum) }) - err = ConfigMapMergeValues(kcm.KubeClient, kcm.Namespace, kcm.ConfigMapName, moduleKubeConfig.Values) - if err != nil { - kcm.withLock(func() { - kcm.knownChecksums.Remove(moduleName, moduleKubeConfig.Checksum) - }) - return err - } - return nil } // KubeConfigEventCh return a channel that emits new KubeConfig on ConfigMap changes in global section or enabled modules. -func (kcm *KubeConfigManager) KubeConfigEventCh() chan KubeConfigEvent { +func (kcm *KubeConfigManager) KubeConfigEventCh() chan config.KubeConfigEvent { return kcm.configEventCh } // loadConfig gets config from ConfigMap before starting informer. // Set checksums for global section and modules. func (kcm *KubeConfigManager) loadConfig() error { - obj, err := ConfigMapGet(kcm.KubeClient, kcm.Namespace, kcm.ConfigMapName) + newConfig, err := kcm.backend.LoadConfig(kcm.ctx) if err != nil { return err } - if obj == nil { - kcm.logEntry.Infof("Initial config from ConfigMap/%s: resource is not found", kcm.ConfigMapName) - return nil + if newConfig.Global != nil { + kcm.knownChecksums.Set(utils.GlobalValuesKey, newConfig.Global.Checksum) } - newConfig, err := ParseConfigMapData(obj.Data) - if err != nil { - return err + for moduleName, moduleConfig := range newConfig.Modules { + kcm.knownChecksums.Set(moduleName, moduleConfig.Checksum) } kcm.currentConfig = newConfig return nil } -func (kcm *KubeConfigManager) Init() error { - kcm.logEntry.Debug("INIT: KUBE_CONFIG") - - if kcm.runtimeConfig != nil { - kcm.runtimeConfig.Register( - "log.configmap.events", - fmt.Sprintf("Set to true to log all operations with ConfigMap/%s", kcm.ConfigMapName), - "false", - func(oldValue string, newValue string) error { - val, err := strconv.ParseBool(newValue) - if err != nil { - return err - } - kcm.logConfigMapEvents = val - return nil - }, - nil, - ) - } - - // Load config and calculate checksums at start. No locking required. - err := kcm.loadConfig() - if err != nil { - return err - } - - return nil -} - // currentModuleNames gather modules names from the checksums map and from the currentConfig struct. func (kcm *KubeConfigManager) currentModuleNames() map[string]struct{} { names := make(map[string]struct{}) @@ -205,7 +138,7 @@ func (kcm *KubeConfigManager) currentModuleNames() map[string]struct{} { } // isGlobalChanged returns true when changes in "global" section requires firing event. -func (kcm *KubeConfigManager) isGlobalChanged(newConfig *KubeConfig) bool { +func (kcm *KubeConfigManager) isGlobalChanged(newConfig *config.KubeConfig) bool { if newConfig.Global == nil { // Fire event when global section is deleted: ConfigMap has no global section but global config is cached. // Note: no checksum checking here, "save" operations can't delete global section. @@ -240,26 +173,96 @@ func (kcm *KubeConfigManager) isGlobalChanged(newConfig *KubeConfig) bool { return false } -// handleNewCm determine changes in kube config. It sends KubeConfigChanged event if something -// changed or KubeConfigInvalid event if ConfigMap is incorrect. -func (kcm *KubeConfigManager) handleCmEvent(obj *v1.ConfigMap) error { - // ConfigMap is deleted, reset cached config and fire event. - if obj == nil { +// handleConfigEvent determine changes in kube config. It sends KubeConfigChanged event if something +// changed or KubeConfigInvalid event if Config is incorrect. +func (kcm *KubeConfigManager) handleConfigEvent(obj config.Event) { + if obj.Err != nil { + // Do not update caches to detect changes on next update. + kcm.configEventCh <- config.KubeConfigInvalid + kcm.logEntry.Errorf("Config/%s invalid: %v", obj.Key, obj.Err) + return + } + + switch obj.Key { + case "": + // Config backend was reset + kcm.m.Lock() + kcm.currentConfig = config.NewConfig() + kcm.m.Unlock() + kcm.configEventCh <- config.KubeConfigChanged + + case utils.GlobalValuesKey: + // global values + kcm.m.Lock() - kcm.currentConfig = NewConfig() + globalChanged := kcm.isGlobalChanged(obj.Config) + // Update state after successful parsing. + kcm.currentConfig.Global = obj.Config.Global + kcm.m.Unlock() + if globalChanged { + kcm.configEventCh <- config.KubeConfigChanged + } + + default: + // some module values + modulesChanged := false + + // module update + kcm.m.Lock() + moduleName := obj.Key + moduleCfg := obj.Config.Modules[obj.Key] + currentModuleNames := kcm.currentModuleNames() + _, exists := currentModuleNames[obj.Key] + if !exists { + kcm.logEntry.Infof("Module sections deleted: %+v", moduleName) + modulesChanged = true + } + // Module section is changed if new checksum not equal to saved one and not in known checksums. + if kcm.knownChecksums.HasEqualChecksum(moduleName, moduleCfg.Checksum) { + // Remove known checksum, do not fire event on self-update. + kcm.knownChecksums.Remove(moduleName, moduleCfg.Checksum) + } else { + if currModuleCfg, has := kcm.currentConfig.Modules[moduleName]; has { + if currModuleCfg.Checksum != moduleCfg.Checksum { + modulesChanged = true + kcm.logEntry.Infof("Module section '%s' changed. Enabled flag transition: %s--%s", + moduleName, + kcm.currentConfig.Modules[moduleName].GetEnabled(), + moduleCfg.GetEnabled(), + ) + } + } else { + modulesChanged = true + kcm.logEntry.Infof("Module section '%s' added. Enabled flag: %s", moduleName, moduleCfg.GetEnabled()) + } + } + + if modulesChanged { + kcm.currentConfig.Modules[obj.Key] = moduleCfg + kcm.configEventCh <- config.KubeConfigChanged + } kcm.m.Unlock() - kcm.configEventCh <- KubeConfigChanged - return nil } +} - newConfig, err := ParseConfigMapData(obj.Data) - if err != nil { +func (kcm *KubeConfigManager) handleBatchConfigEvent(obj config.Event) { + if obj.Err != nil { // Do not update caches to detect changes on next update. - kcm.configEventCh <- KubeConfigInvalid - kcm.logEntry.Errorf("ConfigMap/%s invalid: %v", kcm.ConfigMapName, err) - return err + kcm.configEventCh <- config.KubeConfigInvalid + kcm.logEntry.Errorf("Batch Config invalid: %v", obj.Err) + return + } + + if obj.Key == "" { + // Config backend was reset + kcm.m.Lock() + kcm.currentConfig = config.NewConfig() + kcm.m.Unlock() + kcm.configEventCh <- config.KubeConfigChanged } + newConfig := obj.Config + // Lock to read known checksums and update config. kcm.m.Lock() @@ -306,51 +309,35 @@ func (kcm *KubeConfigManager) handleCmEvent(obj *v1.ConfigMap) error { // Fire event if ConfigMap has changes. if globalChanged || modulesChanged { - kcm.configEventCh <- KubeConfigChanged + kcm.configEventCh <- config.KubeConfigChanged } - - return nil } func (kcm *KubeConfigManager) Start() { kcm.logEntry.Debugf("Start kube config manager") - // define resyncPeriod for informer - resyncPeriod := time.Duration(5) * time.Minute + go kcm.start() +} - // define indexers for informer - indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc} +func (kcm *KubeConfigManager) start() { + eventC := make(chan config.Event, 100) - // define tweakListOptions for informer - tweakListOptions := func(options *metav1.ListOptions) { - options.FieldSelector = fields.OneTermEqualSelector("metadata.name", kcm.ConfigMapName).String() - } + kcm.backend.StartInformer(kcm.ctx, eventC) - cmInformer := corev1.NewFilteredConfigMapInformer(kcm.KubeClient, kcm.Namespace, resyncPeriod, indexers, tweakListOptions) - cmInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - kcm.logConfigMapEvent(obj, "add") - err := kcm.handleCmEvent(obj.(*v1.ConfigMap)) - if err != nil { - kcm.logEntry.Errorf("Handle ConfigMap/%s 'add' error: %s", kcm.ConfigMapName, err) - } - }, - UpdateFunc: func(prevObj interface{}, obj interface{}) { - kcm.logConfigMapEvent(obj, "update") - err := kcm.handleCmEvent(obj.(*v1.ConfigMap)) - if err != nil { - kcm.logEntry.Errorf("Handle ConfigMap/%s 'update' error: %s", kcm.ConfigMapName, err) + for { + select { + case event := <-eventC: + if event.Key == "batch" { + kcm.handleBatchConfigEvent(event) + } else { + kcm.handleConfigEvent(event) } - }, - DeleteFunc: func(obj interface{}) { - kcm.logConfigMapEvent(obj, "delete") - _ = kcm.handleCmEvent(nil) - }, - }) - go func() { - cmInformer.Run(kcm.ctx.Done()) - }() + case <-kcm.ctx.Done(): + kcm.logEntry.Debugf("Stop kube config manager") + return + } + } } func (kcm *KubeConfigManager) Stop() { @@ -359,21 +346,8 @@ func (kcm *KubeConfigManager) Stop() { } } -func (kcm *KubeConfigManager) logConfigMapEvent(obj interface{}, eventName string) { - if !kcm.logConfigMapEvents { - return - } - - objYaml, err := yaml.Marshal(obj) - if err != nil { - kcm.logEntry.Infof("Dump ConfigMap/%s '%s' error: %s", kcm.ConfigMapName, eventName, err) - return - } - kcm.logEntry.Infof("Dump ConfigMap/%s '%s':\n%s", kcm.ConfigMapName, eventName, objYaml) -} - // SafeReadConfig locks currentConfig to safely read from it in external services. -func (kcm *KubeConfigManager) SafeReadConfig(handler func(config *KubeConfig)) { +func (kcm *KubeConfigManager) SafeReadConfig(handler func(config *config.KubeConfig)) { if handler == nil { return } diff --git a/pkg/kube_config_manager/kube_config_manager_test.go b/pkg/kube_config_manager/kube_config_manager_test.go index 8c104b43..5a4eb93d 100644 --- a/pkg/kube_config_manager/kube_config_manager_test.go +++ b/pkg/kube_config_manager/kube_config_manager_test.go @@ -11,6 +11,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "github.com/flant/addon-operator/pkg/kube_config_manager/backend/configmap" + "github.com/flant/addon-operator/pkg/kube_config_manager/config" "github.com/flant/addon-operator/pkg/utils" klient "github.com/flant/kube-client/client" ) @@ -19,7 +21,7 @@ const testConfigMapName = "test-addon-operator" // initKubeConfigManager returns an initialized KubeConfigManager instance. // Pass string or map to prefill ConfigMap. -func initKubeConfigManager(t *testing.T, kubeClient klient.Client, cmData map[string]string, cmContent string) *KubeConfigManager { +func initKubeConfigManager(t *testing.T, kubeClient *klient.Client, cmData map[string]string, cmContent string) *KubeConfigManager { g := NewWithT(t) cm := &v1.ConfigMap{} @@ -37,18 +39,13 @@ func initKubeConfigManager(t *testing.T, kubeClient klient.Client, cmData map[st _, err := kubeClient.CoreV1().ConfigMaps("default").Create(context.TODO(), cm, metav1.CreateOptions{}) g.Expect(err).ShouldNot(HaveOccurred(), "ConfigMap should be created") - kcfg := Config{ - Namespace: "default", - ConfigMapName: testConfigMapName, - KubeClient: kubeClient, - RuntimeConfig: nil, - } - kcm := NewKubeConfigManager(context.Background(), &kcfg) + bk := configmap.New(nil, kubeClient, "default", testConfigMapName) + kcm := NewKubeConfigManager(context.Background(), bk, nil) err = kcm.Init() g.Expect(err).ShouldNot(HaveOccurred(), "KubeConfigManager should init correctly") - go kcm.Start() + kcm.Start() return kcm } @@ -137,16 +134,16 @@ grafanaEnabled: "false" for name, expect := range tests { t.Run(name, func(t *testing.T) { if name == "global" { - kcm.SafeReadConfig(func(config *KubeConfig) { + kcm.SafeReadConfig(func(config *config.KubeConfig) { assert.Equal(t, expect.values, config.Global.Values) }) } else { - kcm.SafeReadConfig(func(config *KubeConfig) { + kcm.SafeReadConfig(func(config *config.KubeConfig) { // module moduleConfig, hasConfig := config.Modules[name] assert.True(t, hasConfig) assert.Equal(t, expect.isEnabled, moduleConfig.IsEnabled) - assert.Equal(t, expect.values, moduleConfig.Values) + assert.Equal(t, expect.values, moduleConfig.GetValues()) }) } }) @@ -246,12 +243,12 @@ func Test_KubeConfigManager_SaveValuesToConfigMap(t *testing.T) { } assert.Contains(t, cm.Data, utils.ModuleNameToValuesKey("mymodule"), "ConfigMap should contain a '%s' key", utils.ModuleNameToValuesKey("mymodule")) - mconf, err := ExtractModuleKubeConfig("mymodule", cm.Data) - if assert.NoError(t, err, "ModuleConfig should load") { - assert.Equal(t, *module, mconf.Values) - } else { - t.FailNow() - } + // mconf, err := ExtractModuleKubeConfig("mymodule", cm.Data) + // if assert.NoError(t, err, "ModuleConfig should load") { + // assert.Equal(t, *module, mconf.Values) + // } else { + // t.FailNow() + //} }, }, } @@ -259,12 +256,12 @@ func Test_KubeConfigManager_SaveValuesToConfigMap(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.globalValues != nil { - err = kcm.SaveGlobalConfigValues(*test.globalValues) + err = kcm.SaveConfigValues(utils.GlobalValuesKey, *test.globalValues) if !assert.NoError(t, err, "Global Values should be saved") { t.FailNow() } } else if test.moduleValues != nil { - err = kcm.SaveModuleConfigValues(test.moduleName, *test.moduleValues) + err = kcm.SaveConfigValues(test.moduleName, *test.moduleValues) if !assert.NoError(t, err, "Module Values should be saved") { t.FailNow() } @@ -300,7 +297,7 @@ param2: val2 defer kcm.Stop() // Check initial modules configs. - kcm.SafeReadConfig(func(config *KubeConfig) { + kcm.SafeReadConfig(func(config *config.KubeConfig) { g.Expect(config.Modules).To(HaveLen(0), "No modules section should be after Init()") }) @@ -320,7 +317,7 @@ param2: val2 // Wait for event. g.Eventually(kcm.KubeConfigEventCh(), "20s", "100ms").Should(Receive(), "KubeConfigManager should emit event") - kcm.SafeReadConfig(func(config *KubeConfig) { + kcm.SafeReadConfig(func(config *config.KubeConfig) { g.Expect(config.Modules).To(HaveLen(1), "Module section should appear after ConfigMap update") g.Expect(config.Modules).To(HaveKey("module-2"), "module-2 section should appear after ConfigMap update") }) @@ -341,7 +338,7 @@ param2: val2 // Wait for event. g.Eventually(kcm.KubeConfigEventCh(), "20s", "100ms").Should(Receive(), "KubeConfigManager should emit event") - kcm.SafeReadConfig(func(config *KubeConfig) { + kcm.SafeReadConfig(func(config *config.KubeConfig) { g.Expect(config.Global.Values).To(HaveKey("global"), "Should update global section cache") g.Expect(config.Global.Values["global"]).To(HaveKey("param1"), "Should update global section cache") }) @@ -370,7 +367,7 @@ moduleLongName: g.Expect(err).ShouldNot(HaveOccurred(), "values should load from bytes") g.Expect(modVals).To(HaveKey("moduleLongName")) - err = kcm.SaveModuleConfigValues("module-long-name", modVals) + err = kcm.SaveConfigValues("module-long-name", modVals) g.Expect(err).ShouldNot(HaveOccurred()) // Check that values are updated in ConfigMap @@ -406,7 +403,7 @@ func Test_KubeConfigManager_error_on_Init(t *testing.T) { // Wait for event ev := <-kcm.KubeConfigEventCh() - g.Expect(ev).To(Equal(KubeConfigInvalid), "Invalid name in module section should generate 'invalid' event") + g.Expect(ev).To(Equal(config.KubeConfigInvalid), "Invalid name in module section should generate 'invalid' event") // kcm.SafeReadConfig(func(config *KubeConfig) { // g.Expect(config.IsInvalid).To(Equal(true), "Current config should be invalid") @@ -430,13 +427,13 @@ func Test_KubeConfigManager_error_on_Init(t *testing.T) { // Wait for event ev = <-kcm.KubeConfigEventCh() - g.Expect(ev).To(Equal(KubeConfigChanged), "Valid section patch should generate 'changed' event") + g.Expect(ev).To(Equal(config.KubeConfigChanged), "Valid section patch should generate 'changed' event") - kcm.SafeReadConfig(func(config *KubeConfig) { + kcm.SafeReadConfig(func(config *config.KubeConfig) { // g.Expect(config.IsInvalid).To(Equal(false), "Current config should be valid") g.Expect(config.Modules).To(HaveLen(1), "Current config should have module sections") g.Expect(config.Modules).To(HaveKey("valid-module-name"), "Current config should have module section for 'valid-module-name'") - modValues := config.Modules["valid-module-name"].Values + modValues := config.Modules["valid-module-name"].GetValues() g.Expect(modValues.HasKey("validModuleName")).To(BeTrue()) m := modValues["validModuleName"] vals := m.(map[string]interface{}) diff --git a/pkg/kube_config_manager/module_kube_config.go b/pkg/kube_config_manager/module_kube_config.go deleted file mode 100644 index 8dc3bb60..00000000 --- a/pkg/kube_config_manager/module_kube_config.go +++ /dev/null @@ -1,91 +0,0 @@ -package kube_config_manager - -import ( - "fmt" - "strings" - - "github.com/flant/addon-operator/pkg/utils" -) - -// GetModulesNamesFromConfigData returns all keys in kube config except global -// modNameEnabled keys are also handled -func GetModulesNamesFromConfigData(configData map[string]string) (map[string]bool, error) { - res := make(map[string]bool) - - for key := range configData { - // Ignore global section. - if key == utils.GlobalValuesKey { - continue - } - - // Treat Enabled flags as module section. - key = strings.TrimSuffix(key, "Enabled") - - modName := utils.ModuleNameFromValuesKey(key) - - if utils.ModuleNameToValuesKey(modName) != key { - return nil, fmt.Errorf("bad module name '%s': should be camelCased", key) - } - res[modName] = true - } - - return res, nil -} - -type ModuleKubeConfig struct { - utils.ModuleConfig - Checksum string - ConfigData map[string]string -} - -func (m *ModuleKubeConfig) GetEnabled() string { - if m == nil { - return "" - } - return m.ModuleConfig.GetEnabled() -} - -func GetModuleKubeConfigFromValues(moduleName string, values utils.Values) (*ModuleKubeConfig, error) { - valuesKey := utils.ModuleNameToValuesKey(moduleName) - if !values.HasKey(valuesKey) { - return nil, nil - } - - moduleValues := values.SectionByKey(valuesKey) - - configData, err := moduleValues.AsConfigMapData() - if err != nil { - return nil, fmt.Errorf("cannot dump yaml for module '%s' kube config: %s. Failed values data: %s", moduleName, err, moduleValues.DebugString()) - } - - checksum, err := moduleValues.Checksum() - if err != nil { - return nil, fmt.Errorf("module '%s' kube config checksum: %s", moduleName, err) - } - - return &ModuleKubeConfig{ - ModuleConfig: utils.ModuleConfig{ - ModuleName: moduleName, - Values: moduleValues, - }, - ConfigData: configData, - Checksum: checksum, - }, nil -} - -// ExtractModuleKubeConfig returns ModuleKubeConfig with values loaded from ConfigMap -func ExtractModuleKubeConfig(moduleName string, configData map[string]string) (*ModuleKubeConfig, error) { - moduleConfig, err := utils.NewModuleConfig(moduleName).FromConfigMapData(configData) - if err != nil { - return nil, fmt.Errorf("bad yaml at key '%s': %s", utils.ModuleNameToValuesKey(moduleName), err) - } - // NOTE this should never happen because of GetModulesNamesFromConfigData - if moduleConfig == nil { - return nil, fmt.Errorf("possible bug!!! No section '%s' for module '%s'", utils.ModuleNameToValuesKey(moduleName), moduleName) - } - - return &ModuleKubeConfig{ - ModuleConfig: *moduleConfig, - Checksum: moduleConfig.Checksum(), - }, nil -} diff --git a/pkg/module_manager/global_hook.go b/pkg/module_manager/global_hook.go index 6df0ead8..1a671966 100644 --- a/pkg/module_manager/global_hook.go +++ b/pkg/module_manager/global_hook.go @@ -5,9 +5,9 @@ import ( "path/filepath" "strings" + uuid "github.com/gofrs/uuid/v5" "github.com/hashicorp/go-multierror" log "github.com/sirupsen/logrus" - uuid "gopkg.in/satori/go.uuid.v1" . "github.com/flant/addon-operator/pkg/hook/types" "github.com/flant/addon-operator/pkg/module_manager/go_hook" @@ -212,7 +212,7 @@ func (h *GlobalHook) Run(bindingType BindingType, bindingContext []BindingContex ) } - err := h.moduleManager.dependencies.KubeConfigManager.SaveGlobalConfigValues(configValuesPatchResult.Values) + err := h.moduleManager.dependencies.KubeConfigManager.SaveConfigValues(utils.GlobalValuesKey, configValuesPatchResult.Values) if err != nil { logEntry.Debugf("Global hook '%s' kube config global values stay unchanged:\n%s", h.Name, h.moduleManager.kubeGlobalConfigValues.DebugString()) return fmt.Errorf("global hook '%s': set kube config failed: %s", h.Name, err) @@ -327,7 +327,7 @@ func (h *GlobalHook) prepareConfigValuesJsonFile() (string, error) { return "", err } - path := filepath.Join(h.TmpDir, fmt.Sprintf("global-hook-%s-config-values-%s.json", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("global-hook-%s-config-values-%s.json", h.SafeName(), uuid.Must(uuid.NewV4()).String())) err = dumpData(path, data) if err != nil { return "", err @@ -353,7 +353,7 @@ func (h *GlobalHook) prepareValuesJsonFile() (filePath string, err error) { return "", err } - filePath = filepath.Join(h.TmpDir, fmt.Sprintf("global-hook-%s-values-%s.json", h.SafeName(), uuid.NewV4().String())) + filePath = filepath.Join(h.TmpDir, fmt.Sprintf("global-hook-%s-values-%s.json", h.SafeName(), uuid.Must(uuid.NewV4()).String())) err = dumpData(filePath, data) if err != nil { return "", err @@ -366,7 +366,7 @@ func (h *GlobalHook) prepareValuesJsonFile() (filePath string, err error) { // BINDING_CONTEXT_PATH func (h *GlobalHook) prepareBindingContextJsonFile(bindingContext []byte) (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("global-hook-%s-binding-context-%s.json", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("global-hook-%s-binding-context-%s.json", h.SafeName(), uuid.Must(uuid.NewV4()).String())) err := dumpData(path, bindingContext) if err != nil { return "", err @@ -380,7 +380,7 @@ func (h *GlobalHook) prepareBindingContextJsonFile(bindingContext []byte) (strin // CONFIG_VALUES_JSON_PATCH_PATH func (h *GlobalHook) prepareConfigValuesJsonPatchFile() (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.global-hook-config-values-%s.json-patch", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.global-hook-config-values-%s.json-patch", h.SafeName(), uuid.Must(uuid.NewV4()).String())) if err := CreateEmptyWritableFile(path); err != nil { return "", err } @@ -389,7 +389,7 @@ func (h *GlobalHook) prepareConfigValuesJsonPatchFile() (string, error) { // VALUES_JSON_PATCH_PATH func (h *GlobalHook) prepareValuesJsonPatchFile() (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.global-hook-values-%s.json-patch", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.global-hook-values-%s.json-patch", h.SafeName(), uuid.Must(uuid.NewV4()).String())) if err := CreateEmptyWritableFile(path); err != nil { return "", err } @@ -398,7 +398,7 @@ func (h *GlobalHook) prepareValuesJsonPatchFile() (string, error) { // METRICS_PATH func (h *GlobalHook) prepareMetricsFile() (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.global-hook-metrics-%s.json", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.global-hook-metrics-%s.json", h.SafeName(), uuid.Must(uuid.NewV4()).String())) if err := CreateEmptyWritableFile(path); err != nil { return "", err } @@ -407,7 +407,7 @@ func (h *GlobalHook) prepareMetricsFile() (string, error) { // KUBERNETES PATCH PATH func (h *GlobalHook) prepareKubernetesPatchFile() (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s-object-patch-%s", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s-object-patch-%s", h.SafeName(), uuid.Must(uuid.NewV4()).String())) if err := CreateEmptyWritableFile(path); err != nil { return "", err } diff --git a/pkg/module_manager/go_hook/go_hook.go b/pkg/module_manager/go_hook/go_hook.go index 50994b2b..b84440f8 100644 --- a/pkg/module_manager/go_hook/go_hook.go +++ b/pkg/module_manager/go_hook/go_hook.go @@ -59,8 +59,11 @@ type BindingAction struct { } type HookConfig struct { - Schedule []ScheduleConfig - Kubernetes []KubernetesConfig + Schedule []ScheduleConfig + Kubernetes []KubernetesConfig + // OnStartup runs hook on module/global startup + // Attention! During the startup you don't have snapshots available + // use native KubeClient to fetch resources OnStartup *OrderedConfig OnBeforeHelm *OrderedConfig OnAfterHelm *OrderedConfig diff --git a/pkg/module_manager/go_hook/metrics/collector.go b/pkg/module_manager/go_hook/metrics/collector.go index d10e17b8..3222f884 100644 --- a/pkg/module_manager/go_hook/metrics/collector.go +++ b/pkg/module_manager/go_hook/metrics/collector.go @@ -34,7 +34,7 @@ func (dms *MemoryMetricsCollector) Add(name string, value float64, labels map[st Name: name, Group: opts.group, Action: "add", - Value: pointer.Float64Ptr(value), + Value: pointer.Float64(value), Labels: labels, }) } @@ -51,7 +51,7 @@ func (dms *MemoryMetricsCollector) Set(name string, value float64, labels map[st Name: name, Group: opts.group, Action: "set", - Value: pointer.Float64Ptr(value), + Value: pointer.Float64(value), Labels: labels, }) } diff --git a/pkg/module_manager/module.go b/pkg/module_manager/module.go index 507fefe1..8d9e24a8 100644 --- a/pkg/module_manager/module.go +++ b/pkg/module_manager/module.go @@ -9,9 +9,9 @@ import ( "strings" "time" + uuid "github.com/gofrs/uuid/v5" "github.com/kennygrant/sanitize" log "github.com/sirupsen/logrus" - uuid "gopkg.in/satori/go.uuid.v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/yaml" @@ -454,10 +454,7 @@ func (m *Module) runHooksByBindingAndCheckValues(binding BindingType, logLabels if err != nil { return false, err } - valuesChecksum, err := values.Checksum() - if err != nil { - return false, err - } + valuesChecksum := values.Checksum() for _, moduleHookName := range moduleHooks { moduleHook := m.moduleManager.GetModuleHook(moduleHookName) @@ -503,10 +500,7 @@ func (m *Module) runHooksByBindingAndCheckValues(binding BindingType, logLabels if err != nil { return false, err } - newValuesChecksum, err := newValues.Checksum() - if err != nil { - return false, err - } + newValuesChecksum := newValues.Checksum() if newValuesChecksum != valuesChecksum { return true, nil @@ -522,7 +516,7 @@ func (m *Module) prepareConfigValuesJsonFile() (string, error) { return "", err } - path := filepath.Join(m.moduleManager.TempDir, fmt.Sprintf("%s.module-config-values-%s.json", m.SafeName(), uuid.NewV4().String())) + path := filepath.Join(m.moduleManager.TempDir, fmt.Sprintf("%s.module-config-values-%s.json", m.SafeName(), uuid.Must(uuid.NewV4()).String())) err = dumpData(path, data) if err != nil { return "", err @@ -545,7 +539,7 @@ func (m *Module) PrepareValuesYamlFile() (string, error) { return "", err } - path := filepath.Join(m.moduleManager.TempDir, fmt.Sprintf("%s.module-values.yaml-%s", m.SafeName(), uuid.NewV4().String())) + path := filepath.Join(m.moduleManager.TempDir, fmt.Sprintf("%s.module-values.yaml-%s", m.SafeName(), uuid.Must(uuid.NewV4()).String())) err = dumpData(path, data) if err != nil { return "", err @@ -563,7 +557,7 @@ func (m *Module) prepareValuesJsonFileWith(values utils.Values) (string, error) return "", err } - path := filepath.Join(m.moduleManager.TempDir, fmt.Sprintf("%s.module-values-%s.json", m.SafeName(), uuid.NewV4().String())) + path := filepath.Join(m.moduleManager.TempDir, fmt.Sprintf("%s.module-values-%s.json", m.SafeName(), uuid.Must(uuid.NewV4()).String())) err = dumpData(path, data) if err != nil { return "", err @@ -641,8 +635,8 @@ func (m *Module) StaticAndConfigValues() utils.Values { // Init module section. utils.Values{m.ValuesKey(): map[string]interface{}{}}, // Merge static values from various values.yaml files. - m.CommonStaticConfig.Values, - m.StaticConfig.Values, + m.CommonStaticConfig.GetValues(), + m.StaticConfig.GetValues(), // Apply config values defaults before ConfigMap overrides. &ApplyDefaultsForModule{ m.ValuesKey(), @@ -664,8 +658,8 @@ func (m *Module) StaticAndNewValues(newValues utils.Values) utils.Values { // Init module section. utils.Values{m.ValuesKey(): map[string]interface{}{}}, // Merge static values from various values.yaml files. - m.CommonStaticConfig.Values, - m.StaticConfig.Values, + m.CommonStaticConfig.GetValues(), + m.StaticConfig.GetValues(), // Apply config values defaults before overrides. &ApplyDefaultsForModule{ m.ValuesKey(), @@ -697,8 +691,8 @@ func (m *Module) Values() (utils.Values, error) { // Init module section. utils.Values{m.ValuesKey(): map[string]interface{}{}}, // Merge static values from various values.yaml files. - m.CommonStaticConfig.Values, - m.StaticConfig.Values, + m.CommonStaticConfig.GetValues(), + m.StaticConfig.GetValues(), // Apply config values defaults before ConfigMap overrides. &ApplyDefaultsForModule{ m.ValuesKey(), @@ -971,7 +965,7 @@ func (mm *ModuleManager) ValidateModule(module *Module) error { } // SyncModulesCR synchronize modules CR from current modules list to the cluster one -func (mm *ModuleManager) SyncModulesCR(client klient.Client) error { +func (mm *ModuleManager) SyncModulesCR(client *klient.Client) error { if mm.moduleProducer == nil { return nil } @@ -1036,16 +1030,16 @@ func (mm *ModuleManager) createModuleOperations(module *Module) (object_patch.Op // loadStaticValues loads config for module from values.yaml // Module is enabled if values.yaml is not exists. func (m *Module) loadStaticValues() (err error) { - m.CommonStaticConfig, err = utils.NewModuleConfig(m.Name).LoadFromValues(m.moduleManager.commonStaticValues) + m.CommonStaticConfig, err = utils.NewModuleConfig(m.Name, nil).LoadFromValues(m.moduleManager.commonStaticValues) if err != nil { return err } - log.Debugf("module %s static values in common file: %s", m.Name, m.CommonStaticConfig.Values.DebugString()) + log.Debugf("module %s static values in common file: %s", m.Name, m.CommonStaticConfig.GetValues().DebugString()) valuesYamlPath := filepath.Join(m.Path, ValuesFileName) if _, err := os.Stat(valuesYamlPath); os.IsNotExist(err) { - m.StaticConfig = utils.NewModuleConfig(m.Name) + m.StaticConfig = utils.NewModuleConfig(m.Name, nil) log.Debugf("module %s has no static values", m.Name) return nil } @@ -1055,11 +1049,11 @@ func (m *Module) loadStaticValues() (err error) { return fmt.Errorf("cannot read '%s': %s", m.Path, err) } - m.StaticConfig, err = utils.NewModuleConfig(m.Name).FromYaml(data) + m.StaticConfig, err = utils.NewModuleConfig(m.Name, nil).FromYaml(data) if err != nil { return err } - log.Debugf("module %s static values: %s", m.Name, m.StaticConfig.Values.DebugString()) + log.Debugf("module %s static values: %s", m.Name, m.StaticConfig.GetValues().DebugString()) return nil } diff --git a/pkg/module_manager/module_hook.go b/pkg/module_manager/module_hook.go index cb8ae2c2..737527bb 100644 --- a/pkg/module_manager/module_hook.go +++ b/pkg/module_manager/module_hook.go @@ -5,9 +5,9 @@ import ( "path/filepath" "strings" + uuid "github.com/gofrs/uuid/v5" "github.com/hashicorp/go-multierror" log "github.com/sirupsen/logrus" - uuid "gopkg.in/satori/go.uuid.v1" . "github.com/flant/addon-operator/pkg/hook/types" "github.com/flant/addon-operator/pkg/module_manager/go_hook" @@ -227,7 +227,7 @@ func (h *ModuleHook) Run(bindingType BindingType, context []BindingContext, logL ) } - err := h.moduleManager.dependencies.KubeConfigManager.SaveModuleConfigValues(moduleName, configValuesPatchResult.Values) + err := h.moduleManager.dependencies.KubeConfigManager.SaveConfigValues(moduleName, configValuesPatchResult.Values) if err != nil { logEntry.Debugf("Module hook '%s' kube module config values stay unchanged:\n%s", h.Name, h.moduleManager.kubeModulesConfigValues[moduleName].DebugString()) return fmt.Errorf("module hook '%s': set kube module config failed: %s", h.Name, err) @@ -337,7 +337,7 @@ func (h *ModuleHook) prepareConfigValuesJsonFile() (string, error) { // BINDING_CONTEXT_PATH func (h *ModuleHook) prepareBindingContextJsonFile(bindingContext []byte) (string, error) { // data := utils.MustDump(utils.DumpValuesJson(context)) - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.module-hook-%s-binding-context-%s.json", h.Module.SafeName(), h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.module-hook-%s-binding-context-%s.json", h.Module.SafeName(), h.SafeName(), uuid.Must(uuid.NewV4()).String())) err := dumpData(path, bindingContext) if err != nil { return "", err @@ -351,7 +351,7 @@ func (h *ModuleHook) prepareBindingContextJsonFile(bindingContext []byte) (strin // CONFIG_VALUES_JSON_PATCH_PATH func (h *ModuleHook) prepareConfigValuesJsonPatchFile() (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.module-hook-config-values-%s.json-patch", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.module-hook-config-values-%s.json-patch", h.SafeName(), uuid.Must(uuid.NewV4()).String())) if err := CreateEmptyWritableFile(path); err != nil { return "", err } @@ -360,7 +360,7 @@ func (h *ModuleHook) prepareConfigValuesJsonPatchFile() (string, error) { // VALUES_JSON_PATCH_PATH func (h *ModuleHook) prepareValuesJsonPatchFile() (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.module-hook-values-%s.json-patch", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.module-hook-values-%s.json-patch", h.SafeName(), uuid.Must(uuid.NewV4()).String())) if err := CreateEmptyWritableFile(path); err != nil { return "", err } @@ -369,7 +369,7 @@ func (h *ModuleHook) prepareValuesJsonPatchFile() (string, error) { // METRICS_PATH func (h *ModuleHook) prepareMetricsFile() (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.module-hook-metrics-%s.json", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s.module-hook-metrics-%s.json", h.SafeName(), uuid.Must(uuid.NewV4()).String())) if err := CreateEmptyWritableFile(path); err != nil { return "", err } @@ -378,7 +378,7 @@ func (h *ModuleHook) prepareMetricsFile() (string, error) { // KUBERNETES PATCH PATH func (h *ModuleHook) prepareKubernetesPatchFile() (string, error) { - path := filepath.Join(h.TmpDir, fmt.Sprintf("%s-object-patch-%s", h.SafeName(), uuid.NewV4().String())) + path := filepath.Join(h.TmpDir, fmt.Sprintf("%s-object-patch-%s", h.SafeName(), uuid.Must(uuid.NewV4()).String())) if err := CreateEmptyWritableFile(path); err != nil { return "", err } diff --git a/pkg/module_manager/module_manager.go b/pkg/module_manager/module_manager.go index 30900358..6111f9ba 100644 --- a/pkg/module_manager/module_manager.go +++ b/pkg/module_manager/module_manager.go @@ -17,7 +17,7 @@ import ( "github.com/flant/addon-operator/pkg/helm" "github.com/flant/addon-operator/pkg/helm_resources_manager" . "github.com/flant/addon-operator/pkg/hook/types" - "github.com/flant/addon-operator/pkg/kube_config_manager" + "github.com/flant/addon-operator/pkg/kube_config_manager/config" "github.com/flant/addon-operator/pkg/module_manager/go_hook" "github.com/flant/addon-operator/pkg/utils" "github.com/flant/addon-operator/pkg/values/validation" @@ -54,8 +54,7 @@ type DirectoryConfig struct { } type KubeConfigManager interface { - SaveGlobalConfigValues(values utils.Values) error - SaveModuleConfigValues(moduleName string, values utils.Values) error + SaveConfigValues(key string, values utils.Values) error } // ModuleManagerDependencies pass dependencies for ModuleManager @@ -221,7 +220,7 @@ func (mm *ModuleManager) runModulesEnabledScript(modules []string, logLabels map // - mm.enabledModulesByConfig // - mm.kubeGlobalConfigValues // - mm.kubeModulesConfigValues -func (mm *ModuleManager) HandleNewKubeConfig(kubeConfig *kube_config_manager.KubeConfig) (*ModulesState, error) { +func (mm *ModuleManager) HandleNewKubeConfig(kubeConfig *config.KubeConfig) (*ModulesState, error) { var err error mm.warnAboutUnknownModules(kubeConfig) @@ -243,15 +242,12 @@ func (mm *ModuleManager) HandleNewKubeConfig(kubeConfig *kube_config_manager.Kub newGlobalValues = make(utils.Values) } if kubeConfig != nil && kubeConfig.Global != nil { - globalChecksum, err := mm.kubeGlobalConfigValues.Checksum() - if err != nil { - return nil, err - } + globalChecksum := mm.kubeGlobalConfigValues.Checksum() if kubeConfig.Global.Checksum != globalChecksum { hasGlobalChange = true } - newGlobalValues = kubeConfig.Global.Values + newGlobalValues = kubeConfig.Global.GetValues() } // Full reload if enabled flags are changed. @@ -277,7 +273,7 @@ func (mm *ModuleManager) HandleNewKubeConfig(kubeConfig *kube_config_manager.Kub modValues, hasConfigValues := mm.kubeModulesConfigValues[moduleName] // New module state from ConfigMap. hasNewKubeConfig := false - var newModConfig *kube_config_manager.ModuleKubeConfig + var newModConfig *config.ModuleKubeConfig if kubeConfig != nil { newModConfig, hasNewKubeConfig = kubeConfig.Modules[moduleName] } @@ -290,14 +286,8 @@ func (mm *ModuleManager) HandleNewKubeConfig(kubeConfig *kube_config_manager.Kub // Compare checksums for new and saved values. if hasConfigValues && hasNewKubeConfig { - modValuesChecksum, err := modValues.Checksum() - if err != nil { - return nil, err - } - newModValuesChecksum, err := newModConfig.Values.Checksum() - if err != nil { - return nil, err - } + modValuesChecksum := modValues.Checksum() + newModValuesChecksum := newModConfig.GetValues().Checksum() if modValuesChecksum != newModValuesChecksum { modulesChanged = append(modulesChanged, moduleName) } @@ -309,7 +299,7 @@ func (mm *ModuleManager) HandleNewKubeConfig(kubeConfig *kube_config_manager.Kub newKubeModuleConfigValues := make(map[string]utils.Values) if kubeConfig != nil { for moduleName, moduleConfig := range kubeConfig.Modules { - newKubeModuleConfigValues[moduleName] = moduleConfig.Values + newKubeModuleConfigValues[moduleName] = moduleConfig.GetValues() } } @@ -338,7 +328,7 @@ func (mm *ModuleManager) HandleNewKubeConfig(kubeConfig *kube_config_manager.Kub } // warnAboutUnknownModules prints to log all unknown module section names. -func (mm *ModuleManager) warnAboutUnknownModules(kubeConfig *kube_config_manager.KubeConfig) { +func (mm *ModuleManager) warnAboutUnknownModules(kubeConfig *config.KubeConfig) { // Ignore empty kube config. if kubeConfig == nil { return @@ -361,7 +351,7 @@ func (mm *ModuleManager) warnAboutUnknownModules(kubeConfig *kube_config_manager // // Module is enabled by config if module section in ConfigMap is a map or an array // or ConfigMap has no module section and module has a map or an array in values.yaml -func (mm *ModuleManager) calculateEnabledModulesByConfig(config *kube_config_manager.KubeConfig) map[string]struct{} { +func (mm *ModuleManager) calculateEnabledModulesByConfig(config *config.KubeConfig) map[string]struct{} { enabledByConfig := make(map[string]struct{}) for _, module := range mm.modules.List() { @@ -433,7 +423,7 @@ func (mm *ModuleManager) Init() error { } // validateKubeConfig checks validity of all sections in ConfigMap with OpenAPI schemas. -func (mm *ModuleManager) validateKubeConfig(kubeConfig *kube_config_manager.KubeConfig, enabledModules map[string]struct{}) error { +func (mm *ModuleManager) validateKubeConfig(kubeConfig *config.KubeConfig, enabledModules map[string]struct{}) error { // Ignore empty kube config. if kubeConfig == nil { mm.SetKubeConfigValuesValid(true) @@ -442,7 +432,7 @@ func (mm *ModuleManager) validateKubeConfig(kubeConfig *kube_config_manager.Kube // Validate values in global section merged with static values. var validationErr error if kubeConfig.Global != nil { - err := mm.ValuesValidator.ValidateGlobalConfigValues(mm.GlobalStaticAndNewValues(kubeConfig.Global.Values)) + err := mm.ValuesValidator.ValidateGlobalConfigValues(mm.GlobalStaticAndNewValues(kubeConfig.Global.GetValues())) if err != nil { validationErr = multierror.Append( validationErr, @@ -459,11 +449,11 @@ func (mm *ModuleManager) validateKubeConfig(kubeConfig *kube_config_manager.Kube continue } mod := mm.GetModule(moduleName) - moduleErr := mm.ValuesValidator.ValidateModuleConfigValues(mod.ValuesKey(), mod.StaticAndNewValues(modCfg.Values)) + moduleErr := mm.ValuesValidator.ValidateModuleConfigValues(mod.ValuesKey(), mod.StaticAndNewValues(modCfg.GetValues())) if moduleErr != nil { validationErr = multierror.Append( validationErr, - fmt.Errorf("'%s' module section in ConfigMap/%s is not valid", mod.ValuesKey(), app.ConfigMapName), + fmt.Errorf("'%s' module section in KubeConfig is not valid", mod.ValuesKey()), moduleErr, ) } @@ -767,10 +757,7 @@ func (mm *ModuleManager) RunGlobalHook(hookName string, binding BindingType, bin if err != nil { return "", "", err } - beforeChecksum, err := beforeValues.Checksum() - if err != nil { - return "", "", err - } + beforeChecksum := beforeValues.Checksum() // Update kubernetes snapshots just before execute a hook if binding == OnKubernetesEvent || binding == Schedule { @@ -796,10 +783,7 @@ func (mm *ModuleManager) RunGlobalHook(hookName string, binding BindingType, bin if err != nil { return "", "", err } - afterChecksum, err := afterValues.Checksum() - if err != nil { - return "", "", err - } + afterChecksum := afterValues.Checksum() return beforeChecksum, afterChecksum, nil } @@ -811,10 +795,7 @@ func (mm *ModuleManager) RunModuleHook(hookName string, binding BindingType, bin if err != nil { return "", "", err } - valuesChecksum, err := values.Checksum() - if err != nil { - return "", "", err - } + valuesChecksum := values.Checksum() // Update kubernetes snapshots just before execute a hook // Note: BeforeHelm and AfterHelm are run by runHookByBinding @@ -838,10 +819,7 @@ func (mm *ModuleManager) RunModuleHook(hookName string, binding BindingType, bin if err != nil { return "", "", err } - newValuesChecksum, err := newValues.Checksum() - if err != nil { - return "", "", err - } + newValuesChecksum := newValues.Checksum() return valuesChecksum, newValuesChecksum, nil } diff --git a/pkg/module_manager/module_manager_test.go b/pkg/module_manager/module_manager_test.go index 48d8ceb5..373142d0 100644 --- a/pkg/module_manager/module_manager_test.go +++ b/pkg/module_manager/module_manager_test.go @@ -20,6 +20,8 @@ import ( mockhelmresmgr "github.com/flant/addon-operator/pkg/helm_resources_manager/test/mock" . "github.com/flant/addon-operator/pkg/hook/types" "github.com/flant/addon-operator/pkg/kube_config_manager" + "github.com/flant/addon-operator/pkg/kube_config_manager/backend/configmap" + "github.com/flant/addon-operator/pkg/kube_config_manager/config" _ "github.com/flant/addon-operator/pkg/module_manager/test/go_hooks/global-hooks" "github.com/flant/addon-operator/pkg/utils" klient "github.com/flant/kube-client/client" @@ -33,8 +35,8 @@ type TestKubeConfigManager interface { Init() error Start() Stop() - KubeConfigEventCh() chan kube_config_manager.KubeConfigEvent - SafeReadConfig(handler func(config *kube_config_manager.KubeConfig)) + KubeConfigEventCh() chan config.KubeConfigEvent + SafeReadConfig(handler func(config *config.KubeConfig)) } type initModuleManagerResult struct { @@ -42,7 +44,7 @@ type initModuleManagerResult struct { kubeConfigManager TestKubeConfigManager helmClient *mockhelm.Client helmResourcesManager *mockhelmresmgr.MockHelmResourcesManager - kubeClient klient.Client + kubeClient *klient.Client initialState *ModulesState initialStateErr error cmName string @@ -102,13 +104,8 @@ func initModuleManager(t *testing.T, configPath string) (*ModuleManager, *initMo require.NoError(t, err, "Should create ConfigMap/%s", result.cmName) } - kcfg := kube_config_manager.Config{ - Namespace: result.cmNamespace, - ConfigMapName: result.cmName, - KubeClient: result.kubeClient, - RuntimeConfig: nil, - } - manager := kube_config_manager.NewKubeConfigManager(context.Background(), &kcfg) + bk := configmap.New(nil, result.kubeClient, result.cmNamespace, result.cmName) + manager := kube_config_manager.NewKubeConfigManager(context.Background(), bk, nil) result.kubeConfigManager = manager err = result.kubeConfigManager.Init() @@ -138,7 +135,7 @@ func initModuleManager(t *testing.T, configPath string) (*ModuleManager, *initMo // Start KubeConfigManager to be able to change config values via patching ConfigMap. result.kubeConfigManager.Start() - result.kubeConfigManager.SafeReadConfig(func(config *kube_config_manager.KubeConfig) { + result.kubeConfigManager.SafeReadConfig(func(config *config.KubeConfig) { result.initialState, result.initialStateErr = result.moduleManager.HandleNewKubeConfig(config) }) @@ -191,11 +188,11 @@ func Test_ModuleManager_LoadValuesInInit(t *testing.T) { with1 := mm.modules.Get("with-values-1") assert.NotNil(t, with1.StaticConfig) - assert.Equal(t, modWithValues1Expected, with1.StaticConfig.Values) + assert.Equal(t, modWithValues1Expected, with1.StaticConfig.GetValues()) with2 := mm.modules.Get("with-values-2") assert.NotNil(t, with2.StaticConfig) - assert.Equal(t, modWithValues2Expected, with2.StaticConfig.Values) + assert.Equal(t, modWithValues2Expected, with2.StaticConfig.GetValues()) }, }, { @@ -227,19 +224,19 @@ func Test_ModuleManager_LoadValuesInInit(t *testing.T) { assert.NotNil(t, with1.CommonStaticConfig) assert.NotNil(t, with1.StaticConfig) assert.Equal(t, "with-values-1", with1.CommonStaticConfig.ModuleName) - assert.Equal(t, "withValues1", with1.CommonStaticConfig.ModuleConfigKey) - assert.Equal(t, "withValues1Enabled", with1.CommonStaticConfig.ModuleEnabledKey) + assert.Equal(t, "withValues1", with1.CommonStaticConfig.ModuleConfigKey()) + assert.Equal(t, "withValues1Enabled", with1.CommonStaticConfig.ModuleEnabledKey()) assert.Equal(t, "with-values-1", with1.StaticConfig.ModuleName) // with-values-1 is enabled by common values.yaml assert.True(t, *with1.CommonStaticConfig.IsEnabled) assert.False(t, *with1.StaticConfig.IsEnabled) - assert.Len(t, with1.CommonStaticConfig.Values["withValues1"], 1) - assert.Len(t, with1.StaticConfig.Values["withValues1"], 3) + assert.Len(t, with1.CommonStaticConfig.GetValues()["withValues1"], 1) + assert.Len(t, with1.StaticConfig.GetValues()["withValues1"], 3) // with-values-1 has "a" value in common and in module static values - assert.Contains(t, with1.CommonStaticConfig.Values["withValues1"], "a") - assert.Contains(t, with1.StaticConfig.Values["withValues1"], "a") + assert.Contains(t, with1.CommonStaticConfig.GetValues()["withValues1"], "a") + assert.Contains(t, with1.StaticConfig.GetValues()["withValues1"], "a") assert.NotContains(t, mm.kubeModulesConfigValues, "with-values-1") @@ -293,8 +290,8 @@ func Test_ModuleManager_LoadValues_ApplyDefaults(t *testing.T) { assert.NotNil(t, modOne.StaticConfig) assert.Equal(t, "module-one", modOne.CommonStaticConfig.ModuleName) assert.Equal(t, "module-one", modOne.StaticConfig.ModuleName) - assert.Equal(t, "moduleOne", modOne.CommonStaticConfig.ModuleConfigKey) - assert.Equal(t, "moduleOneEnabled", modOne.CommonStaticConfig.ModuleEnabledKey) + assert.Equal(t, "moduleOne", modOne.CommonStaticConfig.ModuleConfigKey()) + assert.Equal(t, "moduleOneEnabled", modOne.CommonStaticConfig.ModuleEnabledKey()) // module-one is not enabled in any of values.yaml assert.Nil(t, modOne.CommonStaticConfig.IsEnabled) @@ -1231,7 +1228,7 @@ func Test_ModuleManager_ModulesState_detect_ConfigMap_changes(t *testing.T) { <-res.kubeConfigManager.KubeConfigEventCh() var state *ModulesState - res.kubeConfigManager.SafeReadConfig(func(config *kube_config_manager.KubeConfig) { + res.kubeConfigManager.SafeReadConfig(func(config *config.KubeConfig) { state, err = mm.HandleNewKubeConfig(config) }) require.Len(t, state.ModulesToReload, 0, "Enabled flag change should lead to reload all modules") @@ -1271,7 +1268,7 @@ func Test_ModuleManager_ModulesState_detect_ConfigMap_changes(t *testing.T) { <-res.kubeConfigManager.KubeConfigEventCh() var state *ModulesState - res.kubeConfigManager.SafeReadConfig(func(config *kube_config_manager.KubeConfig) { + res.kubeConfigManager.SafeReadConfig(func(config *config.KubeConfig) { state, err = mm.HandleNewKubeConfig(config) }) require.Len(t, state.ModulesToReload, 2, "Enabled flag change should lead to reload all modules") @@ -1311,7 +1308,7 @@ func Test_ModuleManager_ModulesState_detect_ConfigMap_changes(t *testing.T) { <-res.kubeConfigManager.KubeConfigEventCh() var state *ModulesState - res.kubeConfigManager.SafeReadConfig(func(config *kube_config_manager.KubeConfig) { + res.kubeConfigManager.SafeReadConfig(func(config *config.KubeConfig) { state, err = mm.HandleNewKubeConfig(config) }) require.NoError(t, err, "Should handle new ConfigMap") diff --git a/pkg/utils/logger/logger.go b/pkg/utils/logger/logger.go new file mode 100644 index 00000000..08a42d2c --- /dev/null +++ b/pkg/utils/logger/logger.go @@ -0,0 +1,33 @@ +package logger + +type Logger interface { + // standard logger methods + Print(args ...interface{}) + Printf(format string, args ...interface{}) + Println(args ...interface{}) + + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + Fatalln(args ...interface{}) + + Panic(args ...interface{}) + Panicf(format string, args ...interface{}) + Panicln(args ...interface{}) + + // Leveled methods, from logrus + Debug(args ...interface{}) + Debugf(format string, args ...interface{}) + Debugln(args ...interface{}) + + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Errorln(args ...interface{}) + + Info(args ...interface{}) + Infof(format string, args ...interface{}) + Infoln(args ...interface{}) + + Warn(args ...interface{}) + Warnf(format string, args ...interface{}) + Warnln(args ...interface{}) +} diff --git a/pkg/utils/module_config.go b/pkg/utils/module_config.go index 535aceef..2bf7b6f4 100644 --- a/pkg/utils/module_config.go +++ b/pkg/utils/module_config.go @@ -3,10 +3,10 @@ package utils import ( "encoding/json" "fmt" + "strconv" "strings" "github.com/davecgh/go-spew/spew" - "sigs.k8s.io/yaml" utils_checksum "github.com/flant/shell-operator/pkg/utils/checksum" ) @@ -17,18 +17,25 @@ var ( ) type ModuleConfig struct { - ModuleName string - IsEnabled *bool - Values Values - IsUpdated bool - ModuleConfigKey string - ModuleEnabledKey string - RawConfig []string + ModuleName string + IsEnabled *bool + // module values, don't read it directly, use GetValues() for reading + values Values } // String returns description of ModuleConfig values. func (mc *ModuleConfig) String() string { - return fmt.Sprintf("Module(Name=%s IsEnabled=%v IsUpdated=%v Values:\n%s)", mc.ModuleName, mc.IsEnabled, mc.IsUpdated, mc.Values.DebugString()) + return fmt.Sprintf("Module(Name=%s IsEnabled=%v Values:\n%s)", mc.ModuleName, mc.IsEnabled, mc.values.DebugString()) +} + +// ModuleConfigKey transforms module kebab-case name to the config camelCase name +func (mc *ModuleConfig) ModuleConfigKey() string { + return ModuleNameToValuesKey(mc.ModuleName) +} + +// ModuleEnabledKey transforms module kebab-case name to the config camelCase name with 'Enabled' suffix +func (mc *ModuleConfig) ModuleEnabledKey() string { + return ModuleNameToValuesKey(mc.ModuleName) + "Enabled" } // GetEnabled returns string description of enabled status. @@ -46,34 +53,36 @@ func (mc *ModuleConfig) GetEnabled() string { } } -func NewModuleConfig(moduleName string) *ModuleConfig { +func NewModuleConfig(moduleName string, values Values) *ModuleConfig { + if values == nil { + values = make(Values) + } return &ModuleConfig{ - ModuleName: moduleName, - IsEnabled: nil, - Values: make(Values), - ModuleConfigKey: ModuleNameToValuesKey(moduleName), - ModuleEnabledKey: ModuleNameToValuesKey(moduleName) + "Enabled", - RawConfig: make([]string, 0), + ModuleName: moduleName, + IsEnabled: nil, + values: values, } } -func (mc *ModuleConfig) WithEnabled(v bool) *ModuleConfig { - if v { - mc.IsEnabled = &ModuleEnabled - } else { - mc.IsEnabled = &ModuleDisabled +// GetValues enrich module values with module's name top level key +// if key is already present - returns values as it +// module: test-module with values {"a": "b", "c": "d} will return: +// +// testModule: +// a: b +// c: d +/* TODO: since we have specified struct for module values, we don't need to encapsulate them into the map {"": ... } + we have to change this behavior somewhere in the module-manager */ +func (mc *ModuleConfig) GetValues() Values { + if len(mc.values) == 0 { + return mc.values } - return mc -} -func (mc *ModuleConfig) WithUpdated(v bool) *ModuleConfig { - mc.IsUpdated = v - return mc -} + if mc.values.HasKey(ModuleNameToValuesKey(mc.ModuleName)) { + return mc.values + } -func (mc *ModuleConfig) WithValues(values Values) *ModuleConfig { - mc.Values = values - return mc + return Values{ModuleNameToValuesKey(mc.ModuleName): mc.values} } // LoadFromValues loads module config from a map. @@ -81,25 +90,25 @@ func (mc *ModuleConfig) WithValues(values Values) *ModuleConfig { // Values for module in `values` map are addressed by a key. // This key should be produced with ModuleNameToValuesKey. func (mc *ModuleConfig) LoadFromValues(values Values) (*ModuleConfig, error) { - if moduleValuesData, hasModuleData := values[mc.ModuleConfigKey]; hasModuleData { + if moduleValuesData, hasModuleData := values[mc.ModuleConfigKey()]; hasModuleData { switch v := moduleValuesData.(type) { case map[string]interface{}, []interface{}: - data := map[string]interface{}{mc.ModuleConfigKey: v} + data := map[string]interface{}{mc.ModuleConfigKey(): v} values, err := NewValues(data) if err != nil { return nil, err } - mc.Values = values + mc.values = values default: return nil, fmt.Errorf("load '%s' values: module config should be array or map. Got: %s", mc.ModuleName, spew.Sdump(moduleValuesData)) } } - if moduleEnabled, hasModuleEnabled := values[mc.ModuleEnabledKey]; hasModuleEnabled { + if moduleEnabled, hasModuleEnabled := values[mc.ModuleEnabledKey()]; hasModuleEnabled { switch v := moduleEnabled.(type) { case bool: - mc.WithEnabled(v) + mc.IsEnabled = &v default: return nil, fmt.Errorf("load '%s' enable config: enabled value should be bool. Got: %#v", mc.ModuleName, moduleEnabled) } @@ -124,74 +133,16 @@ func (mc *ModuleConfig) FromYaml(yamlString []byte) (*ModuleConfig, error) { return nil, fmt.Errorf("load module '%s' yaml config: %s\n%s", mc.ModuleName, err, string(yamlString)) } - mc.RawConfig = []string{string(yamlString)} - return mc.LoadFromValues(values) } -// FromConfigMapData loads module config from a structure with string keys and yaml string values (ConfigMap) -// -// Example: -// -// simpleModule: | -// param1: 10 -// param2: 120 -// simpleModuleEnabled: "true" - -// TODO "msg": "Kube config manager: cannot handle ConfigMap update: ConfigMap: -// bad yaml at key 'deployWithHooks': -// data is not compatible with JSON and YAML: -// error marshaling into JSON: -// json: unsupported type: map[interface {}]interface {}, data: -// (map[string]interface {}) (len=1) {\n (string) (len=15) \"deployWithHooks\": (map[interface {}]interface {}) (len=3) {\n (string) (len=6) \"param2\": (string) (len=11) \"srqweqweqwe\",\n (string) (len=8) \"paramArr\": ([]interface {}) (len=4 cap=4) {\n (string) (len=3) \"asd\",\n (string) (len=3) \"qwe\",\n (string) (len=6) \"sadasd\",\n (string) (len=5) \"salad\"\n },\n (string) (len=6) \"param1\": (int) 1\n }\n}\n", - -func (mc *ModuleConfig) FromConfigMapData(configData map[string]string) (*ModuleConfig, error) { - // create Values with moduleNameKey and moduleEnabled keys - configValues := make(Values) - - // if there is data for module, unmarshal it and put into configValues - valuesYaml, hasKey := configData[mc.ModuleConfigKey] - if hasKey { - var moduleValues interface{} - - err := yaml.Unmarshal([]byte(valuesYaml), &moduleValues) - if err != nil { - return nil, fmt.Errorf("unmarshal yaml data in a module config key '%s': %v", mc.ModuleConfigKey, err) - } - - configValues[mc.ModuleConfigKey] = moduleValues - - mc.RawConfig = append(mc.RawConfig, valuesYaml) - } - - // if there is enabled key, treat it as boolean - enabledString, hasKey := configData[mc.ModuleEnabledKey] - if hasKey { - var enabled bool - - switch enabledString { - case "true": - enabled = true - case "false": - enabled = false - default: - return nil, fmt.Errorf("module enabled key '%s' should have a boolean value, got '%v'", mc.ModuleEnabledKey, enabledString) - } - - configValues[mc.ModuleEnabledKey] = enabled - - mc.RawConfig = append(mc.RawConfig, enabledString) - } - - if len(configValues) == 0 { - return mc, nil - } - - return mc.LoadFromValues(configValues) -} - func (mc *ModuleConfig) Checksum() string { - return utils_checksum.CalculateChecksum(mc.RawConfig...) + vChecksum := mc.values.Checksum() + enabled := "" + if mc.IsEnabled != nil { + enabled = strconv.FormatBool(*mc.IsEnabled) + } + return utils_checksum.CalculateChecksum(enabled, vChecksum) } func ModuleEnabledValue(i interface{}) (*bool, error) { diff --git a/pkg/utils/module_config_test.go b/pkg/utils/module_config_test.go index 58e2c8d9..2cc35852 100644 --- a/pkg/utils/module_config_test.go +++ b/pkg/utils/module_config_test.go @@ -5,6 +5,7 @@ import ( . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" + "k8s.io/utils/pointer" ) // Test_FromYaml creates ModuleConfig objects from different input yaml strings @@ -45,7 +46,7 @@ testModule: func() { g.Expect(err).ShouldNot(HaveOccurred()) g.Expect(config).ToNot(BeNil()) - g.Expect(config.Values).To(BeEmpty()) + g.Expect(config.GetValues()).To(BeEmpty()) g.Expect(config.IsEnabled).To(Equal(&ModuleDisabled)) }, }, @@ -55,7 +56,7 @@ testModule: func() { g.Expect(err).ShouldNot(HaveOccurred()) g.Expect(config).ToNot(BeNil()) - g.Expect(config.Values).To(BeEmpty()) + g.Expect(config.GetValues()).To(BeEmpty()) g.Expect(config.IsEnabled).To(Equal(&ModuleEnabled)) }, }, @@ -78,11 +79,11 @@ testModuleEnabled: true g.Expect(config).ToNot(BeNil()) g.Expect(config.IsEnabled).To(Equal(&ModuleEnabled)) - g.Expect(config.Values).ToNot(BeEmpty()) - g.Expect(config.Values).To(HaveKey("testModule")) - g.Expect(config.Values["testModule"]).To(BeAssignableToTypeOf(map[string]interface{}{})) + g.Expect(config.GetValues()).ToNot(BeEmpty()) + g.Expect(config.GetValues()).To(HaveKey("testModule")) + g.Expect(config.GetValues()["testModule"]).To(BeAssignableToTypeOf(map[string]interface{}{})) - modValsMap := config.Values["testModule"].(map[string]interface{}) + modValsMap := config.GetValues()["testModule"].(map[string]interface{}) g.Expect(modValsMap["hello"]).To(Equal("world")) g.Expect(modValsMap["4"]).To(Equal("123")) g.Expect(modValsMap["5"]).To(Equal(5.0)) @@ -112,7 +113,7 @@ testModule: return (element.(map[string]interface{})["id"]).(string) } - g.Expect(config.Values).To(MatchAllKeys(Keys{ + g.Expect(config.GetValues()).To(MatchAllKeys(Keys{ "testModule": MatchAllElements(arrayID, Elements{ "0": MatchAllKeys(Keys{ "a": Equal(1.0), @@ -131,7 +132,7 @@ testModule: t.Run(test.name, func(t *testing.T) { config = nil err = nil - config, err = NewModuleConfig("test-module").FromYaml([]byte(test.yaml)) + config, err = NewModuleConfig("test-module", nil).FromYaml([]byte(test.yaml)) test.assertFn() }) } @@ -163,24 +164,6 @@ testModule: testModuleEnabled: true ` - configMapDataMapValues := map[string]string{ - "global": `asd: qwe`, - "test-module": ` -foo: bar -`, - "testModule": ` -hello: world -4: "123" -5: 5 -aaa: - "no": - - one - - two - - three -`, - "testModuleEnabled": "false", - } - expectedData := Values{ "testModule": map[string]interface{}{ "hello": "world", "4": "123", "5": 5.0, @@ -188,24 +171,17 @@ aaa: }, } - config, err = NewModuleConfig("test-module").LoadFromValues(inputData) + config, err = NewModuleConfig("test-module", nil).LoadFromValues(inputData) g.Expect(err).ShouldNot(HaveOccurred()) g.Expect(config).ToNot(BeNil()) - g.Expect(config.Values).To(Equal(expectedData)) + g.Expect(config.GetValues()).To(Equal(expectedData)) - config, err = NewModuleConfig("test-module").FromYaml([]byte(inputValuesYaml)) + config, err = NewModuleConfig("test-module", nil).FromYaml([]byte(inputValuesYaml)) g.Expect(err).ShouldNot(HaveOccurred()) g.Expect(config).ToNot(BeNil()) - g.Expect(config.Values).To(Equal(expectedData)) + g.Expect(config.GetValues()).To(Equal(expectedData)) g.Expect(config.IsEnabled).ToNot(BeNil()) g.Expect(config.IsEnabled).To(Equal(&ModuleEnabled)) - - config, err = NewModuleConfig("test-module").FromConfigMapData(configMapDataMapValues) - g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(config).ToNot(BeNil()) - g.Expect(config.Values).To(Equal(expectedData)) - g.Expect(config.IsEnabled).ToNot(BeNil()) - g.Expect(config.IsEnabled).To(Equal(&ModuleDisabled)) } func Test_GetEnabled(t *testing.T) { @@ -245,7 +221,7 @@ func Test_GetEnabled(t *testing.T) { "nil", func() { config = &ModuleConfig{} - config.WithEnabled(true) + config.IsEnabled = pointer.Bool(true) }, "true", }, @@ -253,7 +229,7 @@ func Test_GetEnabled(t *testing.T) { "nil", func() { config = &ModuleConfig{} - config.WithEnabled(false) + config.IsEnabled = pointer.Bool(false) }, "false", }, diff --git a/pkg/utils/values.go b/pkg/utils/values.go index e7f5c25e..6de53284 100644 --- a/pkg/utils/values.go +++ b/pkg/utils/values.go @@ -115,12 +115,10 @@ func (v Values) DebugString() string { return string(b) } -func (v Values) Checksum() (string, error) { - valuesJson, err := json.Marshal(v) - if err != nil { - return "", err - } - return utils_checksum.CalculateChecksum(string(valuesJson)), nil +func (v Values) Checksum() string { + valuesJson, _ := json.Marshal(v) + + return utils_checksum.CalculateChecksum(string(valuesJson)) } func (v Values) HasKey(key string) bool {