From be640bc91013b31f0f3e65fe36ebeee56c94e7aa Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Wed, 13 Nov 2024 14:45:30 +0300 Subject: [PATCH 1/4] wip Signed-off-by: Pavel Okhlopkov --- go.mod | 28 +- go.sum | 42 +- pkg/app/webhook.go | 7 +- pkg/hook/binding_context/binding_context.go | 5 +- .../binding_context/binding_context_test.go | 4 +- pkg/hook/config/config.go | 3 +- pkg/hook/config/config_v0.go | 11 +- pkg/hook/config/config_v1.go | 24 +- .../admission_bindings_controller.go | 7 +- .../conversion_bindings_controller.go | 7 +- pkg/hook/controller/hook_controller.go | 14 +- pkg/hook/controller/hook_controller_test.go | 12 +- .../kubernetes_bindings_controller.go | 14 +- .../schedule_bindings_controller.go | 5 +- pkg/hook/hook.go | 10 +- pkg/hook/hook_manager.go | 14 +- pkg/hook/hook_manager_test.go | 6 +- pkg/hook/hook_test.go | 2 +- pkg/hook/task_metadata/task_metadata.go | 14 +- pkg/hook/task_metadata/task_metadata_test.go | 4 +- pkg/hook/types/bindings.go | 77 -- pkg/kube/object_patch/helpers.go | 144 --- pkg/kube/object_patch/operation.go | 288 ------ pkg/kube/object_patch/options.go | 137 --- pkg/kube/object_patch/patch.go | 301 ------ pkg/kube/object_patch/patch_collector.go | 85 -- pkg/kube/object_patch/patch_test.go | 949 ------------------ .../serialized_operations/invalid_create.yaml | 8 - .../serialized_operations/invalid_delete.yaml | 12 - .../serialized_operations/invalid_patch.yaml | 15 - .../serialized_operations/valid_create.yaml | 16 - .../serialized_operations/valid_delete.yaml | 18 - .../serialized_operations/valid_patch.yaml | 27 - pkg/kube/object_patch/validation.go | 214 ---- pkg/kube/object_patch/validation_test.go | 28 - pkg/kube_events_manager/error_handler.go | 6 +- pkg/kube_events_manager/filter.go | 2 +- .../kube_events_manager.go | 10 +- .../kube_events_manager_test.go | 2 +- pkg/kube_events_manager/monitor.go | 8 +- pkg/kube_events_manager/monitor_config.go | 3 +- pkg/kube_events_manager/monitor_test.go | 2 +- pkg/kube_events_manager/resource_informer.go | 8 +- pkg/kube_events_manager/util.go | 3 +- pkg/metric/collector_test.go | 2 +- pkg/metric/grouped_storage_mock.go | 2 +- pkg/metric/storage.go | 3 +- pkg/metric/storage_mock.go | 4 +- pkg/metric/storage_test.go | 8 +- pkg/metric_storage/metric_storage.go | 6 +- pkg/metric_storage/vault/vault.go | 2 +- pkg/schedule_manager/schedule_manager.go | 3 +- pkg/schedule_manager/schedule_manager_test.go | 3 +- pkg/shell-operator/bootstrap.go | 8 +- pkg/shell-operator/combine_binding_context.go | 3 +- .../combine_binding_context_test.go | 64 +- pkg/shell-operator/kube_client.go | 14 +- pkg/shell-operator/manager_events_handler.go | 8 +- pkg/shell-operator/metrics_hooks.go | 7 +- pkg/shell-operator/metrics_operator.go | 11 +- pkg/shell-operator/operator.go | 38 +- pkg/shell-operator/operator_test.go | 2 +- pkg/task/queue/queue_set.go | 7 +- pkg/task/queue/task_queue.go | 6 +- pkg/webhook/admission/manager.go | 2 +- pkg/webhook/admission/settings.go | 2 +- pkg/webhook/conversion/manager.go | 2 +- pkg/webhook/conversion/settings.go | 2 +- test/hook/context/context_combiner.go | 15 +- test/hook/context/generator.go | 10 +- test/hook/context/generator_test.go | 3 +- test/hook/context/state.go | 5 +- .../kube_event_manager_test.go | 12 +- .../kubeclient/object_patch_test.go | 28 +- test/integration/suite/run.go | 6 +- 75 files changed, 282 insertions(+), 2602 deletions(-) delete mode 100644 pkg/hook/types/bindings.go delete mode 100644 pkg/kube/object_patch/helpers.go delete mode 100644 pkg/kube/object_patch/operation.go delete mode 100644 pkg/kube/object_patch/options.go delete mode 100644 pkg/kube/object_patch/patch.go delete mode 100644 pkg/kube/object_patch/patch_collector.go delete mode 100644 pkg/kube/object_patch/patch_test.go delete mode 100644 pkg/kube/object_patch/testdata/serialized_operations/invalid_create.yaml delete mode 100644 pkg/kube/object_patch/testdata/serialized_operations/invalid_delete.yaml delete mode 100644 pkg/kube/object_patch/testdata/serialized_operations/invalid_patch.yaml delete mode 100644 pkg/kube/object_patch/testdata/serialized_operations/valid_create.yaml delete mode 100644 pkg/kube/object_patch/testdata/serialized_operations/valid_delete.yaml delete mode 100644 pkg/kube/object_patch/testdata/serialized_operations/valid_patch.yaml delete mode 100644 pkg/kube/object_patch/validation.go delete mode 100644 pkg/kube/object_patch/validation_test.go diff --git a/go.mod b/go.mod index d5739ada..ebb39657 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/kennygrant/sanitize v1.2.4 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.35.1 - github.com/pkg/errors v0.9.1 + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.20.5 github.com/stretchr/testify v1.9.0 golang.org/x/time v0.7.0 @@ -24,7 +24,7 @@ require ( gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.8 - k8s.io/apiextensions-apiserver v0.28.4 + k8s.io/apiextensions-apiserver v0.29.0 k8s.io/apimachinery v0.29.8 k8s.io/client-go v0.29.8 sigs.k8s.io/yaml v1.4.0 @@ -33,7 +33,10 @@ require ( // Remove 'in body' from errors, fix for Go 1.16 (https://github.com/go-openapi/validate/pull/138). replace github.com/go-openapi/validate => github.com/flant/go-openapi-validate v0.19.12-flant.0 -require github.com/gojuno/minimock/v3 v3.4.1 +require ( + github.com/deckhouse/module-sdk v0.0.0-20241113114352-11f03833a3cf + github.com/gojuno/minimock/v3 v3.4.1 +) require ( github.com/DataDog/gostackparse v0.7.0 // indirect @@ -42,10 +45,10 @@ require ( github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/analysis v0.19.10 // indirect github.com/go-openapi/errors v0.19.7 // indirect @@ -61,7 +64,7 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/imdario/mergo v0.3.15 // indirect @@ -70,18 +73,17 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/tidwall/pretty v1.2.0 // indirect go.mongodb.org/mongo-driver v1.5.4 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect @@ -93,9 +95,9 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/go.sum b/go.sum index d25d28f3..898d5b54 100644 --- a/go.sum +++ b/go.sum @@ -22,15 +22,18 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241106140903-258b93b3334e h1:QUQy+5Bv7/UzhfrytiG3c5gfLGhPppepVbRpbMisVIw= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241106140903-258b93b3334e/go.mod h1:Mk5HRzkc5pIcDIZ2JJ6DPuuqnwhXVkb3you8M8Mg+4w= +github.com/deckhouse/module-sdk v0.0.0-20241113114352-11f03833a3cf h1:YvPB431VunQLb8fChP9t9mQNHSvL2Xavk+UQWnGF1sU= +github.com/deckhouse/module-sdk v0.0.0-20241113114352-11f03833a3cf/go.mod h1:q9jIxjVSVhPRJLWC001c7ujYZIzoPKjiF4LV0Zm+XCE= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 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 v1.2.2 h1:27LBs+PKJEFnkQXjPU9eIps7a7iyI13AKcSYj897DCU= @@ -39,12 +42,11 @@ github.com/flant/libjq-go v1.6.3-0.20201126171326-c46a40ff22ee h1:evii83J+/6QGNv github.com/flant/libjq-go v1.6.3-0.20201126171326-c46a40ff22ee/go.mod h1:f+REaGl/+pZR97rbTcwHEka/MAipoQQ2Mc0iQUj4ak0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= @@ -154,8 +156,8 @@ github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSF github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -207,8 +209,8 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsI github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -240,8 +242,9 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -343,7 +346,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= @@ -410,18 +412,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.29.8 h1:ZBKg9clWnIGtQ5yGhNwMw2zyyrsIAQaXhZACcYNflQE= k8s.io/api v0.29.8/go.mod h1:XlGIpmpzKGrtVca7GlgNryZJ19SvQdI808NN7fy1SgQ= -k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU= -k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= k8s.io/apimachinery v0.29.8 h1:uBHc9WuKiTHClIspJqtR84WNpG0aOGn45HWqxgXkk8Y= k8s.io/apimachinery v0.29.8/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= k8s.io/client-go v0.29.8 h1:QMRKcIzqE/qawknXcsi51GdIAYN8UP39S/M5KnFu/J0= k8s.io/client-go v0.29.8/go.mod h1:ZzrAAVrqO2jVXMb8My/jTke8n0a/mIynnA3y/1y1UB0= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/pkg/app/webhook.go b/pkg/app/webhook.go index f6b36ecc..3d2c9a91 100644 --- a/pkg/app/webhook.go +++ b/pkg/app/webhook.go @@ -1,11 +1,10 @@ package app import ( + "github.com/deckhouse/module-sdk/pkg/webhook/admission" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" + "github.com/deckhouse/module-sdk/pkg/webhook/server" "gopkg.in/alecthomas/kingpin.v2" - - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" - "github.com/flant/shell-operator/pkg/webhook/server" ) var ValidatingWebhookSettings = &admission.WebhookSettings{ diff --git a/pkg/hook/binding_context/binding_context.go b/pkg/hook/binding_context/binding_context.go index 126b5b99..de954083 100644 --- a/pkg/hook/binding_context/binding_context.go +++ b/pkg/hook/binding_context/binding_context.go @@ -4,11 +4,10 @@ import ( "encoding/json" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" v1 "k8s.io/api/admission/v1" apixv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - - . "github.com/flant/shell-operator/pkg/hook/types" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" ) // BindingContext contains information about event for hook diff --git a/pkg/hook/binding_context/binding_context_test.go b/pkg/hook/binding_context/binding_context_test.go index efba5588..d29c24f5 100644 --- a/pkg/hook/binding_context/binding_context_test.go +++ b/pkg/hook/binding_context/binding_context_test.go @@ -11,8 +11,8 @@ package binding_context // "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" // . "github.com/flant/libjq-go" -// . "github.com/flant/shell-operator/pkg/hook/types" -// . "github.com/flant/shell-operator/pkg/kube_events_manager/types" +// . "github.com/deckhouse/module-sdk/pkg/hook/types" +// . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" // ) // func JqEqual(t *testing.T, input []byte, program string, expected string) { diff --git a/pkg/hook/config/config.go b/pkg/hook/config/config.go index f42bac2d..2cb2fd71 100644 --- a/pkg/hook/config/config.go +++ b/pkg/hook/config/config.go @@ -3,9 +3,8 @@ package config import ( "fmt" + . "github.com/deckhouse/module-sdk/pkg/hook/types" "sigs.k8s.io/yaml" - - . "github.com/flant/shell-operator/pkg/hook/types" ) var validBindingTypes = []BindingType{OnStartup, Schedule, OnKubernetesEvent, KubernetesValidating, KubernetesMutating, KubernetesConversion} diff --git a/pkg/hook/config/config_v0.go b/pkg/hook/config/config_v0.go index e0c463fc..671e724c 100644 --- a/pkg/hook/config/config_v0.go +++ b/pkg/hook/config/config_v0.go @@ -3,13 +3,12 @@ package config import ( "fmt" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + . "github.com/deckhouse/module-sdk/pkg/schedule-manager/types" "gopkg.in/robfig/cron.v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - . "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/kube_events_manager" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" - . "github.com/flant/shell-operator/pkg/schedule_manager/types" ) type HookConfigV0 struct { @@ -69,7 +68,7 @@ func (cv0 *HookConfigV0) ConvertAndCheck(c *HookConfig) (err error) { return fmt.Errorf("invalid onKubernetesEvent config [%d]: %v", i, err) } - monitor := &kube_events_manager.MonitorConfig{} + monitor := &kubeeventsmanager.MonitorConfig{} monitor.Metadata.DebugName = MonitorDebugName(kubeCfg.Name, i) monitor.Metadata.MonitorId = MonitorConfigID() monitor.Metadata.LogLabels = map[string]string{} diff --git a/pkg/hook/config/config_v1.go b/pkg/hook/config/config_v1.go index 16307950..df18eb34 100644 --- a/pkg/hook/config/config_v1.go +++ b/pkg/hook/config/config_v1.go @@ -5,6 +5,13 @@ import ( "strconv" "time" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + . "github.com/deckhouse/module-sdk/pkg/schedule-manager/types" + "github.com/deckhouse/module-sdk/pkg/webhook/admission" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" + "github.com/deckhouse/module-sdk/pkg/webhook/validating/validation" "github.com/hashicorp/go-multierror" "gopkg.in/robfig/cron.v2" v1 "k8s.io/api/admissionregistration/v1" @@ -12,13 +19,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "github.com/flant/shell-operator/pkg/app" - . "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/kube_events_manager" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" - . "github.com/flant/shell-operator/pkg/schedule_manager/types" - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" - "github.com/flant/shell-operator/pkg/webhook/validating/validation" ) type HookConfigV1 struct { @@ -118,7 +118,7 @@ func (cv1 *HookConfigV1) ConvertAndCheck(c *HookConfig) (err error) { return fmt.Errorf("invalid kubernetes config [%d]: %v", i, err) } - monitor := &kube_events_manager.MonitorConfig{} + monitor := &kubeeventsmanager.MonitorConfig{} monitor.Metadata.DebugName = MonitorDebugName(kubeCfg.Name, i) monitor.Metadata.MonitorId = MonitorConfigID() monitor.Metadata.LogLabels = map[string]string{} @@ -370,14 +370,14 @@ func (cv1 *HookConfigV1) CheckOnKubernetesEvent(kubeCfg OnKubernetesEventConfigV } if kubeCfg.LabelSelector != nil { - _, err := kube_events_manager.FormatLabelSelector(kubeCfg.LabelSelector) + _, err := kubeeventsmanager.FormatLabelSelector(kubeCfg.LabelSelector) if err != nil { allErr = multierror.Append(allErr, fmt.Errorf("labelSelector is invalid: %v", err)) } } if kubeCfg.FieldSelector != nil { - _, err := kube_events_manager.FormatFieldSelector((*FieldSelector)(kubeCfg.FieldSelector)) + _, err := kubeeventsmanager.FormatFieldSelector((*FieldSelector)(kubeCfg.FieldSelector)) if err != nil { allErr = multierror.Append(allErr, fmt.Errorf("fieldSelector is invalid: %v", err)) } @@ -407,14 +407,14 @@ func (cv1 *HookConfigV1) CheckAdmission(kubeConfigs []OnKubernetesEventConfig, c } if cfgV1.LabelSelector != nil { - _, err := kube_events_manager.FormatLabelSelector(cfgV1.LabelSelector) + _, err := kubeeventsmanager.FormatLabelSelector(cfgV1.LabelSelector) if err != nil { allErr = multierror.Append(allErr, fmt.Errorf("labelSelector is invalid: %v", err)) } } if cfgV1.Namespace != nil && cfgV1.Namespace.LabelSelector != nil { - _, err := kube_events_manager.FormatLabelSelector(cfgV1.Namespace.LabelSelector) + _, err := kubeeventsmanager.FormatLabelSelector(cfgV1.Namespace.LabelSelector) if err != nil { allErr = multierror.Append(allErr, fmt.Errorf("namespace.labelSelector is invalid: %v", err)) } diff --git a/pkg/hook/controller/admission_bindings_controller.go b/pkg/hook/controller/admission_bindings_controller.go index cbeeec32..65bb8651 100644 --- a/pkg/hook/controller/admission_bindings_controller.go +++ b/pkg/hook/controller/admission_bindings_controller.go @@ -2,11 +2,10 @@ package controller import ( "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/webhook/admission" v1 "k8s.io/api/admission/v1" - - . "github.com/flant/shell-operator/pkg/hook/binding_context" - . "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/webhook/admission" ) // AdmissionBindingToWebhookLink is a link between a hook and a webhook configuration. diff --git a/pkg/hook/controller/conversion_bindings_controller.go b/pkg/hook/controller/conversion_bindings_controller.go index 4548e01e..e5d0269b 100644 --- a/pkg/hook/controller/conversion_bindings_controller.go +++ b/pkg/hook/controller/conversion_bindings_controller.go @@ -2,11 +2,10 @@ package controller import ( "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - - . "github.com/flant/shell-operator/pkg/hook/binding_context" - . "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/webhook/conversion" ) // A link between a hook and a kube monitor diff --git a/pkg/hook/controller/hook_controller.go b/pkg/hook/controller/hook_controller.go index 54cd166d..09050ca3 100644 --- a/pkg/hook/controller/hook_controller.go +++ b/pkg/hook/controller/hook_controller.go @@ -2,15 +2,15 @@ package controller import ( "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + "github.com/deckhouse/module-sdk/pkg/webhook/admission" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - . "github.com/flant/shell-operator/pkg/hook/binding_context" - . "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/kube_events_manager" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" "github.com/flant/shell-operator/pkg/schedule_manager" - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" ) type BindingExecutionInfo struct { @@ -53,7 +53,7 @@ type HookController struct { logger *log.Logger } -func (hc *HookController) InitKubernetesBindings(bindings []OnKubernetesEventConfig, kubeEventMgr kube_events_manager.KubeEventsManager, logger *log.Logger) { +func (hc *HookController) InitKubernetesBindings(bindings []OnKubernetesEventConfig, kubeEventMgr kubeeventsmanager.KubeEventsManager, logger *log.Logger) { if len(bindings) == 0 { return } diff --git a/pkg/hook/controller/hook_controller_test.go b/pkg/hook/controller/hook_controller_test.go index 92683f9f..868166fc 100644 --- a/pkg/hook/controller/hook_controller_test.go +++ b/pkg/hook/controller/hook_controller_test.go @@ -5,14 +5,14 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + types2 "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" . "github.com/onsi/gomega" "github.com/flant/kube-client/fake" - "github.com/flant/shell-operator/pkg/hook/binding_context" "github.com/flant/shell-operator/pkg/hook/config" - "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/kube_events_manager" - types2 "github.com/flant/shell-operator/pkg/kube_events_manager/types" ) // Test updating snapshots for combined contexts. @@ -20,7 +20,7 @@ func Test_UpdateSnapshots(t *testing.T) { g := NewWithT(t) fc := fake.NewFakeCluster(fake.ClusterVersionV121) - mgr := kube_events_manager.NewKubeEventsManager(context.Background(), fc.Client, log.NewNop()) + mgr := kubeeventsmanager.NewKubeEventsManager(context.Background(), fc.Client, log.NewNop()) testHookConfig := ` configVersion: v1 @@ -51,7 +51,7 @@ kubernetes: hc.EnableScheduleBindings() // Test case: combined binding context for binding_2 and binding_3. - bcs := []binding_context.BindingContext{ + bcs := []bindingcontext.BindingContext{ { Binding: "binding_2", Type: types2.TypeEvent, diff --git a/pkg/hook/controller/kubernetes_bindings_controller.go b/pkg/hook/controller/kubernetes_bindings_controller.go index dd395d31..57de6252 100644 --- a/pkg/hook/controller/kubernetes_bindings_controller.go +++ b/pkg/hook/controller/kubernetes_bindings_controller.go @@ -4,11 +4,11 @@ import ( "fmt" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" - . "github.com/flant/shell-operator/pkg/hook/binding_context" - . "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/kube_events_manager" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" utils "github.com/flant/shell-operator/pkg/utils/labels" ) @@ -21,7 +21,7 @@ type KubernetesBindingToMonitorLink struct { // KubernetesBindingsController handles kubernetes bindings for one hook. type KubernetesBindingsController interface { WithKubernetesBindings([]OnKubernetesEventConfig) - WithKubeEventsManager(kube_events_manager.KubeEventsManager) + WithKubeEventsManager(kubeeventsmanager.KubeEventsManager) EnableKubernetesBindings() ([]BindingExecutionInfo, error) UpdateMonitor(monitorId string, kind, apiVersion string) error UnlockEvents() @@ -47,7 +47,7 @@ type kubernetesBindingsController struct { KubernetesBindings []OnKubernetesEventConfig // dependencies - kubeEventsManager kube_events_manager.KubeEventsManager + kubeEventsManager kubeeventsmanager.KubeEventsManager logger *log.Logger } @@ -67,7 +67,7 @@ func (c *kubernetesBindingsController) WithKubernetesBindings(bindings []OnKuber c.KubernetesBindings = bindings } -func (c *kubernetesBindingsController) WithKubeEventsManager(kubeEventsManager kube_events_manager.KubeEventsManager) { +func (c *kubernetesBindingsController) WithKubeEventsManager(kubeEventsManager kubeeventsmanager.KubeEventsManager) { c.kubeEventsManager = kubeEventsManager } diff --git a/pkg/hook/controller/schedule_bindings_controller.go b/pkg/hook/controller/schedule_bindings_controller.go index 3f83438a..6d5bdeb4 100644 --- a/pkg/hook/controller/schedule_bindings_controller.go +++ b/pkg/hook/controller/schedule_bindings_controller.go @@ -1,8 +1,9 @@ package controller import ( - . "github.com/flant/shell-operator/pkg/hook/binding_context" - . "github.com/flant/shell-operator/pkg/hook/types" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/flant/shell-operator/pkg/schedule_manager" ) diff --git a/pkg/hook/hook.go b/pkg/hook/hook.go index 803c55b1..75e801c7 100644 --- a/pkg/hook/hook.go +++ b/pkg/hook/hook.go @@ -9,19 +9,19 @@ import ( "strings" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/metric-storage/operation" + "github.com/deckhouse/module-sdk/pkg/webhook/admission" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" uuid "github.com/gofrs/uuid/v5" "github.com/kennygrant/sanitize" "golang.org/x/time/rate" "github.com/flant/shell-operator/pkg/app" "github.com/flant/shell-operator/pkg/executor" - . "github.com/flant/shell-operator/pkg/hook/binding_context" "github.com/flant/shell-operator/pkg/hook/config" "github.com/flant/shell-operator/pkg/hook/controller" - . "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/metric_storage/operation" - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" ) type CommonHook interface { diff --git a/pkg/hook/hook_manager.go b/pkg/hook/hook_manager.go index 5d31c725..4215fde8 100644 --- a/pkg/hook/hook_manager.go +++ b/pkg/hook/hook_manager.go @@ -9,24 +9,24 @@ import ( "strings" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + "github.com/deckhouse/module-sdk/pkg/webhook/admission" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "github.com/flant/shell-operator/pkg/executor" "github.com/flant/shell-operator/pkg/hook/controller" - . "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/kube_events_manager" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" "github.com/flant/shell-operator/pkg/schedule_manager" utils_file "github.com/flant/shell-operator/pkg/utils/file" - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" ) type Manager struct { // dependencies workingDir string tempDir string - kubeEventsManager kube_events_manager.KubeEventsManager + kubeEventsManager kubeeventsmanager.KubeEventsManager scheduleManager schedule_manager.ScheduleManager conversionWebhookManager *conversion.WebhookManager admissionWebhookManager *admission.WebhookManager @@ -49,7 +49,7 @@ type Manager struct { type ManagerConfig struct { WorkingDir string TempDir string - Kmgr kube_events_manager.KubeEventsManager + Kmgr kubeeventsmanager.KubeEventsManager Smgr schedule_manager.ScheduleManager Wmgr *admission.WebhookManager Cmgr *conversion.WebhookManager diff --git a/pkg/hook/hook_manager_test.go b/pkg/hook/hook_manager_test.go index f98d7a73..b2f3c28c 100644 --- a/pkg/hook/hook_manager_test.go +++ b/pkg/hook/hook_manager_test.go @@ -6,13 +6,13 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/webhook/admission" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" . "github.com/onsi/gomega" "github.com/flant/shell-operator/pkg/app" "github.com/flant/shell-operator/pkg/hook/controller" - "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" ) func newHookManager(t *testing.T, testdataDir string) *Manager { diff --git a/pkg/hook/hook_test.go b/pkg/hook/hook_test.go index b7c28cf8..c7fa67da 100644 --- a/pkg/hook/hook_test.go +++ b/pkg/hook/hook_test.go @@ -6,11 +6,11 @@ import ( "time" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/types" . "github.com/onsi/gomega" "golang.org/x/time/rate" "github.com/flant/shell-operator/pkg/hook/config" - . "github.com/flant/shell-operator/pkg/hook/types" ) func Test_Hook_SafeName(t *testing.T) { diff --git a/pkg/hook/task_metadata/task_metadata.go b/pkg/hook/task_metadata/task_metadata.go index 0b0a3b38..eef2e649 100644 --- a/pkg/hook/task_metadata/task_metadata.go +++ b/pkg/hook/task_metadata/task_metadata.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + "github.com/deckhouse/module-sdk/pkg/hook/types" - "github.com/flant/shell-operator/pkg/hook/binding_context" - "github.com/flant/shell-operator/pkg/hook/types" "github.com/flant/shell-operator/pkg/task" ) @@ -22,7 +22,7 @@ type HookNameAccessor interface { } type BindingContextAccessor interface { - GetBindingContext() []binding_context.BindingContext + GetBindingContext() []bindingcontext.BindingContext } type MonitorIDAccessor interface { @@ -34,7 +34,7 @@ type HookMetadata struct { Binding string // binding name Group string BindingType types.BindingType - BindingContext []binding_context.BindingContext + BindingContext []bindingcontext.BindingContext AllowFailure bool // Task considered as 'ok' if hook failed. False by default. Can be true for some schedule hooks. MonitorIDs []string // monitor ids for Synchronization tasks @@ -66,7 +66,7 @@ func (m HookMetadata) GetHookName() string { return m.HookName } -func (m HookMetadata) GetBindingContext() []binding_context.BindingContext { +func (m HookMetadata) GetBindingContext() []bindingcontext.BindingContext { return m.BindingContext } @@ -88,12 +88,12 @@ func (m *HookMetadata) WithBinding(binding types.BindingType) *HookMetadata { return m } -func (m *HookMetadata) WithBindingContext(context []binding_context.BindingContext) *HookMetadata { +func (m *HookMetadata) WithBindingContext(context []bindingcontext.BindingContext) *HookMetadata { m.BindingContext = context return m } -func (m *HookMetadata) AppendBindingContext(context binding_context.BindingContext) *HookMetadata { +func (m *HookMetadata) AppendBindingContext(context bindingcontext.BindingContext) *HookMetadata { m.BindingContext = append(m.BindingContext, context) return m } diff --git a/pkg/hook/task_metadata/task_metadata_test.go b/pkg/hook/task_metadata/task_metadata_test.go index ce3dd0e2..bc1569ae 100644 --- a/pkg/hook/task_metadata/task_metadata_test.go +++ b/pkg/hook/task_metadata/task_metadata_test.go @@ -5,10 +5,10 @@ import ( "strings" "testing" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + . "github.com/deckhouse/module-sdk/pkg/hook/types" . "github.com/onsi/gomega" - . "github.com/flant/shell-operator/pkg/hook/binding_context" - . "github.com/flant/shell-operator/pkg/hook/types" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" ) diff --git a/pkg/hook/types/bindings.go b/pkg/hook/types/bindings.go deleted file mode 100644 index c32b43ab..00000000 --- a/pkg/hook/types/bindings.go +++ /dev/null @@ -1,77 +0,0 @@ -package types - -import ( - "time" - - "github.com/flant/shell-operator/pkg/kube_events_manager" - . "github.com/flant/shell-operator/pkg/schedule_manager/types" - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" -) - -type BindingType string - -const ( - Schedule BindingType = "schedule" - OnStartup BindingType = "onStartup" - OnKubernetesEvent BindingType = "kubernetes" - KubernetesConversion BindingType = "kubernetesCustomResourceConversion" - KubernetesValidating BindingType = "kubernetesValidating" - KubernetesMutating BindingType = "kubernetesMutating" -) - -// Types for effective binding configs -type CommonBindingConfig struct { - BindingName string - AllowFailure bool -} - -type OnStartupConfig struct { - CommonBindingConfig - Order float64 -} - -type ScheduleConfig struct { - CommonBindingConfig - ScheduleEntry ScheduleEntry - IncludeSnapshotsFrom []string - Queue string - Group string -} - -type OnKubernetesEventConfig struct { - CommonBindingConfig - Monitor *kube_events_manager.MonitorConfig - IncludeSnapshotsFrom []string - Queue string - Group string - ExecuteHookOnSynchronization bool - WaitForSynchronization bool - KeepFullObjectsInMemory bool -} - -type ConversionConfig struct { - CommonBindingConfig - IncludeSnapshotsFrom []string - Group string - Webhook *conversion.WebhookConfig -} - -type ValidatingConfig struct { - CommonBindingConfig - IncludeSnapshotsFrom []string - Group string - Webhook *admission.ValidatingWebhookConfig -} - -type MutatingConfig struct { - CommonBindingConfig - IncludeSnapshotsFrom []string - Group string - Webhook *admission.MutatingWebhookConfig -} - -type Settings struct { - ExecutionMinInterval time.Duration - ExecutionBurst int -} diff --git a/pkg/kube/object_patch/helpers.go b/pkg/kube/object_patch/helpers.go deleted file mode 100644 index 507c8374..00000000 --- a/pkg/kube/object_patch/helpers.go +++ /dev/null @@ -1,144 +0,0 @@ -package object_patch - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - - "gopkg.in/yaml.v3" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - k8yaml "sigs.k8s.io/yaml" - - "github.com/flant/kube-client/manifest" - "github.com/flant/shell-operator/pkg/app" - "github.com/flant/shell-operator/pkg/jq" -) - -func unmarshalFromJSONOrYAML(specs []byte) ([]OperationSpec, error) { - fromJsonSpecs, err := unmarshalFromJson(specs) - if err != nil { - return unmarshalFromYaml(specs) - } - - return fromJsonSpecs, nil -} - -func unmarshalFromJson(jsonSpecs []byte) ([]OperationSpec, error) { - var specSlice []OperationSpec - - dec := json.NewDecoder(bytes.NewReader(jsonSpecs)) - for { - var doc OperationSpec - err := dec.Decode(&doc) - if err == io.EOF { - break - } - if err != nil { - return nil, err - } - - specSlice = append(specSlice, doc) - } - - return specSlice, nil -} - -func unmarshalFromYaml(yamlSpecs []byte) ([]OperationSpec, error) { - var specSlice []OperationSpec - - dec := yaml.NewDecoder(bytes.NewReader(yamlSpecs)) - for { - var doc OperationSpec - err := dec.Decode(&doc) - if err == io.EOF { - break - } - if err != nil { - return nil, err - } - - specSlice = append(specSlice, doc) - } - - return specSlice, nil -} - -func applyJQPatch(jqFilter string, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - objBytes, err := obj.MarshalJSON() - if err != nil { - return nil, err - } - - filterResult, err := jq.ApplyJqFilter(jqFilter, objBytes, app.JqLibraryPath) - if err != nil { - return nil, fmt.Errorf("failed to apply jqFilter:\n%sto Object:\n%s\n"+ - "error: %s", jqFilter, obj, err) - } - - retObj := &unstructured.Unstructured{} - _, _, err = unstructured.UnstructuredJSONScheme.Decode([]byte(filterResult), nil, retObj) - if err != nil { - return nil, fmt.Errorf("failed to convert filterResult:\n%s\nto Unstructured Object\nerror: %s", filterResult, err) - } - - return retObj, nil -} - -func generateSubresources(subresource string) (ret []string) { - if subresource != "" { - ret = append(ret, subresource) - } - - return -} - -func toUnstructured(obj interface{}) (*unstructured.Unstructured, error) { - switch v := obj.(type) { - case []byte: - mft, err := manifest.NewFromYAML(string(v)) - if err != nil { - return nil, err - } - return mft.Unstructured(), nil - case string: - mft, err := manifest.NewFromYAML(v) - if err != nil { - return nil, err - } - return mft.Unstructured(), nil - case map[string]interface{}: - return &unstructured.Unstructured{Object: v}, nil - default: - objectContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) - if err != nil { - return nil, fmt.Errorf("convert to unstructured: %v", err) - } - return &unstructured.Unstructured{Object: objectContent}, nil - } -} - -func convertPatchToBytes(patch interface{}) ([]byte, error) { - var err error - var intermediate interface{} - switch v := patch.(type) { - case []byte: - err = k8yaml.Unmarshal(v, &intermediate) - case string: - err = k8yaml.Unmarshal([]byte(v), &intermediate) - default: - intermediate = v - } - if err != nil { - return nil, err - } - - // Try to encode to JSON. - var patchBytes []byte - patchBytes, err = json.Marshal(intermediate) - if err != nil { - return nil, err - } - return patchBytes, nil -} diff --git a/pkg/kube/object_patch/operation.go b/pkg/kube/object_patch/operation.go deleted file mode 100644 index e58ea968..00000000 --- a/pkg/kube/object_patch/operation.go +++ /dev/null @@ -1,288 +0,0 @@ -package object_patch - -import ( - "fmt" - - "github.com/deckhouse/deckhouse/pkg/log" - "github.com/hashicorp/go-multierror" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" -) - -// A JSON and YAML representation of the operation for shell hooks -type OperationSpec struct { - Operation OperationType `json:"operation" yaml:"operation"` - ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Subresource string `json:"subresource,omitempty" yaml:"subresource,omitempty"` - - Object interface{} `json:"object,omitempty" yaml:"object,omitempty"` - JQFilter string `json:"jqFilter,omitempty" yaml:"jqFilter,omitempty"` - MergePatch interface{} `json:"mergePatch,omitempty" yaml:"mergePatch,omitempty"` - JSONPatch interface{} `json:"jsonPatch,omitempty" yaml:"jsonPatch,omitempty"` - - IgnoreMissingObject bool `json:"ignoreMissingObject" yaml:"ignoreMissingObject"` - IgnoreHookError bool `json:"ignoreHookError" yaml:"ignoreHookError"` -} - -type OperationType string - -const ( - CreateOrUpdate OperationType = "CreateOrUpdate" - Create OperationType = "Create" - CreateIfNotExists OperationType = "CreateIfNotExists" - - Delete OperationType = "Delete" - DeleteInBackground OperationType = "DeleteInBackground" - DeleteNonCascading OperationType = "DeleteNonCascading" - - JQPatch OperationType = "JQPatch" - MergePatch OperationType = "MergePatch" - JSONPatch OperationType = "JSONPatch" -) - -// GetPatchStatusOperationsOnHookError returns list of Patch/Filter operations eligible for execution on Hook Error -func GetPatchStatusOperationsOnHookError(operations []Operation) []Operation { - patchStatusOperations := make([]Operation, 0) - for _, op := range operations { - switch operation := op.(type) { - case *filterOperation: - if operation.subresource == "/status" && operation.ignoreHookError { - patchStatusOperations = append(patchStatusOperations, operation) - } - case *patchOperation: - if operation.subresource == "/status" && operation.ignoreHookError { - patchStatusOperations = append(patchStatusOperations, operation) - } - } - } - - return patchStatusOperations -} - -func ParseOperations(specBytes []byte) ([]Operation, error) { - log.Debugf("parsing patcher operations:\n%s", specBytes) - - specs, err := unmarshalFromJSONOrYAML(specBytes) - if err != nil { - return nil, err - } - - validationErrors := &multierror.Error{} - ops := make([]Operation, 0) - for _, spec := range specs { - err = ValidateOperationSpec(spec, GetSchema("v0"), "") - if err != nil { - validationErrors = multierror.Append(validationErrors, err) - break - } - ops = append(ops, NewFromOperationSpec(spec)) - } - - return ops, validationErrors.ErrorOrNil() -} - -// Operation is a command for ObjectPatcher. -// -// There are 4 types of operations: -// -// - createOperation to create or update object via Create and Update API calls. Unstructured, map[string]interface{} or runtime.Object is required. -// -// - deleteOperation to delete object via Delete API call. deletionPropagation should be set, default is Foregound. -// -// - patchOperation to modify object via Patch API call. patchType should be set. patch can be string, []byte or map[string]interface{} -// -// - filterOperation to modify object via Get-filter-Update process. filterFunc should be set. -type Operation interface { - Description() string -} - -type createOperation struct { - object interface{} - subresource string - - ignoreIfExists bool - updateIfExists bool -} - -func (op *createOperation) Description() string { - return "Create object" -} - -type deleteOperation struct { - // Object coordinates. - apiVersion string - kind string - namespace string - name string - subresource string - - // Delete options. - deletionPropagation metav1.DeletionPropagation -} - -func (op *deleteOperation) Description() string { - return fmt.Sprintf("Delete object %s/%s/%s/%s", op.apiVersion, op.kind, op.namespace, op.name) -} - -type patchOperation struct { - // Object coordinates for patch and delete. - apiVersion string - kind string - namespace string - name string - subresource string - - // Patch options. - patchType types.PatchType - patch interface{} - ignoreMissingObject bool - ignoreHookError bool -} - -func (op *patchOperation) Description() string { - return fmt.Sprintf("Patch object %s/%s/%s/%s using %s patch", op.apiVersion, op.kind, op.namespace, op.name, op.patchType) -} - -type filterOperation struct { - // Object coordinates for patch and delete. - apiVersion string - kind string - namespace string - name string - subresource string - - // Patch options. - filterFunc func(*unstructured.Unstructured) (*unstructured.Unstructured, error) - ignoreMissingObject bool - ignoreHookError bool -} - -func (op *filterOperation) Description() string { - return fmt.Sprintf("Filter object %s/%s/%s/%s", op.apiVersion, op.kind, op.namespace, op.name) -} - -func NewFromOperationSpec(spec OperationSpec) Operation { - switch spec.Operation { - case Create: - return NewCreateOperation(spec.Object, - WithSubresource(spec.Subresource)) - case CreateIfNotExists: - return NewCreateOperation(spec.Object, - WithSubresource(spec.Subresource), - IgnoreIfExists()) - case CreateOrUpdate: - return NewCreateOperation(spec.Object, - WithSubresource(spec.Subresource), - UpdateIfExists()) - case Delete: - return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource)) - case DeleteInBackground: - return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - InBackground()) - case DeleteNonCascading: - return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - NonCascading()) - case JQPatch: - return NewFilterPatchOperation( - func(u *unstructured.Unstructured) (*unstructured.Unstructured, error) { - return applyJQPatch(spec.JQFilter, u) - }, - spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - WithIgnoreMissingObject(spec.IgnoreMissingObject), - WithIgnoreHookError(spec.IgnoreHookError), - ) - case MergePatch: - return NewMergePatchOperation(spec.MergePatch, - spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - WithIgnoreMissingObject(spec.IgnoreMissingObject), - WithIgnoreHookError(spec.IgnoreHookError), - ) - case JSONPatch: - return NewJSONPatchOperation(spec.JSONPatch, - spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - WithIgnoreMissingObject(spec.IgnoreMissingObject), - WithIgnoreHookError(spec.IgnoreHookError), - ) - } - - // Should not be reached! - return nil -} - -func NewCreateOperation(obj interface{}, options ...CreateOption) Operation { - op := &createOperation{ - object: obj, - } - for _, option := range options { - option.applyToCreate(op) - } - return op -} - -func NewDeleteOperation(apiVersion, kind, namespace, name string, options ...DeleteOption) Operation { - op := &deleteOperation{ - apiVersion: apiVersion, - kind: kind, - namespace: namespace, - name: name, - deletionPropagation: metav1.DeletePropagationForeground, - } - for _, option := range options { - option.applyToDelete(op) - } - return op -} - -func NewMergePatchOperation(mergePatch interface{}, apiVersion, kind, namespace, name string, options ...PatchOption) Operation { - op := &patchOperation{ - apiVersion: apiVersion, - kind: kind, - namespace: namespace, - name: name, - patch: mergePatch, - patchType: types.MergePatchType, - } - for _, option := range options { - option.applyToPatch(op) - } - return op -} - -func NewJSONPatchOperation(jsonPatch interface{}, apiVersion, kind, namespace, name string, options ...PatchOption) Operation { - op := &patchOperation{ - apiVersion: apiVersion, - kind: kind, - namespace: namespace, - name: name, - patch: jsonPatch, - patchType: types.JSONPatchType, - } - for _, option := range options { - option.applyToPatch(op) - } - return op -} - -func NewFilterPatchOperation(filter func(*unstructured.Unstructured) (*unstructured.Unstructured, error), apiVersion, kind, namespace, name string, options ...FilterOption) Operation { - op := &filterOperation{ - apiVersion: apiVersion, - kind: kind, - namespace: namespace, - name: name, - filterFunc: filter, - } - for _, option := range options { - option.applyToFilter(op) - } - return op -} diff --git a/pkg/kube/object_patch/options.go b/pkg/kube/object_patch/options.go deleted file mode 100644 index c6900ee9..00000000 --- a/pkg/kube/object_patch/options.go +++ /dev/null @@ -1,137 +0,0 @@ -package object_patch - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type CreateOption interface { - applyToCreate(operation *createOperation) -} - -type DeleteOption interface { - applyToDelete(operation *deleteOperation) -} - -type PatchOption interface { - applyToPatch(operation *patchOperation) -} - -type FilterOption interface { - applyToFilter(operation *filterOperation) -} - -type subresourceHolder struct { - subresource string -} - -// WithSubresource options specifies a subresource to operate on. -func WithSubresource(s string) *subresourceHolder { - return &subresourceHolder{subresource: s} -} - -func (s *subresourceHolder) applyToCreate(operation *createOperation) { - operation.subresource = s.subresource -} - -func (s *subresourceHolder) applyToDelete(operation *deleteOperation) { - operation.subresource = s.subresource -} - -func (s *subresourceHolder) applyToPatch(operation *patchOperation) { - operation.subresource = s.subresource -} - -func (s *subresourceHolder) applyToFilter(operation *filterOperation) { - operation.subresource = s.subresource -} - -type ignoreHookError struct { - ignoreError bool -} - -// IgnoreHookError allows applying patches for a Status subresource even if the hook fails -func IgnoreHookError() *ignoreHookError { - return WithIgnoreHookError(true) -} - -func WithIgnoreHookError(ignoreError bool) *ignoreHookError { - return &ignoreHookError{ignoreError: ignoreError} -} - -type ignoreMissingObject struct { - ignore bool -} - -func (i *ignoreHookError) applyToPatch(operation *patchOperation) { - operation.ignoreHookError = i.ignoreError -} - -func (i *ignoreHookError) applyToFilter(operation *filterOperation) { - operation.ignoreHookError = i.ignoreError -} - -// IgnoreMissingObject do not return error if object exists for Patch and Filter operations. -func IgnoreMissingObject() *ignoreMissingObject { - return WithIgnoreMissingObject(true) -} - -func WithIgnoreMissingObject(ignore bool) *ignoreMissingObject { - return &ignoreMissingObject{ignore: ignore} -} - -func (i *ignoreMissingObject) applyToPatch(operation *patchOperation) { - operation.ignoreMissingObject = i.ignore -} - -func (i *ignoreMissingObject) applyToFilter(operation *filterOperation) { - operation.ignoreMissingObject = i.ignore -} - -type ignoreIfExists struct { - ignore bool -} - -// IgnoreIfExists is an option for Create to not return error if object is already exists. -func IgnoreIfExists() CreateOption { - return &ignoreIfExists{ignore: true} -} - -func (i *ignoreIfExists) applyToCreate(operation *createOperation) { - operation.ignoreIfExists = i.ignore -} - -type updateIfExists struct { - update bool -} - -// UpdateIfExists is an option for Create to update object if it already exists. -func UpdateIfExists() CreateOption { - return &updateIfExists{update: true} -} - -func (u *updateIfExists) applyToCreate(operation *createOperation) { - operation.updateIfExists = u.update -} - -type deletePropogation struct { - propagation metav1.DeletionPropagation -} - -func (d *deletePropogation) applyToDelete(operation *deleteOperation) { - operation.deletionPropagation = d.propagation -} - -// InForeground is a default propagation option for Delete -func InForeground() DeleteOption { - return &deletePropogation{propagation: metav1.DeletePropagationForeground} -} - -// InBackground is a propagation option for Delete -func InBackground() DeleteOption { - return &deletePropogation{propagation: metav1.DeletePropagationBackground} -} - -// NonCascading is a propagation option for Delete -func NonCascading() DeleteOption { - return &deletePropogation{propagation: metav1.DeletePropagationOrphan} -} diff --git a/pkg/kube/object_patch/patch.go b/pkg/kube/object_patch/patch.go deleted file mode 100644 index f46e22b7..00000000 --- a/pkg/kube/object_patch/patch.go +++ /dev/null @@ -1,301 +0,0 @@ -package object_patch - -import ( - "bytes" - "context" - "fmt" - "time" - - "github.com/deckhouse/deckhouse/pkg/log" - "github.com/hashicorp/go-multierror" - gerror "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/util/retry" -) - -type ObjectPatcher struct { - kubeClient KubeClient - logger *log.Logger -} - -type KubeClient interface { - kubernetes.Interface - Dynamic() dynamic.Interface - GroupVersionResource(apiVersion string, kind string) (schema.GroupVersionResource, error) -} - -func NewObjectPatcher(kubeClient KubeClient, logger *log.Logger) *ObjectPatcher { - return &ObjectPatcher{ - kubeClient: kubeClient, - logger: logger.With("operator.component", "KubernetesObjectPatcher"), - } -} - -func (o *ObjectPatcher) ExecuteOperations(ops []Operation) error { - log.Debug("Starting execute operations process") - defer log.Debug("Finished execute operations process") - - applyErrors := &multierror.Error{} - for _, op := range ops { - log.Debugf("Applying operation: %s", op.Description()) - if err := o.ExecuteOperation(op); err != nil { - err = gerror.WithMessage(err, op.Description()) - applyErrors = multierror.Append(applyErrors, err) - } - } - - return applyErrors.ErrorOrNil() -} - -func (o *ObjectPatcher) ExecuteOperation(operation Operation) error { - if operation == nil { - return nil - } - - switch v := operation.(type) { - case *createOperation: - return o.executeCreateOperation(v) - case *deleteOperation: - return o.executeDeleteOperation(v) - case *patchOperation: - return o.executePatchOperation(v) - case *filterOperation: - return o.executeFilterOperation(v) - } - - return nil -} - -func (o *ObjectPatcher) executeCreateOperation(op *createOperation) error { - if op.object == nil { - return fmt.Errorf("cannot create empty object") - } - - // Convert object from interface{}. - object, err := toUnstructured(op.object) - if err != nil { - return err - } - - apiVersion := object.GetAPIVersion() - kind := object.GetKind() - - wrapErr := func(e error) error { - objectID := fmt.Sprintf("%s/%s/%s/%s", apiVersion, kind, object.GetNamespace(), object.GetName()) - return gerror.WithMessage(e, objectID) - } - - gvk, err := o.kubeClient.GroupVersionResource(apiVersion, kind) - if err != nil { - return wrapErr(err) - } - - log.Debug("Started Create API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(object.GetNamespace()). - Create(context.TODO(), object, metav1.CreateOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Create API call") - - objectExists := errors.IsAlreadyExists(err) - - if objectExists && op.ignoreIfExists { - log.Debug("resource already exists, exiting without error") - return nil - } - - if objectExists && op.updateIfExists { - log.Debug("Object already exists, attempting to Update it with optimistic lock") - - return retry.RetryOnConflict(retry.DefaultBackoff, func() error { - log.Debug("Started Get API call") - existingObj, err := o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(object.GetNamespace()). - Get(context.TODO(), object.GetName(), metav1.GetOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Get API call") - if err != nil { - return wrapErr(err) - } - - objCopy := object.DeepCopy() - objCopy.SetResourceVersion(existingObj.GetResourceVersion()) - - log.Debug("Started Update API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(objCopy.GetNamespace()). - Update(context.TODO(), objCopy, metav1.UpdateOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Update API call") - return wrapErr(err) - }) - } - - // Simply return result of a Create call if no ignore options are in play. - return wrapErr(err) -} - -// executePatchOperation applies a patch to the specified object using API call Patch. -// -// There 2 types of patches: -// - Merge — use Patch API call with MergePatchType. -// - JSON — use Patch API call with JSONPatchType. -// -// Other options: -// - WithSubresource — a subresource argument for Patch or Update API call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -func (o *ObjectPatcher) executePatchOperation(op *patchOperation) error { - if op.patchType == types.MergePatchType { - log.Debug("Started MergePatchObject") - defer log.Debug("Finished MergePatchObject") - } - if op.patchType == types.JSONPatchType { - log.Debug("Started JSONPatchObject") - defer log.Debug("Finished JSONPatchObject") - } - - patchBytes, err := convertPatchToBytes(op.patch) - if err != nil { - return fmt.Errorf("encode %s patch for %s/%s/%s/%s: %v", op.patchType, op.apiVersion, op.kind, op.namespace, op.name, err) - } - if patchBytes == nil { - return fmt.Errorf("%s patch is nil for %s/%s/%s/%s", op.patchType, op.apiVersion, op.kind, op.namespace, op.name) - } - - gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) - if err != nil { - return err - } - - log.Debug("Started Patch API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Patch(context.TODO(), op.name, op.patchType, patchBytes, metav1.PatchOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Patch API call") - - if op.ignoreMissingObject && errors.IsNotFound(err) { - return nil - } - return err -} - -// executeFilterOperation retrieves a specified object, modified it with -// filterFunc and calls update. - -// Other options: -// - WithSubresource — a subresource argument for Patch or Update API call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -func (o *ObjectPatcher) executeFilterOperation(op *filterOperation) error { - var err error - - if op.filterFunc == nil { - return fmt.Errorf("FilterFunc is nil") - } - - gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) - if err != nil { - return err - } - - err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { - log.Debug("Started Get API call") - obj, err := o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Get(context.TODO(), op.name, metav1.GetOptions{}) - log.Debug("Finished Get API call") - if op.ignoreMissingObject && errors.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - - log.Debug("Started filtering object") - filteredObj, err := op.filterFunc(obj) - log.Debug("Finished filtering object") - if err != nil { - return err - } - - if equality.Semantic.DeepEqual(obj, filteredObj) { - return nil - } - - var filteredObjBuf bytes.Buffer - err = unstructured.UnstructuredJSONScheme.Encode(filteredObj, &filteredObjBuf) - if err != nil { - return err - } - - log.Debug("Started Update API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Update(context.TODO(), filteredObj, metav1.UpdateOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Update API call") - if err != nil { - return err - } - - return nil - }) - - return err -} - -func (o *ObjectPatcher) executeDeleteOperation(op *deleteOperation) error { - gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) - if err != nil { - return err - } - - log.Debug("Started Delete API call") - err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Delete(context.TODO(), op.name, metav1.DeleteOptions{PropagationPolicy: &op.deletionPropagation}, op.subresource) - - log.Debug("Finished Delete API call") - if errors.IsNotFound(err) { - return nil - } - - if err != nil { - return err - } - - if op.deletionPropagation != metav1.DeletePropagationForeground { - return nil - } - - log.Debug("Waiting for object deletion") - - err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 20*time.Second, false, func(ctx context.Context) (done bool, err error) { - log.Debug("Started Get API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Get(ctx, op.name, metav1.GetOptions{}) - - log.Debug("Finished Get API call") - if errors.IsNotFound(err) { - return true, nil - } - - return false, err - }) - - return err -} diff --git a/pkg/kube/object_patch/patch_collector.go b/pkg/kube/object_patch/patch_collector.go deleted file mode 100644 index 0d2c365e..00000000 --- a/pkg/kube/object_patch/patch_collector.go +++ /dev/null @@ -1,85 +0,0 @@ -package object_patch - -import ( - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -type PatchCollector struct { - patchOperations []Operation -} - -// NewPatchCollector creates Operation collector to use within Go hooks. -func NewPatchCollector() *PatchCollector { - return &PatchCollector{ - patchOperations: make([]Operation, 0), - } -} - -// Create or update an object. -// -// Options: -// - WithSubresource - create a specified subresource -// - IgnoreIfExists - do not return error if the specified object exists -// - UpdateIfExists - call Update if the specified object exists -func (dop *PatchCollector) Create(object interface{}, options ...CreateOption) { - dop.add(NewCreateOperation(object, options...)) -} - -// Delete uses apiVersion, kind, namespace and name to delete object from cluster. -// -// Options: -// - WithSubresource - delete a specified subresource -// - InForeground - remove object when all dependants are removed (default) -// - InBackground - remove object immediately, dependants remove in background -// - NonCascading - remove object, dependants become orphan -// -// Missing object is ignored by default. -func (dop *PatchCollector) Delete(apiVersion, kind, namespace, name string, options ...DeleteOption) { - dop.add(NewDeleteOperation(apiVersion, kind, namespace, name, options...)) -} - -// MergePatch applies a merge patch to the specified object using API call Patch. -// -// Options: -// - WithSubresource — a subresource argument for Patch call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -func (dop *PatchCollector) MergePatch(mergePatch interface{}, apiVersion, kind, namespace, name string, options ...PatchOption) { - dop.add(NewMergePatchOperation(mergePatch, apiVersion, kind, namespace, name, options...)) -} - -// JSONPatch applies a json patch to the specified object using API call Patch. -// -// Options: -// - WithSubresource — a subresource argument for Patch call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -func (dop *PatchCollector) JSONPatch(jsonPatch interface{}, apiVersion, kind, namespace, name string, options ...PatchOption) { - dop.add(NewJSONPatchOperation(jsonPatch, apiVersion, kind, namespace, name, options...)) -} - -// Filter retrieves a specified object, modified it with -// filterFunc and calls update. -// -// Options: -// - WithSubresource — a subresource argument for Patch call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -// -// Note: do not modify and return argument in filterFunc, -// use FromUnstructured to instantiate a concrete type or modify after DeepCopy. -func (dop *PatchCollector) Filter( - filterFunc func(*unstructured.Unstructured) (*unstructured.Unstructured, error), - apiVersion, kind, namespace, name string, options ...FilterOption, -) { - dop.add(NewFilterPatchOperation(filterFunc, apiVersion, kind, namespace, name, options...)) -} - -// Operations returns all collected operations -func (dop *PatchCollector) Operations() []Operation { - return dop.patchOperations -} - -func (dop *PatchCollector) add(operation Operation) { - dop.patchOperations = append(dop.patchOperations, operation) -} diff --git a/pkg/kube/object_patch/patch_test.go b/pkg/kube/object_patch/patch_test.go deleted file mode 100644 index bc0a29ce..00000000 --- a/pkg/kube/object_patch/patch_test.go +++ /dev/null @@ -1,949 +0,0 @@ -package object_patch - -import ( - "context" - "fmt" - "os" - "testing" - - "github.com/deckhouse/deckhouse/pkg/log" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/flant/kube-client/fake" - "github.com/flant/kube-client/manifest" -) - -func mustReadFile(t *testing.T, filePath string) []byte { - t.Helper() - content, err := os.ReadFile(filePath) - require.NoError(t, err) - return content -} - -func Test_ParseOperations(t *testing.T) { - const ( - shouldNotBeError = false - shouldBeError = true - ) - - tests := []struct { - name string - testFilePath string - expectError bool - }{ - { - "valid create", - "testdata/serialized_operations/valid_create.yaml", - shouldNotBeError, - }, - { - "invalid create", - "testdata/serialized_operations/invalid_create.yaml", - shouldBeError, - }, - { - "valid delete", - "testdata/serialized_operations/valid_delete.yaml", - shouldNotBeError, - }, - { - "invalid delete", - "testdata/serialized_operations/invalid_delete.yaml", - shouldBeError, - }, - { - "valid patch", - "testdata/serialized_operations/valid_patch.yaml", - shouldNotBeError, - }, - { - "invalid patch", - "testdata/serialized_operations/invalid_patch.yaml", - shouldBeError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testSpecs := mustReadFile(t, tt.testFilePath) - - _, err := ParseOperations(testSpecs) - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func Test_PatchOperations(t *testing.T) { - const ( - namespace = "default" - name = "testcm" - missingName = "missing-object" - configMap = ` -apiVersion: v1 -kind: ConfigMap -metadata: - name: testcm -data: - foo: "bar" -` - newField = "baz" - newValue = "quux" - shouldNotAdd = false - shouldAdd = true - shouldNotBeError = false - shouldBeError = true - ) - - // Filter func to add a new field. - filter := func(u *unstructured.Unstructured) (*unstructured.Unstructured, error) { - res := u.DeepCopy() - data := res.Object["data"].(map[string]interface{}) - data[newField] = newValue - res.Object["data"] = data - return res, nil - } - - tests := []struct { - name string - fn func(patcher *ObjectPatcher) error - expectAdd bool - expectError bool - }{ - { - "merge patch", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewMergePatchOperation( - fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), - "v1", "ConfigMap", namespace, name, - )) - }, - shouldAdd, - shouldNotBeError, - }, - { - "merge patch a missing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewMergePatchOperation( - fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), - "v1", "ConfigMap", namespace, missingName, - )) - }, - shouldNotAdd, - shouldBeError, - }, - { - "merge patch with ignoreMissingObject", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewMergePatchOperation( - fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), - "v1", "ConfigMap", namespace, missingName, - IgnoreMissingObject(), - )) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "merge patch using map", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewMergePatchOperation( - map[string]interface{}{ - "data": map[string]interface{}{ - newField: newValue, - }, - }, - "v1", "ConfigMap", namespace, name, - )) - }, - shouldAdd, - shouldNotBeError, - }, - { - "merge patch via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -mergePatch: - data: - %s: "%s" -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "merge patch a missing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -mergePatch: - data: - %s: "%s" -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldBeError, - }, - { - "merge patch with ignoreMissingObject via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -ignoreMissingObject: true -mergePatch: - data: - %s: "%s" -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "merge patch via string in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -mergePatch: | - data: - %s: "%s" -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "merge patch via stringified JSON in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -mergePatch: | - {"data":{"%s":"%s"}} -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "json patch", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewJSONPatchOperation( - // [{ "op": "replace", "path": "/data/firstField", "value": "jsonPatched"}] - fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), - "v1", "ConfigMap", namespace, name, - )) - }, - shouldAdd, - shouldNotBeError, - }, - { - "json patch a missing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewJSONPatchOperation( - fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), - "v1", "ConfigMap", namespace, missingName, - )) - }, - shouldNotAdd, - shouldBeError, - }, - { - "json patch with ignoreMissingObject", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewJSONPatchOperation( - fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), - "v1", "ConfigMap", namespace, missingName, - IgnoreMissingObject(), - )) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "json patch via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JSONPatch -kind: ConfigMap -namespace: %s -name: %s -jsonPatch: - - op: add - path: /data/%s - value: %s -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "json patch a missing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JSONPatch -kind: ConfigMap -namespace: %s -name: %s -jsonPatch: - - op: add - path: /data/%s - value: %s -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldBeError, - }, - { - "json patch with ignoreMissingObject via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JSONPatch -kind: ConfigMap -namespace: %s -name: %s -ignoreMissingObject: true -jsonPatch: - - op: add - path: /data/%s - value: %s -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "json patch via stringified JSON in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JSONPatch -kind: ConfigMap -namespace: %s -name: %s -jsonPatch: | - [{"op":"add", "path":"/data/%s", "value":"%s"}] -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "filter patch", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewFilterPatchOperation( - filter, - "v1", "ConfigMap", namespace, name, - )) - }, - shouldAdd, - shouldNotBeError, - }, - { - "filter patch missing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewFilterPatchOperation( - filter, - "v1", "ConfigMap", namespace, missingName, - )) - }, - shouldNotAdd, - shouldBeError, - }, - { - "filter patch with ignoreMissingObject", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewFilterPatchOperation( - filter, - "v1", "ConfigMap", namespace, missingName, - IgnoreMissingObject(), - )) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "JQ patch via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JQPatch -kind: ConfigMap -namespace: %s -name: %s -jqFilter: | - .data.%s = "%s" -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "JQ patch missing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JQPatch -kind: ConfigMap -namespace: %s -name: %s -jqFilter: | - .data.%s = "%s" -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldBeError, - }, - { - "JQ patch with ignoreMissingObject via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JQPatch -kind: ConfigMap -namespace: %s -name: %s -ignoreMissingObject: true -jqFilter: | - .data.%s = "%s" -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "update existing object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(fmt.Sprintf(` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: %s - name: %s -data: - foo: "bar" - %s: "%s" -`, namespace, name, newField, newValue)).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj, UpdateIfExists())) - }, - shouldAdd, - shouldNotBeError, - }, - { - "update existing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: CreateOrUpdate -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: "bar" - %s: "%s" -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Prepare fake cluster: create a Namespace and a ConfigMap. - cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, configMap) - - // Apply MergePatch: add a new field in data section. - patcher := NewObjectPatcher(cluster.Client, log.NewNop()) - - err := tt.fn(patcher) - - // Check error expectation. - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - // Fetch updated object and check fields in data section. - cmObj := new(v1.ConfigMap) - fetchObject(t, cluster, namespace, configMap, cmObj) - - require.Equal(t, "bar", cmObj.Data["foo"]) - if tt.expectAdd { - require.Equal(t, newValue, cmObj.Data[newField]) - } else { - require.NotContains(t, cmObj.Data, newField) - } - }) - } -} - -func Test_CreateOperations(t *testing.T) { - const ( - namespace = "default" - existingName = "testcm" - existingConfigMap = ` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: default - name: testcm -data: - foo: "bar" -` - newName = "newtestcm" - newConfigMap = ` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: default - name: newtestcm -data: - foo: "bar" -` - shouldNotCreateNew = false - shouldCreateNew = true - shouldNotBeError = false - shouldBeError = true - ) - - tests := []struct { - name string - fn func(patcher *ObjectPatcher) error - expectNewExists bool - expectError bool - }{ - { - "create new object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(newConfigMap).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj)) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create new object ignore existing object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(newConfigMap).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj, IgnoreIfExists())) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create existing object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(existingConfigMap).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj)) - }, - shouldNotCreateNew, - shouldBeError, - }, - { - "create ignore existing object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(existingConfigMap).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj, IgnoreIfExists())) - }, - shouldNotCreateNew, - shouldNotBeError, - }, - { - "create new object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Create -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create existing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Create -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, existingName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotCreateNew, - shouldBeError, - }, - { - "create ignore existing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: CreateIfNotExists -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, existingName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotCreateNew, - shouldNotBeError, - }, - { - "create or update new object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: CreateOrUpdate -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create new object via string in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Create -object: | - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create new object via stringified JSON in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Create -object: | - {"apiVersion":"v1", "kind":"ConfigMap", "metadata": { - "namespace":"%s", "name":"%s"}, - "data":{"foo":"bar"}} -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create new object via stringified JSON in JSON spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -{"operation": "Create", -"object": "{ - \"apiVersion\":\"v1\", \"kind\":\"ConfigMap\", - \"metadata\": {\"namespace\":\"%s\", \"name\":\"%s\"}, - \"data\":{\"foo\":\"bar\"}} -"} -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Prepare fake cluster: create a Namespace and a ConfigMap. - cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, existingConfigMap) - - // Apply MergePatch: add a new field in data section. - patcher := NewObjectPatcher(cluster.Client, log.NewNop()) - - err := tt.fn(patcher) - - // Check error expectation. - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - // Check if new object is created. - exists := existObject(t, cluster, namespace, newConfigMap) - - if tt.expectNewExists { - require.True(t, exists) - } else { - require.False(t, exists) - } - }) - } -} - -func Test_DeleteOperations(t *testing.T) { - const ( - namespace = "default" - existingName = "testcm" - existingConfigMap = ` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: default - name: testcm -data: - foo: "bar" -` - missingName = "missing-object" - - shouldNotDelete = false - shouldDelete = true - shouldNotBeError = false - shouldBeError = true - ) - - tests := []struct { - name string - fn func(patcher *ObjectPatcher) error - expectDeleted bool - expectError bool - }{ - { - "delete existing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewDeleteOperation("", "ConfigMap", namespace, existingName)) - }, - shouldDelete, - shouldNotBeError, - }, - { - "delete missing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewDeleteOperation("", "ConfigMap", namespace, missingName)) - }, - shouldNotDelete, - shouldNotBeError, - }, - { - "delete existing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Delete -kind: ConfigMap -namespace: %s -name: %s -`, namespace, existingName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldDelete, - shouldNotBeError, - }, - { - "delete missing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Delete -kind: ConfigMap -namespace: %s -name: %s -`, namespace, missingName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotDelete, - shouldNotBeError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Prepare fake cluster: create a Namespace and a ConfigMap. - cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, existingConfigMap) - - // Apply MergePatch: add a new field in data section. - patcher := NewObjectPatcher(cluster.Client, log.NewNop()) - - err := tt.fn(patcher) - - // Check error expectation. - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - // Check if new object is created. - exists := existObject(t, cluster, namespace, existingConfigMap) - - if tt.expectDeleted { - require.False(t, exists) - } else { - require.True(t, exists) - } - }) - } -} - -func newFakeClusterWithNamespaceAndObjects(t *testing.T, ns string, objects ...string) *fake.Cluster { - t.Helper() - - // Prepare fake cluster: create a Namespace and a ConfigMap. - cluster := fake.NewFakeCluster(fake.ClusterVersionV119) - cluster.CreateNs(ns) - - for _, object := range objects { - mft := manifest.MustFromYAML(object) - err := cluster.Create(ns, mft) - require.NoError(t, err) - } - - return cluster -} - -func fetchObject(t *testing.T, cluster *fake.Cluster, ns string, objYAML string, object interface{}) { - t.Helper() - mft, err := manifest.NewFromYAML(objYAML) - require.NoError(t, err) - - gvk := cluster.MustFindGVR(mft.ApiVersion(), mft.Kind()) - obj, err := cluster.Client.Dynamic().Resource(*gvk).Namespace(ns).Get(context.TODO(), mft.Name(), metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, obj) - - err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), object) - require.NoError(t, err) -} - -func existObject(t *testing.T, cluster *fake.Cluster, ns string, objYAML string) bool { - mft := manifest.MustFromYAML(objYAML) - gvk := cluster.MustFindGVR(mft.ApiVersion(), mft.Kind()) - obj, err := cluster.Client.Dynamic().Resource(*gvk).Namespace(ns).Get(context.TODO(), mft.Name(), metav1.GetOptions{}) - if errors.IsNotFound(err) { - return false - } - require.NoError(t, err) - return obj != nil -} diff --git a/pkg/kube/object_patch/testdata/serialized_operations/invalid_create.yaml b/pkg/kube/object_patch/testdata/serialized_operations/invalid_create.yaml deleted file mode 100644 index 319e2f4e..00000000 --- a/pkg/kube/object_patch/testdata/serialized_operations/invalid_create.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -operation: Create -namespace: default -object: ---- -operation: CreateOrUpdate -namespace: default -object: \ No newline at end of file diff --git a/pkg/kube/object_patch/testdata/serialized_operations/invalid_delete.yaml b/pkg/kube/object_patch/testdata/serialized_operations/invalid_delete.yaml deleted file mode 100644 index 926f9333..00000000 --- a/pkg/kube/object_patch/testdata/serialized_operations/invalid_delete.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -operation: Delete -kind: ConfigMap -name: "test" ---- -operation: DeleteInBackground -apiversion: core/v1 -name: "test" ---- -operation: DeleteNonCascading -apiversion: core/v1 -kind: ConfigMap diff --git a/pkg/kube/object_patch/testdata/serialized_operations/invalid_patch.yaml b/pkg/kube/object_patch/testdata/serialized_operations/invalid_patch.yaml deleted file mode 100644 index f7b042c3..00000000 --- a/pkg/kube/object_patch/testdata/serialized_operations/invalid_patch.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -operation: JQPatch -apiversion: core/v1 -kind: ConfigMap -name: "test" ---- -operation: MergePatch -apiversion: core/v1 -kind: ConfigMap -name: "test" ---- -operation: jsonPatch -apiversion: core/v1 -kind: ConfigMap -name: "test" diff --git a/pkg/kube/object_patch/testdata/serialized_operations/valid_create.yaml b/pkg/kube/object_patch/testdata/serialized_operations/valid_create.yaml deleted file mode 100644 index 8ed6904b..00000000 --- a/pkg/kube/object_patch/testdata/serialized_operations/valid_create.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -operation: Create -namespace: default -object: - apiVersion: core/v1 - kind: ConfigMap - data: - test: test ---- -operation: CreateOrUpdate -namespace: default -object: - apiVersion: core/v1 - kind: ConfigMap - data: - test: test \ No newline at end of file diff --git a/pkg/kube/object_patch/testdata/serialized_operations/valid_delete.yaml b/pkg/kube/object_patch/testdata/serialized_operations/valid_delete.yaml deleted file mode 100644 index 73ae807c..00000000 --- a/pkg/kube/object_patch/testdata/serialized_operations/valid_delete.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -operation: Delete -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" ---- -operation: DeleteInBackground -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" ---- -operation: DeleteNonCascading -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" \ No newline at end of file diff --git a/pkg/kube/object_patch/testdata/serialized_operations/valid_patch.yaml b/pkg/kube/object_patch/testdata/serialized_operations/valid_patch.yaml deleted file mode 100644 index 3a45eb76..00000000 --- a/pkg/kube/object_patch/testdata/serialized_operations/valid_patch.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -operation: JQPatch -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" -jqFilter: '.data = {"test": test}' ---- -operation: MergePatch -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" -mergePatch: - data: - test: test ---- -operation: JSONPatch -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" -jsonPatch: -- op: replace - path: /data - value: - test: test diff --git a/pkg/kube/object_patch/validation.go b/pkg/kube/object_patch/validation.go deleted file mode 100644 index e8055a67..00000000 --- a/pkg/kube/object_patch/validation.go +++ /dev/null @@ -1,214 +0,0 @@ -package object_patch - -import ( - "encoding/json" - "fmt" - - "github.com/go-openapi/spec" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" - "github.com/hashicorp/go-multierror" - "sigs.k8s.io/yaml" -) - -var Schemas = map[string]string{ - "v0": ` -definitions: - common: - type: object - properties: - subresource: - type: string - create: - required: - - object - properties: - object: - oneOf: - - type: object - additionalProperties: true - minProperties: 1 - - type: string - delete: - type: object - required: - - kind - - name - properties: - apiVersion: - type: string - kind: - type: string - name: - type: string - patch: - type: object - required: - - kind - - name - properties: - apiVersion: - type: string - kind: - type: string - name: - type: string - ignoreMissingObject: - type: boolean - ignoreHookError: - type: boolean - -type: object -additionalProperties: false -properties: - operation: {} - namespace: {} - subresource: {} - apiVersion: {} - kind: {} - name: {} - object: {} - jsonPatch: {} - jqFilter: {} - mergePatch: {} - ignoreMissingObject: {} - ignoreHookError: {} - -oneOf: -- allOf: - - properties: - operation: - type: string - enum: ["Create", "CreateOrUpdate", "CreateIfNotExists"] - - "$ref": "#/definitions/common" - - "$ref": "#/definitions/create" -- allOf: - - properties: - operation: - type: string - enum: ["Delete", "DeleteInBackground", "DeleteNonCascading"] - - "$ref": "#/definitions/common" - - "$ref": "#/definitions/delete" -- allOf: - - oneOf: - - required: - - operation - - jqFilter - properties: - operation: - type: string - enum: ["JQPatch"] - jqFilter: - type: string - minimum: 1 - - required: - - operation - - mergePatch - properties: - operation: - type: string - enum: ["MergePatch"] - mergePatch: - oneOf: - - type: object - minProperties: 1 - - type: string - - required: - - operation - - jsonPatch - properties: - operation: - type: string - enum: ["JSONPatch"] - jsonPatch: - oneOf: - - type: array - minItems: 1 - items: - - type: object - required: ["op", "path", "value"] - properties: - op: - type: string - minLength: 1 - path: - type: string - minLength: 1 - value: {} - - type: string - - "$ref": "#/definitions/common" - - "$ref": "#/definitions/patch" -`, -} - -var SchemasCache = map[string]*spec.Schema{} - -// GetSchema returns loaded schema. -func GetSchema(name string) *spec.Schema { - if s, ok := SchemasCache[name]; ok { - return s - } - if _, ok := Schemas[name]; !ok { - return nil - } - - // ignore error because load is guaranteed by tests - SchemasCache[name], _ = LoadSchema(name) - return SchemasCache[name] -} - -// LoadSchema returns spec.Schema object loaded from yaml in Schemas map. -func LoadSchema(name string) (*spec.Schema, error) { - yml, err := swag.BytesToYAMLDoc([]byte(Schemas[name])) - if err != nil { - return nil, fmt.Errorf("yaml unmarshal: %v", err) - } - d, err := swag.YAMLToJSON(yml) - if err != nil { - return nil, fmt.Errorf("yaml to json: %v", err) - } - - s := new(spec.Schema) - - if err := json.Unmarshal(d, s); err != nil { - return nil, fmt.Errorf("json unmarshal: %v", err) - } - - err = spec.ExpandSchema(s, s, nil /*new(noopResCache)*/) - if err != nil { - return nil, fmt.Errorf("expand schema: %v", err) - } - - return s, nil -} - -// See https://github.com/kubernetes/apiextensions-apiserver/blob/1bb376f70aa2c6f2dec9a8c7f05384adbfac7fbb/pkg/apiserver/validation/validation.go#L47 -func ValidateOperationSpec(obj interface{}, s *spec.Schema, rootName string) (multiErr error) { - if s == nil { - return fmt.Errorf("validate kubernetes patch spec: schema is not provided") - } - - validator := validate.NewSchemaValidator(s, nil, rootName, strfmt.Default) - - result := validator.Validate(obj) - if result.IsValid() { - return nil - } - - allErrs := &multierror.Error{Errors: make([]error, 1)} - for _, err := range result.Errors { - allErrs = multierror.Append(allErrs, err) - } - // NOTE: no validation errors, but kubernetes patch spec is not valid! - if allErrs.Len() == 1 { - allErrs = multierror.Append(allErrs, fmt.Errorf("kubernetes patch spec is not valid")) - } - - if allErrs.Len() > 1 { - yamlObj, _ := yaml.Marshal(obj) - allErrs.Errors[0] = fmt.Errorf("can't validate document:\n%s", yamlObj) - } - - return allErrs -} diff --git a/pkg/kube/object_patch/validation_test.go b/pkg/kube/object_patch/validation_test.go deleted file mode 100644 index 73d28580..00000000 --- a/pkg/kube/object_patch/validation_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package object_patch - -import ( - "testing" -) - -func Test_GetSchema(t *testing.T) { - schemas := []string{"v0"} - - for _, schema := range schemas { - s := GetSchema(schema) - if s == nil { - t.Fatalf("schema '%s' should not be nil", schema) - } - } -} - -func Test_LoadSchema_From_Schemas(t *testing.T) { - for schemaVer := range Schemas { - s, err := LoadSchema(schemaVer) - if err != nil { - t.Fatalf("schema '%s' should load: %v", schemaVer, err) - } - if s == nil { - t.Fatalf("schema '%s' should not be nil: %v", schemaVer, err) - } - } -} diff --git a/pkg/kube_events_manager/error_handler.go b/pkg/kube_events_manager/error_handler.go index 852b789e..143a4196 100644 --- a/pkg/kube_events_manager/error_handler.go +++ b/pkg/kube_events_manager/error_handler.go @@ -4,22 +4,22 @@ import ( "io" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/metric-storage" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/tools/cache" - "github.com/flant/shell-operator/pkg/metric_storage" utils "github.com/flant/shell-operator/pkg/utils/labels" ) type WatchErrorHandler struct { description string kind string - metricStorage *metric_storage.MetricStorage + metricStorage *metricstorage.MetricStorage logger *log.Logger } -func newWatchErrorHandler(description string, kind string, logLabels map[string]string, metricStorage *metric_storage.MetricStorage, logger *log.Logger) *WatchErrorHandler { +func newWatchErrorHandler(description string, kind string, logLabels map[string]string, metricStorage *metricstorage.MetricStorage, logger *log.Logger) *WatchErrorHandler { return &WatchErrorHandler{ description: description, kind: kind, diff --git a/pkg/kube_events_manager/filter.go b/pkg/kube_events_manager/filter.go index 2165f2c9..cff2c233 100644 --- a/pkg/kube_events_manager/filter.go +++ b/pkg/kube_events_manager/filter.go @@ -8,11 +8,11 @@ import ( "runtime" "runtime/trace" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/flant/shell-operator/pkg/app" "github.com/flant/shell-operator/pkg/jq" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" utils_checksum "github.com/flant/shell-operator/pkg/utils/checksum" ) diff --git a/pkg/kube_events_manager/kube_events_manager.go b/pkg/kube_events_manager/kube_events_manager.go index cc8b583c..57aeb912 100644 --- a/pkg/kube_events_manager/kube_events_manager.go +++ b/pkg/kube_events_manager/kube_events_manager.go @@ -6,14 +6,14 @@ import ( "sync" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + "github.com/deckhouse/module-sdk/pkg/metric-storage" klient "github.com/flant/kube-client/client" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" - "github.com/flant/shell-operator/pkg/metric_storage" ) type KubeEventsManager interface { - WithMetricStorage(mstor *metric_storage.MetricStorage) + WithMetricStorage(mstor *metricstorage.MetricStorage) AddMonitor(monitorConfig *MonitorConfig) error HasMonitor(monitorID string) bool GetMonitor(monitorID string) Monitor @@ -33,7 +33,7 @@ type kubeEventsManager struct { ctx context.Context cancel context.CancelFunc - metricStorage *metric_storage.MetricStorage + metricStorage *metricstorage.MetricStorage m sync.RWMutex Monitors map[string]Monitor @@ -59,7 +59,7 @@ func NewKubeEventsManager(ctx context.Context, client *klient.Client, logger *lo return em } -func (mgr *kubeEventsManager) WithMetricStorage(mstor *metric_storage.MetricStorage) { +func (mgr *kubeEventsManager) WithMetricStorage(mstor *metricstorage.MetricStorage) { mgr.metricStorage = mstor } diff --git a/pkg/kube_events_manager/kube_events_manager_test.go b/pkg/kube_events_manager/kube_events_manager_test.go index ed55b4d5..fb954287 100644 --- a/pkg/kube_events_manager/kube_events_manager_test.go +++ b/pkg/kube_events_manager/kube_events_manager_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -15,7 +16,6 @@ import ( fakediscovery "k8s.io/client-go/discovery/fake" klient "github.com/flant/kube-client/client" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" ) func Test_MainKubeEventsManager_Run(t *testing.T) { diff --git a/pkg/kube_events_manager/monitor.go b/pkg/kube_events_manager/monitor.go index 1cbc4b65..94a28de9 100644 --- a/pkg/kube_events_manager/monitor.go +++ b/pkg/kube_events_manager/monitor.go @@ -6,10 +6,10 @@ import ( "sort" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + "github.com/deckhouse/module-sdk/pkg/metric-storage" klient "github.com/flant/kube-client/client" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" - "github.com/flant/shell-operator/pkg/metric_storage" utils "github.com/flant/shell-operator/pkg/utils/labels" ) @@ -45,12 +45,12 @@ type monitor struct { ctx context.Context cancel context.CancelFunc - metricStorage *metric_storage.MetricStorage + metricStorage *metricstorage.MetricStorage logger *log.Logger } -func NewMonitor(ctx context.Context, client *klient.Client, mstor *metric_storage.MetricStorage, config *MonitorConfig, eventCb func(KubeEvent), logger *log.Logger) *monitor { +func NewMonitor(ctx context.Context, client *klient.Client, mstor *metricstorage.MetricStorage, config *MonitorConfig, eventCb func(KubeEvent), logger *log.Logger) *monitor { cctx, cancel := context.WithCancel(ctx) return &monitor{ diff --git a/pkg/kube_events_manager/monitor_config.go b/pkg/kube_events_manager/monitor_config.go index 08e87566..2ca70b63 100644 --- a/pkg/kube_events_manager/monitor_config.go +++ b/pkg/kube_events_manager/monitor_config.go @@ -2,10 +2,9 @@ package kube_events_manager import ( "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" ) // MonitorConfig is a config that suits the latest diff --git a/pkg/kube_events_manager/monitor_test.go b/pkg/kube_events_manager/monitor_test.go index b71d9a8b..3938ac32 100644 --- a/pkg/kube_events_manager/monitor_test.go +++ b/pkg/kube_events_manager/monitor_test.go @@ -6,13 +6,13 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/flant/kube-client/fake" "github.com/flant/kube-client/manifest" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" ) func Test_Monitor_should_handle_dynamic_ns_events(t *testing.T) { diff --git a/pkg/kube_events_manager/resource_informer.go b/pkg/kube_events_manager/resource_informer.go index 3f7d25c0..043b29ef 100644 --- a/pkg/kube_events_manager/resource_informer.go +++ b/pkg/kube_events_manager/resource_informer.go @@ -8,6 +8,8 @@ import ( "time" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + "github.com/deckhouse/module-sdk/pkg/metric-storage" "github.com/gofrs/uuid/v5" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -15,8 +17,6 @@ import ( "k8s.io/client-go/tools/cache" klient "github.com/flant/kube-client/client" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" - "github.com/flant/shell-operator/pkg/metric_storage" "github.com/flant/shell-operator/pkg/utils/measure" ) @@ -56,7 +56,7 @@ type resourceInformer struct { ctx context.Context cancel context.CancelFunc - metricStorage *metric_storage.MetricStorage + metricStorage *metricstorage.MetricStorage // a flag to stop handle events after Stop() stopped bool @@ -67,7 +67,7 @@ type resourceInformer struct { // resourceInformer should implement ResourceInformer type resourceInformerConfig struct { client *klient.Client - mstor *metric_storage.MetricStorage + mstor *metricstorage.MetricStorage eventCb func(KubeEvent) monitor *MonitorConfig diff --git a/pkg/kube_events_manager/util.go b/pkg/kube_events_manager/util.go index b4459d2e..60b146bc 100644 --- a/pkg/kube_events_manager/util.go +++ b/pkg/kube_events_manager/util.go @@ -5,11 +5,10 @@ import ( "math/rand/v2" "time" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" - - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" ) // ResourceId describes object with namespace, kind and name diff --git a/pkg/metric/collector_test.go b/pkg/metric/collector_test.go index 06ff1874..9478b5fc 100644 --- a/pkg/metric/collector_test.go +++ b/pkg/metric/collector_test.go @@ -1,7 +1,7 @@ package metric_test import ( - "github.com/flant/shell-operator/pkg/metric" + "github.com/deckhouse/module-sdk/pkg/metric" ) var ( diff --git a/pkg/metric/grouped_storage_mock.go b/pkg/metric/grouped_storage_mock.go index babe2e96..04b5c54a 100644 --- a/pkg/metric/grouped_storage_mock.go +++ b/pkg/metric/grouped_storage_mock.go @@ -2,7 +2,7 @@ package metric -//go:generate minimock -i github.com/flant/shell-operator/pkg/metric.GroupedStorage -o grouped_storage_mock.go -n GroupedStorageMock -p metric +//go:generate minimock -i github.com/deckhouse/module-sdk/pkg/metric.GroupedStorage -o grouped_storage_mock.go -n GroupedStorageMock -p metric import ( "sync" diff --git a/pkg/metric/storage.go b/pkg/metric/storage.go index 3bcf207b..e1b68e3a 100644 --- a/pkg/metric/storage.go +++ b/pkg/metric/storage.go @@ -1,9 +1,8 @@ package metric import ( + "github.com/deckhouse/module-sdk/pkg/metric-storage/operation" "github.com/prometheus/client_golang/prometheus" - - "github.com/flant/shell-operator/pkg/metric_storage/operation" ) type Storage interface { diff --git a/pkg/metric/storage_mock.go b/pkg/metric/storage_mock.go index 787e815b..195bbf75 100644 --- a/pkg/metric/storage_mock.go +++ b/pkg/metric/storage_mock.go @@ -2,14 +2,14 @@ package metric -//go:generate minimock -i github.com/flant/shell-operator/pkg/metric.Storage -o storage_mock.go -n StorageMock -p metric +//go:generate minimock -i github.com/deckhouse/module-sdk/pkg/metric.Storage -o storage_mock.go -n StorageMock -p metric import ( "sync" mm_atomic "sync/atomic" mm_time "time" - "github.com/flant/shell-operator/pkg/metric_storage/operation" + "github.com/deckhouse/module-sdk/pkg/metric-storage/operation" "github.com/gojuno/minimock/v3" "github.com/prometheus/client_golang/prometheus" ) diff --git a/pkg/metric/storage_test.go b/pkg/metric/storage_test.go index b7f907d8..55bcc76f 100644 --- a/pkg/metric/storage_test.go +++ b/pkg/metric/storage_test.go @@ -1,13 +1,13 @@ package metric_test import ( - "github.com/flant/shell-operator/pkg/metric" - "github.com/flant/shell-operator/pkg/metric_storage" - "github.com/flant/shell-operator/pkg/metric_storage/vault" + "github.com/deckhouse/module-sdk/pkg/metric" + "github.com/deckhouse/module-sdk/pkg/metric-storage" + "github.com/deckhouse/module-sdk/pkg/metric-storage/vault" ) var ( - _ metric.Storage = (*metric_storage.MetricStorage)(nil) + _ metric.Storage = (*metricstorage.MetricStorage)(nil) _ metric.Storage = (*metric.StorageMock)(nil) _ metric.GroupedStorage = (*vault.GroupedVault)(nil) diff --git a/pkg/metric_storage/metric_storage.go b/pkg/metric_storage/metric_storage.go index 3c74fae2..ab11e430 100644 --- a/pkg/metric_storage/metric_storage.go +++ b/pkg/metric_storage/metric_storage.go @@ -8,12 +8,12 @@ import ( "sync" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/metric" + "github.com/deckhouse/module-sdk/pkg/metric-storage/operation" + "github.com/deckhouse/module-sdk/pkg/metric-storage/vault" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/flant/shell-operator/pkg/metric" - "github.com/flant/shell-operator/pkg/metric_storage/operation" - "github.com/flant/shell-operator/pkg/metric_storage/vault" . "github.com/flant/shell-operator/pkg/utils/labels" ) diff --git a/pkg/metric_storage/vault/vault.go b/pkg/metric_storage/vault/vault.go index 118107fe..4a62a21a 100644 --- a/pkg/metric_storage/vault/vault.go +++ b/pkg/metric_storage/vault/vault.go @@ -5,9 +5,9 @@ import ( "sync" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/metric" "github.com/prometheus/client_golang/prometheus" - "github.com/flant/shell-operator/pkg/metric" . "github.com/flant/shell-operator/pkg/utils/labels" ) diff --git a/pkg/schedule_manager/schedule_manager.go b/pkg/schedule_manager/schedule_manager.go index 6681b5bd..60896927 100644 --- a/pkg/schedule_manager/schedule_manager.go +++ b/pkg/schedule_manager/schedule_manager.go @@ -4,9 +4,8 @@ import ( "context" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/schedule-manager/types" "gopkg.in/robfig/cron.v2" - - . "github.com/flant/shell-operator/pkg/schedule_manager/types" ) type ScheduleManager interface { diff --git a/pkg/schedule_manager/schedule_manager_test.go b/pkg/schedule_manager/schedule_manager_test.go index 21efbc2b..da519b04 100644 --- a/pkg/schedule_manager/schedule_manager_test.go +++ b/pkg/schedule_manager/schedule_manager_test.go @@ -5,8 +5,7 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" - - "github.com/flant/shell-operator/pkg/schedule_manager/types" + "github.com/deckhouse/module-sdk/pkg/schedule-manager/types" ) func Test_ScheduleManager_Add(t *testing.T) { diff --git a/pkg/shell-operator/bootstrap.go b/pkg/shell-operator/bootstrap.go index 760726a4..b21e4763 100644 --- a/pkg/shell-operator/bootstrap.go +++ b/pkg/shell-operator/bootstrap.go @@ -6,18 +6,18 @@ import ( "log/slog" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + "github.com/deckhouse/module-sdk/pkg/webhook/admission" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" "github.com/flant/shell-operator/pkg/app" "github.com/flant/shell-operator/pkg/config" "github.com/flant/shell-operator/pkg/debug" "github.com/flant/shell-operator/pkg/hook" "github.com/flant/shell-operator/pkg/jq" - "github.com/flant/shell-operator/pkg/kube_events_manager" "github.com/flant/shell-operator/pkg/schedule_manager" "github.com/flant/shell-operator/pkg/task/queue" utils "github.com/flant/shell-operator/pkg/utils/file" - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" ) // Init initialize logging, ensures directories and creates @@ -157,7 +157,7 @@ func (op *ShellOperator) SetupEventManagers() { op.ScheduleManager = schedule_manager.NewScheduleManager(op.ctx, op.logger.Named("schedule-manager")) // Initialize kubernetes events manager. - op.KubeEventsManager = kube_events_manager.NewKubeEventsManager(op.ctx, op.KubeClient, op.logger.Named("kube-events-manager")) + op.KubeEventsManager = kubeeventsmanager.NewKubeEventsManager(op.ctx, op.KubeClient, op.logger.Named("kube-events-manager")) op.KubeEventsManager.WithMetricStorage(op.MetricStorage) // Initialize events handler that emit tasks to run hooks diff --git a/pkg/shell-operator/combine_binding_context.go b/pkg/shell-operator/combine_binding_context.go index 7638f4db..e3d191d1 100644 --- a/pkg/shell-operator/combine_binding_context.go +++ b/pkg/shell-operator/combine_binding_context.go @@ -3,7 +3,8 @@ package shell_operator import ( "fmt" - . "github.com/flant/shell-operator/pkg/hook/binding_context" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + . "github.com/flant/shell-operator/pkg/hook/task_metadata" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" diff --git a/pkg/shell-operator/combine_binding_context_test.go b/pkg/shell-operator/combine_binding_context_test.go index 6caef60b..d487249d 100644 --- a/pkg/shell-operator/combine_binding_context_test.go +++ b/pkg/shell-operator/combine_binding_context_test.go @@ -5,12 +5,12 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + "github.com/deckhouse/module-sdk/pkg/hook/types" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" . "github.com/onsi/gomega" - "github.com/flant/shell-operator/pkg/hook/binding_context" . "github.com/flant/shell-operator/pkg/hook/task_metadata" - "github.com/flant/shell-operator/pkg/hook/types" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" ) @@ -31,7 +31,7 @@ func Test_CombineBindingContext_MultipleHooks(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -42,7 +42,7 @@ func Test_CombineBindingContext_MultipleHooks(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -53,7 +53,7 @@ func Test_CombineBindingContext_MultipleHooks(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "schedule", }, @@ -63,7 +63,7 @@ func Test_CombineBindingContext_MultipleHooks(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -74,7 +74,7 @@ func Test_CombineBindingContext_MultipleHooks(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook2.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -85,7 +85,7 @@ func Test_CombineBindingContext_MultipleHooks(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -125,7 +125,7 @@ func Test_CombineBindingContext_Nil_On_NoCombine(t *testing.T) { WithQueueName("test_no_combine"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -136,7 +136,7 @@ func Test_CombineBindingContext_Nil_On_NoCombine(t *testing.T) { WithQueueName("test_no_combine"). WithMetadata(HookMetadata{ HookName: "hook2.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -147,7 +147,7 @@ func Test_CombineBindingContext_Nil_On_NoCombine(t *testing.T) { WithQueueName("test_no_combine"). WithMetadata(HookMetadata{ HookName: "hook3.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "schedule", }, @@ -179,7 +179,7 @@ func Test_CombineBindingContext_Group_Compaction(t *testing.T) { } }) - bcMeta := binding_context.BindingContext{}.Metadata + bcMeta := bindingcontext.BindingContext{}.Metadata bcMeta.Group = "pods" tasks := []task.Task{ @@ -188,7 +188,7 @@ func Test_CombineBindingContext_Group_Compaction(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Metadata: bcMeta, Binding: "kubernetes", @@ -200,7 +200,7 @@ func Test_CombineBindingContext_Group_Compaction(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Metadata: bcMeta, Binding: "kubernetes", @@ -212,7 +212,7 @@ func Test_CombineBindingContext_Group_Compaction(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Metadata: bcMeta, Binding: "schedule", @@ -223,7 +223,7 @@ func Test_CombineBindingContext_Group_Compaction(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -235,7 +235,7 @@ func Test_CombineBindingContext_Group_Compaction(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook2.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -246,7 +246,7 @@ func Test_CombineBindingContext_Group_Compaction(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -281,11 +281,11 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { } }) - bcMeta := binding_context.BindingContext{}.Metadata + bcMeta := bindingcontext.BindingContext{}.Metadata bcMeta.Group = "pods" bcMeta.BindingType = types.OnKubernetesEvent - schMeta := binding_context.BindingContext{}.Metadata + schMeta := bindingcontext.BindingContext{}.Metadata schMeta.Group = "pods" schMeta.BindingType = types.Schedule @@ -294,7 +294,7 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Metadata: bcMeta, Binding: "kubernetes", @@ -306,7 +306,7 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Metadata: bcMeta, Binding: "kubernetes2", @@ -318,7 +318,7 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Metadata: bcMeta, Binding: "schedule", @@ -332,7 +332,7 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -344,7 +344,7 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Metadata: schMeta, Binding: "schedule", @@ -356,10 +356,10 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { - Metadata: func() binding_context.BindingContext { - bc := binding_context.BindingContext{} + Metadata: func() bindingcontext.BindingContext { + bc := bindingcontext.BindingContext{} bc.Metadata.BindingType = types.Schedule return bc }().Metadata, @@ -372,7 +372,7 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Metadata: bcMeta, Binding: "kubernetes", @@ -385,7 +385,7 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, @@ -396,7 +396,7 @@ func Test_CombineBindingContext_Group_Type(t *testing.T) { WithQueueName("test_multiple_hooks"). WithMetadata(HookMetadata{ HookName: "hook1.sh", - BindingContext: []binding_context.BindingContext{ + BindingContext: []bindingcontext.BindingContext{ { Binding: "kubernetes", Type: TypeEvent, diff --git a/pkg/shell-operator/kube_client.go b/pkg/shell-operator/kube_client.go index 733341ac..9ad2cb79 100644 --- a/pkg/shell-operator/kube_client.go +++ b/pkg/shell-operator/kube_client.go @@ -4,11 +4,11 @@ import ( "fmt" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/kube/object-patch" + "github.com/deckhouse/module-sdk/pkg/metric-storage" klient "github.com/flant/kube-client/client" "github.com/flant/shell-operator/pkg/app" - "github.com/flant/shell-operator/pkg/kube/object_patch" - "github.com/flant/shell-operator/pkg/metric_storage" utils "github.com/flant/shell-operator/pkg/utils/labels" ) @@ -19,7 +19,7 @@ var ( // defaultMainKubeClient creates a Kubernetes client for hooks. No timeout specified, because // timeout will reset connections for Watchers. -func defaultMainKubeClient(metricStorage *metric_storage.MetricStorage, metricLabels map[string]string, logger *log.Logger) *klient.Client { +func defaultMainKubeClient(metricStorage *metricstorage.MetricStorage, metricLabels map[string]string, logger *log.Logger) *klient.Client { client := klient.New(klient.WithLogger(logger)) client.WithContextName(app.KubeContext) client.WithConfigPath(app.KubeConfig) @@ -29,7 +29,7 @@ func defaultMainKubeClient(metricStorage *metric_storage.MetricStorage, metricLa return client } -func initDefaultMainKubeClient(metricStorage *metric_storage.MetricStorage, logger *log.Logger) (*klient.Client, error) { +func initDefaultMainKubeClient(metricStorage *metricstorage.MetricStorage, logger *log.Logger) (*klient.Client, error) { //nolint:staticcheck klient.RegisterKubernetesClientMetrics(metricStorage, defaultMainKubeClientMetricLabels) kubeClient := defaultMainKubeClient(metricStorage, defaultMainKubeClientMetricLabels, logger.Named("main-kube-client")) @@ -41,7 +41,7 @@ func initDefaultMainKubeClient(metricStorage *metric_storage.MetricStorage, logg } // defaultObjectPatcherKubeClient initializes a Kubernetes client for ObjectPatcher. Timeout is specified here. -func defaultObjectPatcherKubeClient(metricStorage *metric_storage.MetricStorage, metricLabels map[string]string, logger *log.Logger) *klient.Client { +func defaultObjectPatcherKubeClient(metricStorage *metricstorage.MetricStorage, metricLabels map[string]string, logger *log.Logger) *klient.Client { client := klient.New(klient.WithLogger(logger)) client.WithContextName(app.KubeContext) client.WithConfigPath(app.KubeConfig) @@ -52,11 +52,11 @@ func defaultObjectPatcherKubeClient(metricStorage *metric_storage.MetricStorage, return client } -func initDefaultObjectPatcher(metricStorage *metric_storage.MetricStorage, logger *log.Logger) (*object_patch.ObjectPatcher, error) { +func initDefaultObjectPatcher(metricStorage *metricstorage.MetricStorage, logger *log.Logger) (*objectpatch.ObjectPatcher, error) { patcherKubeClient := defaultObjectPatcherKubeClient(metricStorage, defaultObjectPatcherKubeClientMetricLabels, logger.Named("object-patcher-kube-client")) err := patcherKubeClient.Init() if err != nil { return nil, fmt.Errorf("initialize Kubernetes client for Object patcher: %s\n", err) } - return object_patch.NewObjectPatcher(patcherKubeClient, logger), nil + return objectpatch.NewObjectPatcher(patcherKubeClient, logger), nil } diff --git a/pkg/shell-operator/manager_events_handler.go b/pkg/shell-operator/manager_events_handler.go index 643c8de0..ab401e12 100644 --- a/pkg/shell-operator/manager_events_handler.go +++ b/pkg/shell-operator/manager_events_handler.go @@ -4,9 +4,9 @@ import ( "context" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" - "github.com/flant/shell-operator/pkg/kube_events_manager" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" "github.com/flant/shell-operator/pkg/schedule_manager" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" @@ -14,7 +14,7 @@ import ( type managerEventsHandlerConfig struct { tqs *queue.TaskQueueSet - mgr kube_events_manager.KubeEventsManager + mgr kubeeventsmanager.KubeEventsManager smgr schedule_manager.ScheduleManager logger *log.Logger @@ -24,7 +24,7 @@ type ManagerEventsHandler struct { ctx context.Context cancel context.CancelFunc - kubeEventsManager kube_events_manager.KubeEventsManager + kubeEventsManager kubeeventsmanager.KubeEventsManager scheduleManager schedule_manager.ScheduleManager kubeEventCb func(kubeEvent KubeEvent) []task.Task diff --git a/pkg/shell-operator/metrics_hooks.go b/pkg/shell-operator/metrics_hooks.go index 24e2bfa5..25202c47 100644 --- a/pkg/shell-operator/metrics_hooks.go +++ b/pkg/shell-operator/metrics_hooks.go @@ -3,12 +3,13 @@ package shell_operator import ( "net/http" + "github.com/deckhouse/module-sdk/pkg/metric-storage" + "github.com/flant/shell-operator/pkg/app" - "github.com/flant/shell-operator/pkg/metric_storage" ) func (op *ShellOperator) setupHookMetricStorage() { - metricStorage := metric_storage.NewMetricStorage(op.ctx, app.PrometheusMetricsPrefix, true, op.logger.Named("metric-storage")) + metricStorage := metricstorage.NewMetricStorage(op.ctx, app.PrometheusMetricsPrefix, true, op.logger.Named("metric-storage")) op.APIServer.RegisterRoute(http.MethodGet, "/metrics/hooks", metricStorage.Handler().ServeHTTP) // create new metric storage for hooks @@ -17,7 +18,7 @@ func (op *ShellOperator) setupHookMetricStorage() { } // specific metrics for shell-operator HookManager -func registerHookMetrics(metricStorage *metric_storage.MetricStorage) { +func registerHookMetrics(metricStorage *metricstorage.MetricStorage) { // Metrics for enable kubernetes bindings. metricStorage.RegisterGauge("{PREFIX}hook_enable_kubernetes_bindings_seconds", map[string]string{"hook": ""}) metricStorage.RegisterCounter("{PREFIX}hook_enable_kubernetes_bindings_errors_total", map[string]string{"hook": ""}) diff --git a/pkg/shell-operator/metrics_operator.go b/pkg/shell-operator/metrics_operator.go index 021335a9..c78b312c 100644 --- a/pkg/shell-operator/metrics_operator.go +++ b/pkg/shell-operator/metrics_operator.go @@ -3,13 +3,14 @@ package shell_operator import ( "net/http" + "github.com/deckhouse/module-sdk/pkg/metric-storage" + "github.com/flant/shell-operator/pkg/app" - "github.com/flant/shell-operator/pkg/metric_storage" ) // setupMetricStorage creates and initializes metrics storage for built-in operator metrics func (op *ShellOperator) setupMetricStorage(kubeEventsManagerLabels map[string]string) { - metricStorage := metric_storage.NewMetricStorage(op.ctx, app.PrometheusMetricsPrefix, false, op.logger.Named("metric-storage")) + metricStorage := metricstorage.NewMetricStorage(op.ctx, app.PrometheusMetricsPrefix, false, op.logger.Named("metric-storage")) registerCommonMetrics(metricStorage) registerTaskQueueMetrics(metricStorage) @@ -23,13 +24,13 @@ func (op *ShellOperator) setupMetricStorage(kubeEventsManagerLabels map[string]s // registerCommonMetrics register base metric // This function is used in the addon-operator -func registerCommonMetrics(metricStorage *metric_storage.MetricStorage) { +func registerCommonMetrics(metricStorage *metricstorage.MetricStorage) { metricStorage.RegisterCounter("{PREFIX}live_ticks", map[string]string{}) } // registerTaskQueueMetrics // This function is used in the addon-operator -func registerTaskQueueMetrics(metricStorage *metric_storage.MetricStorage) { +func registerTaskQueueMetrics(metricStorage *metricstorage.MetricStorage) { metricStorage.RegisterHistogram( "{PREFIX}tasks_queue_action_duration_seconds", map[string]string{ @@ -50,7 +51,7 @@ func registerTaskQueueMetrics(metricStorage *metric_storage.MetricStorage) { // registerKubeEventsManagerMetrics registers metrics for kube_event_manager // This function is used in the addon-operator -func registerKubeEventsManagerMetrics(metricStorage *metric_storage.MetricStorage, labels map[string]string) { +func registerKubeEventsManagerMetrics(metricStorage *metricstorage.MetricStorage, labels map[string]string) { // Count of objects in snapshot for one kubernets bindings. metricStorage.RegisterGauge("{PREFIX}kube_snapshot_objects", labels) // Duration of jqFilter applying. diff --git a/pkg/shell-operator/operator.go b/pkg/shell-operator/operator.go index 73306142..eddf57ed 100644 --- a/pkg/shell-operator/operator.go +++ b/pkg/shell-operator/operator.go @@ -6,26 +6,26 @@ import ( "time" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + kemTypes "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + "github.com/deckhouse/module-sdk/pkg/kube/object-patch" + "github.com/deckhouse/module-sdk/pkg/metric-storage" + "github.com/deckhouse/module-sdk/pkg/webhook/admission" + "github.com/deckhouse/module-sdk/pkg/webhook/conversion" "github.com/gofrs/uuid/v5" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" klient "github.com/flant/kube-client/client" "github.com/flant/shell-operator/pkg/hook" - "github.com/flant/shell-operator/pkg/hook/binding_context" "github.com/flant/shell-operator/pkg/hook/controller" "github.com/flant/shell-operator/pkg/hook/task_metadata" - "github.com/flant/shell-operator/pkg/hook/types" - "github.com/flant/shell-operator/pkg/kube/object_patch" - "github.com/flant/shell-operator/pkg/kube_events_manager" - kemTypes "github.com/flant/shell-operator/pkg/kube_events_manager/types" - "github.com/flant/shell-operator/pkg/metric_storage" "github.com/flant/shell-operator/pkg/schedule_manager" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" utils "github.com/flant/shell-operator/pkg/utils/labels" "github.com/flant/shell-operator/pkg/utils/measure" - "github.com/flant/shell-operator/pkg/webhook/admission" - "github.com/flant/shell-operator/pkg/webhook/conversion" ) var WaitQueuesTimeout = time.Second * 10 @@ -40,14 +40,14 @@ type ShellOperator struct { APIServer *baseHTTPServer // MetricStorage collects and store metrics for built-in operator primitives, hook execution - MetricStorage *metric_storage.MetricStorage + MetricStorage *metricstorage.MetricStorage // HookMetricStorage separate metric storage for metrics, which are returned by user hooks - HookMetricStorage *metric_storage.MetricStorage + HookMetricStorage *metricstorage.MetricStorage KubeClient *klient.Client - ObjectPatcher *object_patch.ObjectPatcher + ObjectPatcher *objectpatch.ObjectPatcher ScheduleManager schedule_manager.ScheduleManager - KubeEventsManager kube_events_manager.KubeEventsManager + KubeEventsManager kubeeventsmanager.KubeEventsManager TaskQueues *queue.TaskQueueSet @@ -622,12 +622,12 @@ func (op *ShellOperator) handleRunHook(t task.Task, taskHook *hook.Hook, hookMet result, err := taskHook.Run(hookMeta.BindingType, hookMeta.BindingContext, hookLogLabels) if err != nil { if result != nil && len(result.KubernetesPatchBytes) > 0 { - operations, patchStatusErr := object_patch.ParseOperations(result.KubernetesPatchBytes) + operations, patchStatusErr := objectpatch.ParseOperations(result.KubernetesPatchBytes) if patchStatusErr != nil { return fmt.Errorf("%s: couldn't patch status: %s", err, patchStatusErr) } - patchStatusErr = op.ObjectPatcher.ExecuteOperations(object_patch.GetPatchStatusOperationsOnHookError(operations)) + patchStatusErr = op.ObjectPatcher.ExecuteOperations(objectpatch.GetPatchStatusOperationsOnHookError(operations)) if patchStatusErr != nil { return fmt.Errorf("%s: couldn't patch status: %s", err, patchStatusErr) } @@ -644,7 +644,7 @@ func (op *ShellOperator) handleRunHook(t task.Task, taskHook *hook.Hook, hookMet // Try to apply Kubernetes actions. if len(result.KubernetesPatchBytes) > 0 { - operations, err := object_patch.ParseOperations(result.KubernetesPatchBytes) + operations, err := objectpatch.ParseOperations(result.KubernetesPatchBytes) if err != nil { return err } @@ -735,7 +735,7 @@ func (op *ShellOperator) CombineBindingContextForHook(q *queue.TaskQueue, t task } // Combine binding context and make a map to delete excess tasks - combinedContext := make([]binding_context.BindingContext, 0) + combinedContext := make([]bindingcontext.BindingContext, 0) monitorIDs := taskMeta.(task_metadata.MonitorIDAccessor).GetMonitorIDs() tasksFilter := make(map[string]bool) // current task always remain in queue @@ -759,7 +759,7 @@ func (op *ShellOperator) CombineBindingContextForHook(q *queue.TaskQueue, t task }) // group is used to compact binding contexts when only snapshots are needed - compactedContext := make([]binding_context.BindingContext, 0) + compactedContext := make([]bindingcontext.BindingContext, 0) for i := 0; i < len(combinedContext); i++ { keep := true @@ -807,7 +807,7 @@ func (op *ShellOperator) bootstrapMainQueue(tqs *queue.TaskQueueSet) { } for _, hookName := range onStartupHooks { - bc := binding_context.BindingContext{ + bc := bindingcontext.BindingContext{ Binding: string(types.OnStartup), } bc.Metadata.BindingType = types.OnStartup @@ -816,7 +816,7 @@ func (op *ShellOperator) bootstrapMainQueue(tqs *queue.TaskQueueSet) { WithMetadata(task_metadata.HookMetadata{ HookName: hookName, BindingType: types.OnStartup, - BindingContext: []binding_context.BindingContext{bc}, + BindingContext: []bindingcontext.BindingContext{bc}, }). WithQueuedAt(time.Now()) mainQueue.AddLast(newTask) diff --git a/pkg/shell-operator/operator_test.go b/pkg/shell-operator/operator_test.go index 5e8ee610..fbd90d05 100644 --- a/pkg/shell-operator/operator_test.go +++ b/pkg/shell-operator/operator_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/types" . "github.com/onsi/gomega" . "github.com/flant/shell-operator/pkg/hook/task_metadata" - . "github.com/flant/shell-operator/pkg/hook/types" "github.com/flant/shell-operator/pkg/task" utils "github.com/flant/shell-operator/pkg/utils/file" ) diff --git a/pkg/task/queue/queue_set.go b/pkg/task/queue/queue_set.go index 5c876f25..49d3565c 100644 --- a/pkg/task/queue/queue_set.go +++ b/pkg/task/queue/queue_set.go @@ -5,7 +5,8 @@ import ( "sync" "time" - "github.com/flant/shell-operator/pkg/metric_storage" + "github.com/deckhouse/module-sdk/pkg/metric-storage" + "github.com/flant/shell-operator/pkg/task" ) @@ -15,7 +16,7 @@ const MainQueueName = "main" type TaskQueueSet struct { MainName string - metricStorage *metric_storage.MetricStorage + metricStorage *metricstorage.MetricStorage ctx context.Context cancel context.CancelFunc @@ -40,7 +41,7 @@ func (tqs *TaskQueueSet) WithContext(ctx context.Context) { tqs.ctx, tqs.cancel = context.WithCancel(ctx) } -func (tqs *TaskQueueSet) WithMetricStorage(mstor *metric_storage.MetricStorage) { +func (tqs *TaskQueueSet) WithMetricStorage(mstor *metricstorage.MetricStorage) { tqs.metricStorage = mstor } diff --git a/pkg/task/queue/task_queue.go b/pkg/task/queue/task_queue.go index 32019550..19ac354f 100644 --- a/pkg/task/queue/task_queue.go +++ b/pkg/task/queue/task_queue.go @@ -9,8 +9,8 @@ import ( "time" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/metric-storage" - "github.com/flant/shell-operator/pkg/metric_storage" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/utils/exponential_backoff" "github.com/flant/shell-operator/pkg/utils/measure" @@ -56,7 +56,7 @@ type TaskResult struct { type TaskQueue struct { m sync.RWMutex - metricStorage *metric_storage.MetricStorage + metricStorage *metricstorage.MetricStorage ctx context.Context cancel context.CancelFunc @@ -101,7 +101,7 @@ func (q *TaskQueue) WithContext(ctx context.Context) { q.ctx, q.cancel = context.WithCancel(ctx) } -func (q *TaskQueue) WithMetricStorage(mstor *metric_storage.MetricStorage) { +func (q *TaskQueue) WithMetricStorage(mstor *metricstorage.MetricStorage) { q.metricStorage = mstor } diff --git a/pkg/webhook/admission/manager.go b/pkg/webhook/admission/manager.go index b34459c0..0c8c34bb 100644 --- a/pkg/webhook/admission/manager.go +++ b/pkg/webhook/admission/manager.go @@ -4,9 +4,9 @@ import ( "os" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/webhook/server" klient "github.com/flant/kube-client/client" - "github.com/flant/shell-operator/pkg/webhook/server" ) // DefaultConfigurationId is a ConfigurationId for ValidatingWebhookConfiguration/MutatingWebhookConfiguration diff --git a/pkg/webhook/admission/settings.go b/pkg/webhook/admission/settings.go index ad9cf795..0057935d 100644 --- a/pkg/webhook/admission/settings.go +++ b/pkg/webhook/admission/settings.go @@ -1,6 +1,6 @@ package admission -import "github.com/flant/shell-operator/pkg/webhook/server" +import "github.com/deckhouse/module-sdk/pkg/webhook/server" type WebhookSettings struct { server.Settings diff --git a/pkg/webhook/conversion/manager.go b/pkg/webhook/conversion/manager.go index 44f5287f..50351a59 100644 --- a/pkg/webhook/conversion/manager.go +++ b/pkg/webhook/conversion/manager.go @@ -5,10 +5,10 @@ import ( "os" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/webhook/server" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" klient "github.com/flant/kube-client/client" - "github.com/flant/shell-operator/pkg/webhook/server" ) type EventHandlerFn func(cdrName string, request *v1.ConversionRequest) (*Response, error) diff --git a/pkg/webhook/conversion/settings.go b/pkg/webhook/conversion/settings.go index eda7119c..cc25ecb4 100644 --- a/pkg/webhook/conversion/settings.go +++ b/pkg/webhook/conversion/settings.go @@ -1,6 +1,6 @@ package conversion -import "github.com/flant/shell-operator/pkg/webhook/server" +import "github.com/deckhouse/module-sdk/pkg/webhook/server" type WebhookSettings struct { server.Settings diff --git a/test/hook/context/context_combiner.go b/test/hook/context/context_combiner.go index 62b30f25..ded8a206 100644 --- a/test/hook/context/context_combiner.go +++ b/test/hook/context/context_combiner.go @@ -4,10 +4,11 @@ import ( "context" "fmt" - "github.com/flant/shell-operator/pkg/hook/binding_context" - "github.com/flant/shell-operator/pkg/hook/controller" + "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + "github.com/deckhouse/module-sdk/pkg/hook/controller" + "github.com/deckhouse/module-sdk/pkg/hook/types" + "github.com/flant/shell-operator/pkg/hook/task_metadata" - "github.com/flant/shell-operator/pkg/hook/types" shell_operator "github.com/flant/shell-operator/pkg/shell-operator" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" @@ -54,10 +55,10 @@ func (c *ContextCombiner) AddBindingContext(bindingType types.BindingType, info // CombinedContext returns a combined context or a binding context // from the first task. -func (c *ContextCombiner) Combined() []binding_context.BindingContext { +func (c *ContextCombiner) Combined() []bindingcontext.BindingContext { firstTask := c.q.GetFirst() if firstTask == nil { - return []binding_context.BindingContext{} + return []bindingcontext.BindingContext{} } taskMeta := firstTask.GetMetadata() @@ -81,11 +82,11 @@ func (c *ContextCombiner) QueueLen() int { return c.q.Length() } -func ConvertToGeneratedBindingContexts(bindingContexts []binding_context.BindingContext) (GeneratedBindingContexts, error) { +func ConvertToGeneratedBindingContexts(bindingContexts []bindingcontext.BindingContext) (GeneratedBindingContexts, error) { res := GeneratedBindingContexts{} // Support only v1 binding contexts. - bcList := binding_context.ConvertBindingContextList("v1", bindingContexts) + bcList := bindingcontext.ConvertBindingContextList("v1", bindingContexts) data, err := bcList.Json() if err != nil { return res, fmt.Errorf("marshaling binding context error: %v", err) diff --git a/test/hook/context/generator.go b/test/hook/context/generator.go index a786237e..33be146e 100644 --- a/test/hook/context/generator.go +++ b/test/hook/context/generator.go @@ -7,13 +7,13 @@ import ( "time" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/hook" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + "github.com/deckhouse/module-sdk/pkg/hook/controller" + "github.com/deckhouse/module-sdk/pkg/hook/types" + kubeeventsmanager "github.com/deckhouse/module-sdk/pkg/kube-events-manager" "github.com/flant/kube-client/fake" - "github.com/flant/shell-operator/pkg/hook" - . "github.com/flant/shell-operator/pkg/hook/binding_context" - "github.com/flant/shell-operator/pkg/hook/controller" - "github.com/flant/shell-operator/pkg/hook/types" - kubeeventsmanager "github.com/flant/shell-operator/pkg/kube_events_manager" schedulemanager "github.com/flant/shell-operator/pkg/schedule_manager" ) diff --git a/test/hook/context/generator_test.go b/test/hook/context/generator_test.go index 09028938..3482915b 100644 --- a/test/hook/context/generator_test.go +++ b/test/hook/context/generator_test.go @@ -5,9 +5,8 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" + . "github.com/deckhouse/module-sdk/pkg/hook/binding-context" . "github.com/onsi/gomega" - - . "github.com/flant/shell-operator/pkg/hook/binding_context" ) func parseContexts(contexts string) []BindingContext { diff --git a/test/hook/context/state.go b/test/hook/context/state.go index d99a965d..5ce34dab 100644 --- a/test/hook/context/state.go +++ b/test/hook/context/state.go @@ -5,10 +5,11 @@ import ( "reflect" "time" + kubeeventsmanager "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" + "github.com/flant/kube-client/fake" "github.com/flant/kube-client/manifest" - kubeeventsmanager "github.com/flant/shell-operator/pkg/kube_events_manager" - "github.com/flant/shell-operator/pkg/kube_events_manager/types" ) // if we use default, then we are not able to emulate global resources due to fake cluster limitations diff --git a/test/integration/kube_event_manager/kube_event_manager_test.go b/test/integration/kube_event_manager/kube_event_manager_test.go index 51d1ecdd..0caeccee 100644 --- a/test/integration/kube_event_manager/kube_event_manager_test.go +++ b/test/integration/kube_event_manager/kube_event_manager_test.go @@ -12,9 +12,9 @@ import ( . "github.com/onsi/gomega" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + . "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" "github.com/flant/shell-operator/pkg/app" - "github.com/flant/shell-operator/pkg/kube_events_manager" - . "github.com/flant/shell-operator/pkg/kube_events_manager/types" . "github.com/flant/shell-operator/test/integration/suite" testutils "github.com/flant/shell-operator/test/utils" ) @@ -24,17 +24,17 @@ func Test(t *testing.T) { } var _ = Describe("Binding 'kubernetes' with kind 'Pod' should emit KubeEvent objects", func() { - var KubeEventsManager kube_events_manager.KubeEventsManager + var KubeEventsManager kubeeventsmanager.KubeEventsManager BeforeEach(func() { - KubeEventsManager = kube_events_manager.NewKubeEventsManager(context.Background(), KubeClient, log.NewNop()) + KubeEventsManager = kubeeventsmanager.NewKubeEventsManager(context.Background(), KubeClient, log.NewNop()) }) Context("with configVersion: v1", func() { - var monitorConfig *kube_events_manager.MonitorConfig + var monitorConfig *kubeeventsmanager.MonitorConfig BeforeEach(func() { - monitorConfig = &kube_events_manager.MonitorConfig{ + monitorConfig = &kubeeventsmanager.MonitorConfig{ Kind: "Pod", ApiVersion: "v1", KeepFullObjectsInMemory: true, diff --git a/test/integration/kubeclient/object_patch_test.go b/test/integration/kubeclient/object_patch_test.go index 22182317..3a5cf3c5 100644 --- a/test/integration/kubeclient/object_patch_test.go +++ b/test/integration/kubeclient/object_patch_test.go @@ -17,7 +17,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" - "github.com/flant/shell-operator/pkg/kube/object_patch" + "github.com/deckhouse/module-sdk/pkg/kube/object-patch" . "github.com/flant/shell-operator/test/integration/suite" ) @@ -65,7 +65,7 @@ var _ = Describe("Kubernetes API object patching", func() { }) It("should fail to Create() an object if it already exists", func() { - err := ObjectPatcher.ExecuteOperation(object_patch.NewCreateOperation(unstructuredCM)) + err := ObjectPatcher.ExecuteOperation(objectpatch.NewCreateOperation(unstructuredCM)) Expect(err).To(Not(Succeed())) }) @@ -79,7 +79,7 @@ var _ = Describe("Kubernetes API object patching", func() { unstructuredNewTestCM, err := generateUnstructured(newTestCM) Expect(err).To(Succeed()) - err = ObjectPatcher.ExecuteOperation(object_patch.NewCreateOperation(unstructuredNewTestCM, object_patch.UpdateIfExists())) + err = ObjectPatcher.ExecuteOperation(objectpatch.NewCreateOperation(unstructuredNewTestCM, objectpatch.UpdateIfExists())) Expect(err).To(Succeed()) cm, err := KubeClient.CoreV1().ConfigMaps(testCM.Namespace).Get(context.TODO(), newTestCM.Name, metav1.GetOptions{}) @@ -94,7 +94,7 @@ var _ = Describe("Kubernetes API object patching", func() { unstructuredSeparateTestCM, err := generateUnstructured(separateTestCM) Expect(err).To(Succeed()) - err = ObjectPatcher.ExecuteOperation(object_patch.NewCreateOperation(unstructuredSeparateTestCM, object_patch.UpdateIfExists())) + err = ObjectPatcher.ExecuteOperation(objectpatch.NewCreateOperation(unstructuredSeparateTestCM, objectpatch.UpdateIfExists())) Expect(err).To(Succeed()) _, err = KubeClient.CoreV1().ConfigMaps(testCM.Namespace).Get(context.TODO(), separateTestCM.Name, metav1.GetOptions{}) @@ -118,9 +118,9 @@ var _ = Describe("Kubernetes API object patching", func() { }) It("should successfully delete an object", func() { - err := ObjectPatcher.ExecuteOperation(object_patch.NewDeleteOperation( + err := ObjectPatcher.ExecuteOperation(objectpatch.NewDeleteOperation( testCM.APIVersion, testCM.Kind, testCM.Namespace, testCM.Name, - object_patch.InBackground())) + objectpatch.InBackground())) Expect(err).Should(Succeed()) _, err = KubeClient.CoreV1().ConfigMaps(testCM.Namespace).Get(context.TODO(), testCM.Name, metav1.GetOptions{}) @@ -128,7 +128,7 @@ var _ = Describe("Kubernetes API object patching", func() { }) It("should successfully delete an object if it doesn't exist", func() { - err := ObjectPatcher.ExecuteOperation(object_patch.NewDeleteOperation(testCM.APIVersion, testCM.Kind, testCM.Namespace, testCM.Name)) + err := ObjectPatcher.ExecuteOperation(objectpatch.NewDeleteOperation(testCM.APIVersion, testCM.Kind, testCM.Namespace, testCM.Name)) Expect(err).Should(Succeed()) }) }) @@ -149,8 +149,8 @@ var _ = Describe("Kubernetes API object patching", func() { }) It("should successfully JQPatch an object", func() { - err := ObjectPatcher.ExecuteOperation(object_patch.NewFromOperationSpec(object_patch.OperationSpec{ - Operation: object_patch.JQPatch, + err := ObjectPatcher.ExecuteOperation(objectpatch.NewFromOperationSpec(objectpatch.OperationSpec{ + Operation: objectpatch.JQPatch, ApiVersion: testCM.APIVersion, Kind: testCM.Kind, Namespace: testCM.Namespace, @@ -176,7 +176,7 @@ data: mergePatchJson, err := json.Marshal(mergePatch) Expect(err).To(Succeed()) - err = ObjectPatcher.ExecuteOperation(object_patch.NewMergePatchOperation(mergePatchJson, testCM.APIVersion, testCM.Kind, testCM.Namespace, testCM.Name)) + err = ObjectPatcher.ExecuteOperation(objectpatch.NewMergePatchOperation(mergePatchJson, testCM.APIVersion, testCM.Kind, testCM.Namespace, testCM.Name)) Expect(err).To(Succeed()) existingCM, err := KubeClient.CoreV1().ConfigMaps(testCM.Namespace).Get(context.TODO(), testCM.Name, metav1.GetOptions{}) @@ -185,7 +185,7 @@ data: }) It("should successfully JSONPatch an object", func() { - err := ObjectPatcher.ExecuteOperation(object_patch.NewJSONPatchOperation( + err := ObjectPatcher.ExecuteOperation(objectpatch.NewJSONPatchOperation( []byte(`[{ "op": "replace", "path": "/data/firstField", "value": "jsonPatched"}]`), testCM.APIVersion, testCM.Kind, testCM.Namespace, testCM.Name)) Expect(err).To(Succeed()) @@ -205,7 +205,7 @@ func ensureNamespace(name string) error { panic(err) } - return ObjectPatcher.ExecuteOperation(object_patch.NewCreateOperation(unstructuredNS, object_patch.UpdateIfExists())) + return ObjectPatcher.ExecuteOperation(objectpatch.NewCreateOperation(unstructuredNS, objectpatch.UpdateIfExists())) } func ensureTestObject(_ string, obj interface{}) error { @@ -214,11 +214,11 @@ func ensureTestObject(_ string, obj interface{}) error { panic(err) } - return ObjectPatcher.ExecuteOperation(object_patch.NewCreateOperation(unstructuredObj, object_patch.UpdateIfExists())) + return ObjectPatcher.ExecuteOperation(objectpatch.NewCreateOperation(unstructuredObj, objectpatch.UpdateIfExists())) } func removeNamespace(name string) error { - return ObjectPatcher.ExecuteOperation(object_patch.NewDeleteOperation("", "Namespace", "", name)) + return ObjectPatcher.ExecuteOperation(objectpatch.NewDeleteOperation("", "Namespace", "", name)) } func generateUnstructured(obj interface{}) (*unstructured.Unstructured, error) { diff --git a/test/integration/suite/run.go b/test/integration/suite/run.go index f6fb6dc6..46754060 100644 --- a/test/integration/suite/run.go +++ b/test/integration/suite/run.go @@ -11,15 +11,15 @@ import ( . "github.com/onsi/gomega" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg/kube/object-patch" klient "github.com/flant/kube-client/client" - "github.com/flant/shell-operator/pkg/kube/object_patch" ) var ( ClusterName string ContextName string KubeClient *klient.Client - ObjectPatcher *object_patch.ObjectPatcher + ObjectPatcher *objectpatch.ObjectPatcher ) func RunIntegrationSuite(t *testing.T, description string, clusterPrefix string) { @@ -37,5 +37,5 @@ var _ = BeforeSuite(func() { err := KubeClient.Init() Expect(err).ShouldNot(HaveOccurred()) - ObjectPatcher = object_patch.NewObjectPatcher(KubeClient, log.NewNop()) + ObjectPatcher = objectpatch.NewObjectPatcher(KubeClient, log.NewNop()) }) From 631dc0ee30d6c43c7c57b1a92b6fbfc1d29de336 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Wed, 13 Nov 2024 16:07:36 +0300 Subject: [PATCH 2/4] bump sdk Signed-off-by: Pavel Okhlopkov --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ebb39657..f1184fa0 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( replace github.com/go-openapi/validate => github.com/flant/go-openapi-validate v0.19.12-flant.0 require ( - github.com/deckhouse/module-sdk v0.0.0-20241113114352-11f03833a3cf + github.com/deckhouse/module-sdk v0.0.0-20241113125902-56440b53be96 github.com/gojuno/minimock/v3 v3.4.1 ) diff --git a/go.sum b/go.sum index 898d5b54..3e981b21 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241106140903-258b93b3334e h1:QUQy+5Bv7/UzhfrytiG3c5gfLGhPppepVbRpbMisVIw= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241106140903-258b93b3334e/go.mod h1:Mk5HRzkc5pIcDIZ2JJ6DPuuqnwhXVkb3you8M8Mg+4w= -github.com/deckhouse/module-sdk v0.0.0-20241113114352-11f03833a3cf h1:YvPB431VunQLb8fChP9t9mQNHSvL2Xavk+UQWnGF1sU= -github.com/deckhouse/module-sdk v0.0.0-20241113114352-11f03833a3cf/go.mod h1:q9jIxjVSVhPRJLWC001c7ujYZIzoPKjiF4LV0Zm+XCE= +github.com/deckhouse/module-sdk v0.0.0-20241113125902-56440b53be96 h1:bbA7KCDPWN8gfFl2tRWU2Ik03eS5p+VFz0LC8uqqXCo= +github.com/deckhouse/module-sdk v0.0.0-20241113125902-56440b53be96/go.mod h1:q9jIxjVSVhPRJLWC001c7ujYZIzoPKjiF4LV0Zm+XCE= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= From 9068de4cde538c6ccb5da111b1e715c2a0ba0472 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Thu, 14 Nov 2024 12:29:25 +0300 Subject: [PATCH 3/4] return object patch Signed-off-by: Pavel Okhlopkov --- go.mod | 4 +- go.sum | 4 +- pkg/kube/object-patch/helpers.go | 144 +++ pkg/kube/object-patch/operation.go | 318 ++++++ pkg/kube/object-patch/options.go | 122 +++ pkg/kube/object-patch/patch.go | 302 ++++++ pkg/kube/object-patch/patch_collector.go | 86 ++ pkg/kube/object-patch/patch_test.go | 949 ++++++++++++++++++ .../serialized_operations/invalid_create.yaml | 8 + .../serialized_operations/invalid_delete.yaml | 12 + .../serialized_operations/invalid_patch.yaml | 15 + .../serialized_operations/valid_create.yaml | 16 + .../serialized_operations/valid_delete.yaml | 18 + .../serialized_operations/valid_patch.yaml | 27 + pkg/kube/object-patch/validation.go | 214 ++++ pkg/kube/object-patch/validation_test.go | 28 + pkg/shell-operator/kube_client.go | 4 +- pkg/shell-operator/operator.go | 8 +- .../kubeclient/object_patch_test.go | 5 +- test/integration/suite/run.go | 7 +- 20 files changed, 2274 insertions(+), 17 deletions(-) create mode 100644 pkg/kube/object-patch/helpers.go create mode 100644 pkg/kube/object-patch/operation.go create mode 100644 pkg/kube/object-patch/options.go create mode 100644 pkg/kube/object-patch/patch.go create mode 100644 pkg/kube/object-patch/patch_collector.go create mode 100644 pkg/kube/object-patch/patch_test.go create mode 100644 pkg/kube/object-patch/testdata/serialized_operations/invalid_create.yaml create mode 100644 pkg/kube/object-patch/testdata/serialized_operations/invalid_delete.yaml create mode 100644 pkg/kube/object-patch/testdata/serialized_operations/invalid_patch.yaml create mode 100644 pkg/kube/object-patch/testdata/serialized_operations/valid_create.yaml create mode 100644 pkg/kube/object-patch/testdata/serialized_operations/valid_delete.yaml create mode 100644 pkg/kube/object-patch/testdata/serialized_operations/valid_patch.yaml create mode 100644 pkg/kube/object-patch/validation.go create mode 100644 pkg/kube/object-patch/validation_test.go diff --git a/go.mod b/go.mod index f1184fa0..e0eda4aa 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/kennygrant/sanitize v1.2.4 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.35.1 - github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.20.5 github.com/stretchr/testify v1.9.0 golang.org/x/time v0.7.0 @@ -34,7 +34,7 @@ require ( replace github.com/go-openapi/validate => github.com/flant/go-openapi-validate v0.19.12-flant.0 require ( - github.com/deckhouse/module-sdk v0.0.0-20241113125902-56440b53be96 + github.com/deckhouse/module-sdk v0.0.0-20241114091625-9e129814d38d github.com/gojuno/minimock/v3 v3.4.1 ) diff --git a/go.sum b/go.sum index 3e981b21..0bdc01b7 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241106140903-258b93b3334e h1:QUQy+5Bv7/UzhfrytiG3c5gfLGhPppepVbRpbMisVIw= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241106140903-258b93b3334e/go.mod h1:Mk5HRzkc5pIcDIZ2JJ6DPuuqnwhXVkb3you8M8Mg+4w= -github.com/deckhouse/module-sdk v0.0.0-20241113125902-56440b53be96 h1:bbA7KCDPWN8gfFl2tRWU2Ik03eS5p+VFz0LC8uqqXCo= -github.com/deckhouse/module-sdk v0.0.0-20241113125902-56440b53be96/go.mod h1:q9jIxjVSVhPRJLWC001c7ujYZIzoPKjiF4LV0Zm+XCE= +github.com/deckhouse/module-sdk v0.0.0-20241114091625-9e129814d38d h1:aHOxJAQcs2GgB7H5Lu6AfFiqrxBO+dyu7GxOn1Rg+OY= +github.com/deckhouse/module-sdk v0.0.0-20241114091625-9e129814d38d/go.mod h1:q9jIxjVSVhPRJLWC001c7ujYZIzoPKjiF4LV0Zm+XCE= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= diff --git a/pkg/kube/object-patch/helpers.go b/pkg/kube/object-patch/helpers.go new file mode 100644 index 00000000..453cc260 --- /dev/null +++ b/pkg/kube/object-patch/helpers.go @@ -0,0 +1,144 @@ +package objectpatch + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + + "github.com/deckhouse/module-sdk/pkg/app" + "github.com/deckhouse/module-sdk/pkg/jq" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + k8yaml "sigs.k8s.io/yaml" + + "github.com/flant/kube-client/manifest" +) + +func unmarshalFromJSONOrYAML(specs []byte) ([]OperationSpec, error) { + fromJsonSpecs, err := unmarshalFromJson(specs) + if err != nil { + return unmarshalFromYaml(specs) + } + + return fromJsonSpecs, nil +} + +func unmarshalFromJson(jsonSpecs []byte) ([]OperationSpec, error) { + var specSlice []OperationSpec + + dec := json.NewDecoder(bytes.NewReader(jsonSpecs)) + for { + var doc OperationSpec + err := dec.Decode(&doc) + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + specSlice = append(specSlice, doc) + } + + return specSlice, nil +} + +func unmarshalFromYaml(yamlSpecs []byte) ([]OperationSpec, error) { + var specSlice []OperationSpec + + dec := yaml.NewDecoder(bytes.NewReader(yamlSpecs)) + for { + var doc OperationSpec + err := dec.Decode(&doc) + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + specSlice = append(specSlice, doc) + } + + return specSlice, nil +} + +func applyJQPatch(jqFilter string, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + objBytes, err := obj.MarshalJSON() + if err != nil { + return nil, err + } + + filterResult, err := jq.ApplyJqFilter(jqFilter, objBytes, app.JqLibraryPath) + if err != nil { + return nil, fmt.Errorf("failed to apply jqFilter:\n%sto Object:\n%s\n"+ + "error: %s", jqFilter, obj, err) + } + + retObj := &unstructured.Unstructured{} + _, _, err = unstructured.UnstructuredJSONScheme.Decode([]byte(filterResult), nil, retObj) + if err != nil { + return nil, fmt.Errorf("failed to convert filterResult:\n%s\nto Unstructured Object\nerror: %s", filterResult, err) + } + + return retObj, nil +} + +func generateSubresources(subresource string) (ret []string) { + if subresource != "" { + ret = append(ret, subresource) + } + + return +} + +func toUnstructured(obj interface{}) (*unstructured.Unstructured, error) { + switch v := obj.(type) { + case []byte: + mft, err := manifest.NewFromYAML(string(v)) + if err != nil { + return nil, err + } + return mft.Unstructured(), nil + case string: + mft, err := manifest.NewFromYAML(v) + if err != nil { + return nil, err + } + return mft.Unstructured(), nil + case map[string]interface{}: + return &unstructured.Unstructured{Object: v}, nil + default: + objectContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, fmt.Errorf("convert to unstructured: %v", err) + } + return &unstructured.Unstructured{Object: objectContent}, nil + } +} + +func convertPatchToBytes(patch interface{}) ([]byte, error) { + var err error + var intermediate interface{} + switch v := patch.(type) { + case []byte: + err = k8yaml.Unmarshal(v, &intermediate) + case string: + err = k8yaml.Unmarshal([]byte(v), &intermediate) + default: + intermediate = v + } + if err != nil { + return nil, err + } + + // Try to encode to JSON. + var patchBytes []byte + patchBytes, err = json.Marshal(intermediate) + if err != nil { + return nil, err + } + return patchBytes, nil +} diff --git a/pkg/kube/object-patch/operation.go b/pkg/kube/object-patch/operation.go new file mode 100644 index 00000000..981464d6 --- /dev/null +++ b/pkg/kube/object-patch/operation.go @@ -0,0 +1,318 @@ +package objectpatch + +import ( + "fmt" + + "github.com/deckhouse/deckhouse/pkg/log" + objectpatch "github.com/deckhouse/module-sdk/pkg/go-hook/object-patch" + "github.com/hashicorp/go-multierror" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" +) + +// A JSON and YAML representation of the operation for shell hooks +type OperationSpec struct { + Operation OperationType `json:"operation" yaml:"operation"` + ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Subresource string `json:"subresource,omitempty" yaml:"subresource,omitempty"` + + Object interface{} `json:"object,omitempty" yaml:"object,omitempty"` + JQFilter string `json:"jqFilter,omitempty" yaml:"jqFilter,omitempty"` + MergePatch interface{} `json:"mergePatch,omitempty" yaml:"mergePatch,omitempty"` + JSONPatch interface{} `json:"jsonPatch,omitempty" yaml:"jsonPatch,omitempty"` + + IgnoreMissingObject bool `json:"ignoreMissingObject" yaml:"ignoreMissingObject"` + IgnoreHookError bool `json:"ignoreHookError" yaml:"ignoreHookError"` +} + +type OperationType string + +const ( + CreateOrUpdate OperationType = "CreateOrUpdate" + Create OperationType = "Create" + CreateIfNotExists OperationType = "CreateIfNotExists" + + Delete OperationType = "Delete" + DeleteInBackground OperationType = "DeleteInBackground" + DeleteNonCascading OperationType = "DeleteNonCascading" + + JQPatch OperationType = "JQPatch" + MergePatch OperationType = "MergePatch" + JSONPatch OperationType = "JSONPatch" +) + +// GetPatchStatusOperationsOnHookError returns list of Patch/Filter operations eligible for execution on Hook Error +func GetPatchStatusOperationsOnHookError(operations []objectpatch.Operation) []objectpatch.Operation { + patchStatusOperations := make([]objectpatch.Operation, 0) + for _, op := range operations { + switch operation := op.(type) { + case *filterOperation: + if operation.subresource == "/status" && operation.ignoreHookError { + patchStatusOperations = append(patchStatusOperations, operation) + } + case *patchOperation: + if operation.subresource == "/status" && operation.ignoreHookError { + patchStatusOperations = append(patchStatusOperations, operation) + } + } + } + + return patchStatusOperations +} + +func ParseOperations(specBytes []byte) ([]objectpatch.Operation, error) { + log.Debugf("parsing patcher operations:\n%s", specBytes) + + specs, err := unmarshalFromJSONOrYAML(specBytes) + if err != nil { + return nil, err + } + + validationErrors := &multierror.Error{} + ops := make([]objectpatch.Operation, 0) + for _, spec := range specs { + err = ValidateOperationSpec(spec, GetSchema("v0"), "") + if err != nil { + validationErrors = multierror.Append(validationErrors, err) + break + } + ops = append(ops, NewFromOperationSpec(spec)) + } + + return ops, validationErrors.ErrorOrNil() +} + +type createOperation struct { + object interface{} + subresource string + + ignoreIfExists bool + updateIfExists bool +} + +func (op *createOperation) Description() string { + return "Create object" +} + +func (op *createOperation) SetSubresource(subres string) { + op.subresource = subres +} + +func (op *createOperation) SetIgnoreIfExists(ignore bool) { + op.ignoreIfExists = ignore +} + +func (op *createOperation) SetUpdateIfExists(update bool) { + op.updateIfExists = update +} + +type deleteOperation struct { + // Object coordinates. + apiVersion string + kind string + namespace string + name string + subresource string + + // Delete options. + deletionPropagation metav1.DeletionPropagation +} + +func (op *deleteOperation) Description() string { + return fmt.Sprintf("Delete object %s/%s/%s/%s", op.apiVersion, op.kind, op.namespace, op.name) +} + +func (op *deleteOperation) SetSubresource(subres string) { + op.subresource = subres +} + +func (op *deleteOperation) SetDeletePropagation(prop metav1.DeletionPropagation) { + op.deletionPropagation = prop +} + +type patchOperation struct { + // Object coordinates for patch and delete. + apiVersion string + kind string + namespace string + name string + subresource string + + // Patch options. + patchType types.PatchType + patch interface{} + ignoreMissingObject bool + ignoreHookError bool +} + +func (op *patchOperation) Description() string { + return fmt.Sprintf("Patch object %s/%s/%s/%s using %s patch", op.apiVersion, op.kind, op.namespace, op.name, op.patchType) +} + +func (op *patchOperation) SetSubresource(subres string) { + op.subresource = subres +} + +func (op *patchOperation) SetIgnoreHookError(ignore bool) { + op.ignoreHookError = ignore +} + +func (op *patchOperation) SetIgnoreMissingObject(ignore bool) { + op.ignoreMissingObject = ignore +} + +type filterOperation struct { + // Object coordinates for patch and delete. + apiVersion string + kind string + namespace string + name string + subresource string + + // Patch options. + filterFunc func(*unstructured.Unstructured) (*unstructured.Unstructured, error) + ignoreMissingObject bool + ignoreHookError bool +} + +func (op *filterOperation) Description() string { + return fmt.Sprintf("Filter object %s/%s/%s/%s", op.apiVersion, op.kind, op.namespace, op.name) +} + +func (op *filterOperation) SetSubresource(subres string) { + op.subresource = subres +} + +func (op *filterOperation) SetIgnoreHookError(ignore bool) { + op.ignoreHookError = ignore +} + +func (op *filterOperation) SetIgnoreMissingObject(ignore bool) { + op.ignoreMissingObject = ignore +} + +func NewFromOperationSpec(spec OperationSpec) objectpatch.Operation { + switch spec.Operation { + case Create: + return NewCreateOperation(spec.Object, + WithSubresource(spec.Subresource)) + case CreateIfNotExists: + return NewCreateOperation(spec.Object, + WithSubresource(spec.Subresource), + IgnoreIfExists()) + case CreateOrUpdate: + return NewCreateOperation(spec.Object, + WithSubresource(spec.Subresource), + UpdateIfExists()) + case Delete: + return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, + WithSubresource(spec.Subresource)) + case DeleteInBackground: + return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, + WithSubresource(spec.Subresource), + InBackground()) + case DeleteNonCascading: + return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, + WithSubresource(spec.Subresource), + NonCascading()) + case JQPatch: + return NewFilterPatchOperation( + func(u *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return applyJQPatch(spec.JQFilter, u) + }, + spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, + WithSubresource(spec.Subresource), + WithIgnoreMissingObject(spec.IgnoreMissingObject), + WithIgnoreHookError(spec.IgnoreHookError), + ) + case MergePatch: + return NewMergePatchOperation(spec.MergePatch, + spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, + WithSubresource(spec.Subresource), + WithIgnoreMissingObject(spec.IgnoreMissingObject), + WithIgnoreHookError(spec.IgnoreHookError), + ) + case JSONPatch: + return NewJSONPatchOperation(spec.JSONPatch, + spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, + WithSubresource(spec.Subresource), + WithIgnoreMissingObject(spec.IgnoreMissingObject), + WithIgnoreHookError(spec.IgnoreHookError), + ) + } + + // Should not be reached! + return nil +} + +func NewCreateOperation(obj interface{}, options ...objectpatch.CreateOption) objectpatch.Operation { + op := &createOperation{ + object: obj, + } + for _, option := range options { + option.ApplyToCreate(op) + } + return op +} + +func NewDeleteOperation(apiVersion, kind, namespace, name string, options ...objectpatch.DeleteOption) objectpatch.Operation { + op := &deleteOperation{ + apiVersion: apiVersion, + kind: kind, + namespace: namespace, + name: name, + deletionPropagation: metav1.DeletePropagationForeground, + } + for _, option := range options { + option.ApplyToDelete(op) + } + return op +} + +func NewMergePatchOperation(mergePatch interface{}, apiVersion, kind, namespace, name string, options ...objectpatch.PatchOption) objectpatch.Operation { + op := &patchOperation{ + apiVersion: apiVersion, + kind: kind, + namespace: namespace, + name: name, + patch: mergePatch, + patchType: types.MergePatchType, + } + for _, option := range options { + option.ApplyToPatch(op) + } + return op +} + +func NewJSONPatchOperation(jsonPatch interface{}, apiVersion, kind, namespace, name string, options ...objectpatch.PatchOption) objectpatch.Operation { + op := &patchOperation{ + apiVersion: apiVersion, + kind: kind, + namespace: namespace, + name: name, + patch: jsonPatch, + patchType: types.JSONPatchType, + } + for _, option := range options { + option.ApplyToPatch(op) + } + return op +} + +func NewFilterPatchOperation(filter func(*unstructured.Unstructured) (*unstructured.Unstructured, error), apiVersion, kind, namespace, name string, options ...objectpatch.FilterOption) objectpatch.Operation { + op := &filterOperation{ + apiVersion: apiVersion, + kind: kind, + namespace: namespace, + name: name, + filterFunc: filter, + } + for _, option := range options { + option.ApplyToFilter(op) + } + return op +} diff --git a/pkg/kube/object-patch/options.go b/pkg/kube/object-patch/options.go new file mode 100644 index 00000000..eaff3056 --- /dev/null +++ b/pkg/kube/object-patch/options.go @@ -0,0 +1,122 @@ +package objectpatch + +import ( + objectpatch "github.com/deckhouse/module-sdk/pkg/go-hook/object-patch" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type subresourceHolder struct { + subresource string +} + +// WithSubresource options specifies a subresource to operate on. +func WithSubresource(s string) *subresourceHolder { + return &subresourceHolder{subresource: s} +} + +func (s *subresourceHolder) ApplyToCreate(operation objectpatch.CreateOperation) { + operation.SetSubresource(s.subresource) +} + +func (s *subresourceHolder) ApplyToDelete(operation objectpatch.DeleteOperation) { + operation.SetSubresource(s.subresource) +} + +func (s *subresourceHolder) ApplyToPatch(operation objectpatch.PatchOperation) { + operation.SetSubresource(s.subresource) +} + +func (s *subresourceHolder) ApplyToFilter(operation objectpatch.FilterOperation) { + operation.SetSubresource(s.subresource) +} + +type ignoreHookError struct { + ignoreError bool +} + +// IgnoreHookError allows Applying patches for a Status subresource even if the hook fails +func IgnoreHookError() *ignoreHookError { + return WithIgnoreHookError(true) +} + +func WithIgnoreHookError(ignoreError bool) *ignoreHookError { + return &ignoreHookError{ignoreError: ignoreError} +} + +type ignoreMissingObject struct { + ignore bool +} + +func (i *ignoreHookError) ApplyToPatch(operation objectpatch.PatchOperation) { + operation.SetIgnoreHookError(i.ignoreError) +} + +func (i *ignoreHookError) ApplyToFilter(operation objectpatch.FilterOperation) { + operation.SetIgnoreHookError(i.ignoreError) +} + +// IgnoreMissingObject do not return error if object exists for Patch and Filter operations. +func IgnoreMissingObject() *ignoreMissingObject { + return WithIgnoreMissingObject(true) +} + +func WithIgnoreMissingObject(ignore bool) *ignoreMissingObject { + return &ignoreMissingObject{ignore: ignore} +} + +func (i *ignoreMissingObject) ApplyToPatch(operation objectpatch.PatchOperation) { + operation.SetIgnoreMissingObject(i.ignore) +} + +func (i *ignoreMissingObject) ApplyToFilter(operation objectpatch.FilterOperation) { + operation.SetIgnoreMissingObject(i.ignore) +} + +type ignoreIfExists struct { + ignore bool +} + +// IgnoreIfExists is an option for Create to not return error if object is already exists. +func IgnoreIfExists() objectpatch.CreateOption { + return &ignoreIfExists{ignore: true} +} + +func (i *ignoreIfExists) ApplyToCreate(operation objectpatch.CreateOperation) { + operation.SetIgnoreIfExists(i.ignore) +} + +type updateIfExists struct { + update bool +} + +// UpdateIfExists is an option for Create to update object if it already exists. +func UpdateIfExists() objectpatch.CreateOption { + return &updateIfExists{update: true} +} + +func (u *updateIfExists) ApplyToCreate(operation objectpatch.CreateOperation) { + operation.SetUpdateIfExists(u.update) +} + +type deletePropogation struct { + propagation metav1.DeletionPropagation +} + +func (d *deletePropogation) ApplyToDelete(operation objectpatch.DeleteOperation) { + operation.SetDeletePropagation(d.propagation) +} + +// InForeground is a default propagation option for Delete +func InForeground() objectpatch.DeleteOption { + return &deletePropogation{propagation: metav1.DeletePropagationForeground} +} + +// InBackground is a propagation option for Delete +func InBackground() objectpatch.DeleteOption { + return &deletePropogation{propagation: metav1.DeletePropagationBackground} +} + +// NonCascading is a propagation option for Delete +func NonCascading() objectpatch.DeleteOption { + return &deletePropogation{propagation: metav1.DeletePropagationOrphan} +} diff --git a/pkg/kube/object-patch/patch.go b/pkg/kube/object-patch/patch.go new file mode 100644 index 00000000..2e929197 --- /dev/null +++ b/pkg/kube/object-patch/patch.go @@ -0,0 +1,302 @@ +package objectpatch + +import ( + "bytes" + "context" + "fmt" + "time" + + "github.com/deckhouse/deckhouse/pkg/log" + objectpatch "github.com/deckhouse/module-sdk/pkg/go-hook/object-patch" + "github.com/hashicorp/go-multierror" + gerror "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/util/retry" +) + +type ObjectPatcher struct { + kubeClient KubeClient + logger *log.Logger +} + +type KubeClient interface { + kubernetes.Interface + Dynamic() dynamic.Interface + GroupVersionResource(apiVersion string, kind string) (schema.GroupVersionResource, error) +} + +func NewObjectPatcher(kubeClient KubeClient, logger *log.Logger) *ObjectPatcher { + return &ObjectPatcher{ + kubeClient: kubeClient, + logger: logger.With("operator.component", "KubernetesObjectPatcher"), + } +} + +func (o *ObjectPatcher) ExecuteOperations(ops []objectpatch.Operation) error { + log.Debug("Starting execute operations process") + defer log.Debug("Finished execute operations process") + + applyErrors := &multierror.Error{} + for _, op := range ops { + log.Debugf("Applying operation: %s", op.Description()) + if err := o.ExecuteOperation(op); err != nil { + err = gerror.WithMessage(err, op.Description()) + applyErrors = multierror.Append(applyErrors, err) + } + } + + return applyErrors.ErrorOrNil() +} + +func (o *ObjectPatcher) ExecuteOperation(operation objectpatch.Operation) error { + if operation == nil { + return nil + } + + switch v := operation.(type) { + case *createOperation: + return o.executeCreateOperation(v) + case *deleteOperation: + return o.executeDeleteOperation(v) + case *patchOperation: + return o.executePatchOperation(v) + case *filterOperation: + return o.executeFilterOperation(v) + } + + return nil +} + +func (o *ObjectPatcher) executeCreateOperation(op *createOperation) error { + if op.object == nil { + return fmt.Errorf("cannot create empty object") + } + + // Convert object from interface{}. + object, err := toUnstructured(op.object) + if err != nil { + return err + } + + apiVersion := object.GetAPIVersion() + kind := object.GetKind() + + wrapErr := func(e error) error { + objectID := fmt.Sprintf("%s/%s/%s/%s", apiVersion, kind, object.GetNamespace(), object.GetName()) + return gerror.WithMessage(e, objectID) + } + + gvk, err := o.kubeClient.GroupVersionResource(apiVersion, kind) + if err != nil { + return wrapErr(err) + } + + log.Debug("Started Create API call") + _, err = o.kubeClient.Dynamic(). + Resource(gvk). + Namespace(object.GetNamespace()). + Create(context.TODO(), object, metav1.CreateOptions{}, generateSubresources(op.subresource)...) + log.Debug("Finished Create API call") + + objectExists := errors.IsAlreadyExists(err) + + if objectExists && op.ignoreIfExists { + log.Debug("resource already exists, exiting without error") + return nil + } + + if objectExists && op.updateIfExists { + log.Debug("Object already exists, attempting to Update it with optimistic lock") + + return retry.RetryOnConflict(retry.DefaultBackoff, func() error { + log.Debug("Started Get API call") + existingObj, err := o.kubeClient.Dynamic(). + Resource(gvk). + Namespace(object.GetNamespace()). + Get(context.TODO(), object.GetName(), metav1.GetOptions{}, generateSubresources(op.subresource)...) + log.Debug("Finished Get API call") + if err != nil { + return wrapErr(err) + } + + objCopy := object.DeepCopy() + objCopy.SetResourceVersion(existingObj.GetResourceVersion()) + + log.Debug("Started Update API call") + _, err = o.kubeClient.Dynamic(). + Resource(gvk). + Namespace(objCopy.GetNamespace()). + Update(context.TODO(), objCopy, metav1.UpdateOptions{}, generateSubresources(op.subresource)...) + log.Debug("Finished Update API call") + return wrapErr(err) + }) + } + + // Simply return result of a Create call if no ignore options are in play. + return wrapErr(err) +} + +// executePatchOperation applies a patch to the specified object using API call Patch. +// +// There 2 types of patches: +// - Merge — use Patch API call with MergePatchType. +// - JSON — use Patch API call with JSONPatchType. +// +// Other options: +// - WithSubresource — a subresource argument for Patch or Update API call. +// - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails +func (o *ObjectPatcher) executePatchOperation(op *patchOperation) error { + if op.patchType == types.MergePatchType { + log.Debug("Started MergePatchObject") + defer log.Debug("Finished MergePatchObject") + } + if op.patchType == types.JSONPatchType { + log.Debug("Started JSONPatchObject") + defer log.Debug("Finished JSONPatchObject") + } + + patchBytes, err := convertPatchToBytes(op.patch) + if err != nil { + return fmt.Errorf("encode %s patch for %s/%s/%s/%s: %v", op.patchType, op.apiVersion, op.kind, op.namespace, op.name, err) + } + if patchBytes == nil { + return fmt.Errorf("%s patch is nil for %s/%s/%s/%s", op.patchType, op.apiVersion, op.kind, op.namespace, op.name) + } + + gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) + if err != nil { + return err + } + + log.Debug("Started Patch API call") + _, err = o.kubeClient.Dynamic(). + Resource(gvk). + Namespace(op.namespace). + Patch(context.TODO(), op.name, op.patchType, patchBytes, metav1.PatchOptions{}, generateSubresources(op.subresource)...) + log.Debug("Finished Patch API call") + + if op.ignoreMissingObject && errors.IsNotFound(err) { + return nil + } + return err +} + +// executeFilterOperation retrieves a specified object, modified it with +// filterFunc and calls update. + +// Other options: +// - WithSubresource — a subresource argument for Patch or Update API call. +// - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails +func (o *ObjectPatcher) executeFilterOperation(op *filterOperation) error { + var err error + + if op.filterFunc == nil { + return fmt.Errorf("FilterFunc is nil") + } + + gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) + if err != nil { + return err + } + + err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { + log.Debug("Started Get API call") + obj, err := o.kubeClient.Dynamic(). + Resource(gvk). + Namespace(op.namespace). + Get(context.TODO(), op.name, metav1.GetOptions{}) + log.Debug("Finished Get API call") + if op.ignoreMissingObject && errors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + + log.Debug("Started filtering object") + filteredObj, err := op.filterFunc(obj) + log.Debug("Finished filtering object") + if err != nil { + return err + } + + if equality.Semantic.DeepEqual(obj, filteredObj) { + return nil + } + + var filteredObjBuf bytes.Buffer + err = unstructured.UnstructuredJSONScheme.Encode(filteredObj, &filteredObjBuf) + if err != nil { + return err + } + + log.Debug("Started Update API call") + _, err = o.kubeClient.Dynamic(). + Resource(gvk). + Namespace(op.namespace). + Update(context.TODO(), filteredObj, metav1.UpdateOptions{}, generateSubresources(op.subresource)...) + log.Debug("Finished Update API call") + if err != nil { + return err + } + + return nil + }) + + return err +} + +func (o *ObjectPatcher) executeDeleteOperation(op *deleteOperation) error { + gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) + if err != nil { + return err + } + + log.Debug("Started Delete API call") + err = o.kubeClient.Dynamic(). + Resource(gvk). + Namespace(op.namespace). + Delete(context.TODO(), op.name, metav1.DeleteOptions{PropagationPolicy: &op.deletionPropagation}, op.subresource) + + log.Debug("Finished Delete API call") + if errors.IsNotFound(err) { + return nil + } + + if err != nil { + return err + } + + if op.deletionPropagation != metav1.DeletePropagationForeground { + return nil + } + + log.Debug("Waiting for object deletion") + + err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 20*time.Second, false, func(ctx context.Context) (done bool, err error) { + log.Debug("Started Get API call") + _, err = o.kubeClient.Dynamic(). + Resource(gvk). + Namespace(op.namespace). + Get(ctx, op.name, metav1.GetOptions{}) + + log.Debug("Finished Get API call") + if errors.IsNotFound(err) { + return true, nil + } + + return false, err + }) + + return err +} diff --git a/pkg/kube/object-patch/patch_collector.go b/pkg/kube/object-patch/patch_collector.go new file mode 100644 index 00000000..ebb1ecae --- /dev/null +++ b/pkg/kube/object-patch/patch_collector.go @@ -0,0 +1,86 @@ +package objectpatch + +import ( + objectpatch "github.com/deckhouse/module-sdk/pkg/go-hook/object-patch" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type PatchCollector struct { + patchOperations []objectpatch.Operation +} + +// NewPatchCollector creates Operation collector to use within Go hooks. +func NewPatchCollector() *PatchCollector { + return &PatchCollector{ + patchOperations: make([]objectpatch.Operation, 0), + } +} + +// Create or update an object. +// +// Options: +// - WithSubresource - create a specified subresource +// - IgnoreIfExists - do not return error if the specified object exists +// - UpdateIfExists - call Update if the specified object exists +func (dop *PatchCollector) Create(object interface{}, options ...objectpatch.CreateOption) { + dop.add(NewCreateOperation(object, options...)) +} + +// Delete uses apiVersion, kind, namespace and name to delete object from cluster. +// +// Options: +// - WithSubresource - delete a specified subresource +// - InForeground - remove object when all dependants are removed (default) +// - InBackground - remove object immediately, dependants remove in background +// - NonCascading - remove object, dependants become orphan +// +// Missing object is ignored by default. +func (dop *PatchCollector) Delete(apiVersion, kind, namespace, name string, options ...objectpatch.DeleteOption) { + dop.add(NewDeleteOperation(apiVersion, kind, namespace, name, options...)) +} + +// MergePatch applies a merge patch to the specified object using API call Patch. +// +// Options: +// - WithSubresource — a subresource argument for Patch call. +// - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails +func (dop *PatchCollector) MergePatch(mergePatch interface{}, apiVersion, kind, namespace, name string, options ...objectpatch.PatchOption) { + dop.add(NewMergePatchOperation(mergePatch, apiVersion, kind, namespace, name, options...)) +} + +// JSONPatch applies a json patch to the specified object using API call Patch. +// +// Options: +// - WithSubresource — a subresource argument for Patch call. +// - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails +func (dop *PatchCollector) JSONPatch(jsonPatch interface{}, apiVersion, kind, namespace, name string, options ...objectpatch.PatchOption) { + dop.add(NewJSONPatchOperation(jsonPatch, apiVersion, kind, namespace, name, options...)) +} + +// Filter retrieves a specified object, modified it with +// filterFunc and calls update. +// +// Options: +// - WithSubresource — a subresource argument for Patch call. +// - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails +// +// Note: do not modify and return argument in filterFunc, +// use FromUnstructured to instantiate a concrete type or modify after DeepCopy. +func (dop *PatchCollector) Filter( + filterFunc func(*unstructured.Unstructured) (*unstructured.Unstructured, error), + apiVersion, kind, namespace, name string, options ...objectpatch.FilterOption, +) { + dop.add(NewFilterPatchOperation(filterFunc, apiVersion, kind, namespace, name, options...)) +} + +// Operations returns all collected operations +func (dop *PatchCollector) Operations() []objectpatch.Operation { + return dop.patchOperations +} + +func (dop *PatchCollector) add(operation objectpatch.Operation) { + dop.patchOperations = append(dop.patchOperations, operation) +} diff --git a/pkg/kube/object-patch/patch_test.go b/pkg/kube/object-patch/patch_test.go new file mode 100644 index 00000000..ec0b5f43 --- /dev/null +++ b/pkg/kube/object-patch/patch_test.go @@ -0,0 +1,949 @@ +package objectpatch + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/deckhouse/deckhouse/pkg/log" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/flant/kube-client/fake" + "github.com/flant/kube-client/manifest" +) + +func mustReadFile(t *testing.T, filePath string) []byte { + t.Helper() + content, err := os.ReadFile(filePath) + require.NoError(t, err) + return content +} + +func Test_ParseOperations(t *testing.T) { + const ( + shouldNotBeError = false + shouldBeError = true + ) + + tests := []struct { + name string + testFilePath string + expectError bool + }{ + { + "valid create", + "testdata/serialized_operations/valid_create.yaml", + shouldNotBeError, + }, + { + "invalid create", + "testdata/serialized_operations/invalid_create.yaml", + shouldBeError, + }, + { + "valid delete", + "testdata/serialized_operations/valid_delete.yaml", + shouldNotBeError, + }, + { + "invalid delete", + "testdata/serialized_operations/invalid_delete.yaml", + shouldBeError, + }, + { + "valid patch", + "testdata/serialized_operations/valid_patch.yaml", + shouldNotBeError, + }, + { + "invalid patch", + "testdata/serialized_operations/invalid_patch.yaml", + shouldBeError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testSpecs := mustReadFile(t, tt.testFilePath) + + _, err := ParseOperations(testSpecs) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func Test_PatchOperations(t *testing.T) { + const ( + namespace = "default" + name = "testcm" + missingName = "missing-object" + configMap = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: testcm +data: + foo: "bar" +` + newField = "baz" + newValue = "quux" + shouldNotAdd = false + shouldAdd = true + shouldNotBeError = false + shouldBeError = true + ) + + // Filter func to add a new field. + filter := func(u *unstructured.Unstructured) (*unstructured.Unstructured, error) { + res := u.DeepCopy() + data := res.Object["data"].(map[string]interface{}) + data[newField] = newValue + res.Object["data"] = data + return res, nil + } + + tests := []struct { + name string + fn func(patcher *ObjectPatcher) error + expectAdd bool + expectError bool + }{ + { + "merge patch", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewMergePatchOperation( + fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), + "v1", "ConfigMap", namespace, name, + )) + }, + shouldAdd, + shouldNotBeError, + }, + { + "merge patch a missing object", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewMergePatchOperation( + fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), + "v1", "ConfigMap", namespace, missingName, + )) + }, + shouldNotAdd, + shouldBeError, + }, + { + "merge patch with ignoreMissingObject", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewMergePatchOperation( + fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), + "v1", "ConfigMap", namespace, missingName, + IgnoreMissingObject(), + )) + }, + shouldNotAdd, + shouldNotBeError, + }, + { + "merge patch using map", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewMergePatchOperation( + map[string]interface{}{ + "data": map[string]interface{}{ + newField: newValue, + }, + }, + "v1", "ConfigMap", namespace, name, + )) + }, + shouldAdd, + shouldNotBeError, + }, + { + "merge patch via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: MergePatch +kind: ConfigMap +namespace: %s +name: %s +mergePatch: + data: + %s: "%s" +`, namespace, name, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldAdd, + shouldNotBeError, + }, + { + "merge patch a missing object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: MergePatch +kind: ConfigMap +namespace: %s +name: %s +mergePatch: + data: + %s: "%s" +`, namespace, missingName, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotAdd, + shouldBeError, + }, + { + "merge patch with ignoreMissingObject via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: MergePatch +kind: ConfigMap +namespace: %s +name: %s +ignoreMissingObject: true +mergePatch: + data: + %s: "%s" +`, namespace, missingName, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotAdd, + shouldNotBeError, + }, + { + "merge patch via string in YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: MergePatch +kind: ConfigMap +namespace: %s +name: %s +mergePatch: | + data: + %s: "%s" +`, namespace, name, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldAdd, + shouldNotBeError, + }, + { + "merge patch via stringified JSON in YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: MergePatch +kind: ConfigMap +namespace: %s +name: %s +mergePatch: | + {"data":{"%s":"%s"}} +`, namespace, name, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldAdd, + shouldNotBeError, + }, + { + "json patch", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewJSONPatchOperation( + // [{ "op": "replace", "path": "/data/firstField", "value": "jsonPatched"}] + fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), + "v1", "ConfigMap", namespace, name, + )) + }, + shouldAdd, + shouldNotBeError, + }, + { + "json patch a missing object", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewJSONPatchOperation( + fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), + "v1", "ConfigMap", namespace, missingName, + )) + }, + shouldNotAdd, + shouldBeError, + }, + { + "json patch with ignoreMissingObject", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewJSONPatchOperation( + fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), + "v1", "ConfigMap", namespace, missingName, + IgnoreMissingObject(), + )) + }, + shouldNotAdd, + shouldNotBeError, + }, + { + "json patch via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: JSONPatch +kind: ConfigMap +namespace: %s +name: %s +jsonPatch: + - op: add + path: /data/%s + value: %s +`, namespace, name, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldAdd, + shouldNotBeError, + }, + { + "json patch a missing object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: JSONPatch +kind: ConfigMap +namespace: %s +name: %s +jsonPatch: + - op: add + path: /data/%s + value: %s +`, namespace, missingName, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotAdd, + shouldBeError, + }, + { + "json patch with ignoreMissingObject via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: JSONPatch +kind: ConfigMap +namespace: %s +name: %s +ignoreMissingObject: true +jsonPatch: + - op: add + path: /data/%s + value: %s +`, namespace, missingName, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotAdd, + shouldNotBeError, + }, + { + "json patch via stringified JSON in YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: JSONPatch +kind: ConfigMap +namespace: %s +name: %s +jsonPatch: | + [{"op":"add", "path":"/data/%s", "value":"%s"}] +`, namespace, name, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldAdd, + shouldNotBeError, + }, + { + "filter patch", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewFilterPatchOperation( + filter, + "v1", "ConfigMap", namespace, name, + )) + }, + shouldAdd, + shouldNotBeError, + }, + { + "filter patch missing object", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewFilterPatchOperation( + filter, + "v1", "ConfigMap", namespace, missingName, + )) + }, + shouldNotAdd, + shouldBeError, + }, + { + "filter patch with ignoreMissingObject", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewFilterPatchOperation( + filter, + "v1", "ConfigMap", namespace, missingName, + IgnoreMissingObject(), + )) + }, + shouldNotAdd, + shouldNotBeError, + }, + { + "JQ patch via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: JQPatch +kind: ConfigMap +namespace: %s +name: %s +jqFilter: | + .data.%s = "%s" +`, namespace, name, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldAdd, + shouldNotBeError, + }, + { + "JQ patch missing object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: JQPatch +kind: ConfigMap +namespace: %s +name: %s +jqFilter: | + .data.%s = "%s" +`, namespace, missingName, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotAdd, + shouldBeError, + }, + { + "JQ patch with ignoreMissingObject via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: JQPatch +kind: ConfigMap +namespace: %s +name: %s +ignoreMissingObject: true +jqFilter: | + .data.%s = "%s" +`, namespace, missingName, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotAdd, + shouldNotBeError, + }, + { + "update existing object", + func(patcher *ObjectPatcher) error { + obj := manifest.MustFromYAML(fmt.Sprintf(` +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: %s + name: %s +data: + foo: "bar" + %s: "%s" +`, namespace, name, newField, newValue)).Unstructured() + return patcher.ExecuteOperation(NewCreateOperation(obj, UpdateIfExists())) + }, + shouldAdd, + shouldNotBeError, + }, + { + "update existing object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: CreateOrUpdate +object: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: %s + name: %s + data: + foo: "bar" + %s: "%s" +`, namespace, name, newField, newValue))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldAdd, + shouldNotBeError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Prepare fake cluster: create a Namespace and a ConfigMap. + cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, configMap) + + // Apply MergePatch: add a new field in data section. + patcher := NewObjectPatcher(cluster.Client, log.NewNop()) + + err := tt.fn(patcher) + + // Check error expectation. + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + // Fetch updated object and check fields in data section. + cmObj := new(v1.ConfigMap) + fetchObject(t, cluster, namespace, configMap, cmObj) + + require.Equal(t, "bar", cmObj.Data["foo"]) + if tt.expectAdd { + require.Equal(t, newValue, cmObj.Data[newField]) + } else { + require.NotContains(t, cmObj.Data, newField) + } + }) + } +} + +func Test_CreateOperations(t *testing.T) { + const ( + namespace = "default" + existingName = "testcm" + existingConfigMap = ` +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: default + name: testcm +data: + foo: "bar" +` + newName = "newtestcm" + newConfigMap = ` +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: default + name: newtestcm +data: + foo: "bar" +` + shouldNotCreateNew = false + shouldCreateNew = true + shouldNotBeError = false + shouldBeError = true + ) + + tests := []struct { + name string + fn func(patcher *ObjectPatcher) error + expectNewExists bool + expectError bool + }{ + { + "create new object", + func(patcher *ObjectPatcher) error { + obj := manifest.MustFromYAML(newConfigMap).Unstructured() + return patcher.ExecuteOperation(NewCreateOperation(obj)) + }, + shouldCreateNew, + shouldNotBeError, + }, + { + "create new object ignore existing object", + func(patcher *ObjectPatcher) error { + obj := manifest.MustFromYAML(newConfigMap).Unstructured() + return patcher.ExecuteOperation(NewCreateOperation(obj, IgnoreIfExists())) + }, + shouldCreateNew, + shouldNotBeError, + }, + { + "create existing object", + func(patcher *ObjectPatcher) error { + obj := manifest.MustFromYAML(existingConfigMap).Unstructured() + return patcher.ExecuteOperation(NewCreateOperation(obj)) + }, + shouldNotCreateNew, + shouldBeError, + }, + { + "create ignore existing object", + func(patcher *ObjectPatcher) error { + obj := manifest.MustFromYAML(existingConfigMap).Unstructured() + return patcher.ExecuteOperation(NewCreateOperation(obj, IgnoreIfExists())) + }, + shouldNotCreateNew, + shouldNotBeError, + }, + { + "create new object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: Create +object: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: %s + name: %s + data: + foo: bar +`, namespace, newName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldCreateNew, + shouldNotBeError, + }, + { + "create existing object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: Create +object: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: %s + name: %s + data: + foo: bar +`, namespace, existingName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotCreateNew, + shouldBeError, + }, + { + "create ignore existing object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: CreateIfNotExists +object: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: %s + name: %s + data: + foo: bar +`, namespace, existingName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotCreateNew, + shouldNotBeError, + }, + { + "create or update new object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: CreateOrUpdate +object: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: %s + name: %s + data: + foo: bar +`, namespace, newName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldCreateNew, + shouldNotBeError, + }, + { + "create new object via string in YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: Create +object: | + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: %s + name: %s + data: + foo: bar +`, namespace, newName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldCreateNew, + shouldNotBeError, + }, + { + "create new object via stringified JSON in YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: Create +object: | + {"apiVersion":"v1", "kind":"ConfigMap", "metadata": { + "namespace":"%s", "name":"%s"}, + "data":{"foo":"bar"}} +`, namespace, newName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldCreateNew, + shouldNotBeError, + }, + { + "create new object via stringified JSON in JSON spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +{"operation": "Create", +"object": "{ + \"apiVersion\":\"v1\", \"kind\":\"ConfigMap\", + \"metadata\": {\"namespace\":\"%s\", \"name\":\"%s\"}, + \"data\":{\"foo\":\"bar\"}} +"} +`, namespace, newName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldCreateNew, + shouldNotBeError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Prepare fake cluster: create a Namespace and a ConfigMap. + cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, existingConfigMap) + + // Apply MergePatch: add a new field in data section. + patcher := NewObjectPatcher(cluster.Client, log.NewNop()) + + err := tt.fn(patcher) + + // Check error expectation. + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + // Check if new object is created. + exists := existObject(t, cluster, namespace, newConfigMap) + + if tt.expectNewExists { + require.True(t, exists) + } else { + require.False(t, exists) + } + }) + } +} + +func Test_DeleteOperations(t *testing.T) { + const ( + namespace = "default" + existingName = "testcm" + existingConfigMap = ` +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: default + name: testcm +data: + foo: "bar" +` + missingName = "missing-object" + + shouldNotDelete = false + shouldDelete = true + shouldNotBeError = false + shouldBeError = true + ) + + tests := []struct { + name string + fn func(patcher *ObjectPatcher) error + expectDeleted bool + expectError bool + }{ + { + "delete existing object", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewDeleteOperation("", "ConfigMap", namespace, existingName)) + }, + shouldDelete, + shouldNotBeError, + }, + { + "delete missing object", + func(patcher *ObjectPatcher) error { + return patcher.ExecuteOperation(NewDeleteOperation("", "ConfigMap", namespace, missingName)) + }, + shouldNotDelete, + shouldNotBeError, + }, + { + "delete existing object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: Delete +kind: ConfigMap +namespace: %s +name: %s +`, namespace, existingName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldDelete, + shouldNotBeError, + }, + { + "delete missing object via YAML spec", + func(patcher *ObjectPatcher) error { + operations, err := ParseOperations([]byte(fmt.Sprintf(` +operation: Delete +kind: ConfigMap +namespace: %s +name: %s +`, namespace, missingName))) + if err != nil { + return err + } + return patcher.ExecuteOperations(operations) + }, + shouldNotDelete, + shouldNotBeError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Prepare fake cluster: create a Namespace and a ConfigMap. + cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, existingConfigMap) + + // Apply MergePatch: add a new field in data section. + patcher := NewObjectPatcher(cluster.Client, log.NewNop()) + + err := tt.fn(patcher) + + // Check error expectation. + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + // Check if new object is created. + exists := existObject(t, cluster, namespace, existingConfigMap) + + if tt.expectDeleted { + require.False(t, exists) + } else { + require.True(t, exists) + } + }) + } +} + +func newFakeClusterWithNamespaceAndObjects(t *testing.T, ns string, objects ...string) *fake.Cluster { + t.Helper() + + // Prepare fake cluster: create a Namespace and a ConfigMap. + cluster := fake.NewFakeCluster(fake.ClusterVersionV119) + cluster.CreateNs(ns) + + for _, object := range objects { + mft := manifest.MustFromYAML(object) + err := cluster.Create(ns, mft) + require.NoError(t, err) + } + + return cluster +} + +func fetchObject(t *testing.T, cluster *fake.Cluster, ns string, objYAML string, object interface{}) { + t.Helper() + mft, err := manifest.NewFromYAML(objYAML) + require.NoError(t, err) + + gvk := cluster.MustFindGVR(mft.ApiVersion(), mft.Kind()) + obj, err := cluster.Client.Dynamic().Resource(*gvk).Namespace(ns).Get(context.TODO(), mft.Name(), metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, obj) + + err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), object) + require.NoError(t, err) +} + +func existObject(t *testing.T, cluster *fake.Cluster, ns string, objYAML string) bool { + mft := manifest.MustFromYAML(objYAML) + gvk := cluster.MustFindGVR(mft.ApiVersion(), mft.Kind()) + obj, err := cluster.Client.Dynamic().Resource(*gvk).Namespace(ns).Get(context.TODO(), mft.Name(), metav1.GetOptions{}) + if errors.IsNotFound(err) { + return false + } + require.NoError(t, err) + return obj != nil +} diff --git a/pkg/kube/object-patch/testdata/serialized_operations/invalid_create.yaml b/pkg/kube/object-patch/testdata/serialized_operations/invalid_create.yaml new file mode 100644 index 00000000..319e2f4e --- /dev/null +++ b/pkg/kube/object-patch/testdata/serialized_operations/invalid_create.yaml @@ -0,0 +1,8 @@ +--- +operation: Create +namespace: default +object: +--- +operation: CreateOrUpdate +namespace: default +object: \ No newline at end of file diff --git a/pkg/kube/object-patch/testdata/serialized_operations/invalid_delete.yaml b/pkg/kube/object-patch/testdata/serialized_operations/invalid_delete.yaml new file mode 100644 index 00000000..926f9333 --- /dev/null +++ b/pkg/kube/object-patch/testdata/serialized_operations/invalid_delete.yaml @@ -0,0 +1,12 @@ +--- +operation: Delete +kind: ConfigMap +name: "test" +--- +operation: DeleteInBackground +apiversion: core/v1 +name: "test" +--- +operation: DeleteNonCascading +apiversion: core/v1 +kind: ConfigMap diff --git a/pkg/kube/object-patch/testdata/serialized_operations/invalid_patch.yaml b/pkg/kube/object-patch/testdata/serialized_operations/invalid_patch.yaml new file mode 100644 index 00000000..f7b042c3 --- /dev/null +++ b/pkg/kube/object-patch/testdata/serialized_operations/invalid_patch.yaml @@ -0,0 +1,15 @@ +--- +operation: JQPatch +apiversion: core/v1 +kind: ConfigMap +name: "test" +--- +operation: MergePatch +apiversion: core/v1 +kind: ConfigMap +name: "test" +--- +operation: jsonPatch +apiversion: core/v1 +kind: ConfigMap +name: "test" diff --git a/pkg/kube/object-patch/testdata/serialized_operations/valid_create.yaml b/pkg/kube/object-patch/testdata/serialized_operations/valid_create.yaml new file mode 100644 index 00000000..8ed6904b --- /dev/null +++ b/pkg/kube/object-patch/testdata/serialized_operations/valid_create.yaml @@ -0,0 +1,16 @@ +--- +operation: Create +namespace: default +object: + apiVersion: core/v1 + kind: ConfigMap + data: + test: test +--- +operation: CreateOrUpdate +namespace: default +object: + apiVersion: core/v1 + kind: ConfigMap + data: + test: test \ No newline at end of file diff --git a/pkg/kube/object-patch/testdata/serialized_operations/valid_delete.yaml b/pkg/kube/object-patch/testdata/serialized_operations/valid_delete.yaml new file mode 100644 index 00000000..73ae807c --- /dev/null +++ b/pkg/kube/object-patch/testdata/serialized_operations/valid_delete.yaml @@ -0,0 +1,18 @@ +--- +operation: Delete +apiVersion: core/v1 +kind: ConfigMap +name: "test" +namespace: "default" +--- +operation: DeleteInBackground +apiVersion: core/v1 +kind: ConfigMap +name: "test" +namespace: "default" +--- +operation: DeleteNonCascading +apiVersion: core/v1 +kind: ConfigMap +name: "test" +namespace: "default" \ No newline at end of file diff --git a/pkg/kube/object-patch/testdata/serialized_operations/valid_patch.yaml b/pkg/kube/object-patch/testdata/serialized_operations/valid_patch.yaml new file mode 100644 index 00000000..3a45eb76 --- /dev/null +++ b/pkg/kube/object-patch/testdata/serialized_operations/valid_patch.yaml @@ -0,0 +1,27 @@ +--- +operation: JQPatch +apiVersion: core/v1 +kind: ConfigMap +name: "test" +namespace: "default" +jqFilter: '.data = {"test": test}' +--- +operation: MergePatch +apiVersion: core/v1 +kind: ConfigMap +name: "test" +namespace: "default" +mergePatch: + data: + test: test +--- +operation: JSONPatch +apiVersion: core/v1 +kind: ConfigMap +name: "test" +namespace: "default" +jsonPatch: +- op: replace + path: /data + value: + test: test diff --git a/pkg/kube/object-patch/validation.go b/pkg/kube/object-patch/validation.go new file mode 100644 index 00000000..a651b89f --- /dev/null +++ b/pkg/kube/object-patch/validation.go @@ -0,0 +1,214 @@ +package objectpatch + +import ( + "encoding/json" + "fmt" + + "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" + "github.com/hashicorp/go-multierror" + "sigs.k8s.io/yaml" +) + +var Schemas = map[string]string{ + "v0": ` +definitions: + common: + type: object + properties: + subresource: + type: string + create: + required: + - object + properties: + object: + oneOf: + - type: object + additionalProperties: true + minProperties: 1 + - type: string + delete: + type: object + required: + - kind + - name + properties: + apiVersion: + type: string + kind: + type: string + name: + type: string + patch: + type: object + required: + - kind + - name + properties: + apiVersion: + type: string + kind: + type: string + name: + type: string + ignoreMissingObject: + type: boolean + ignoreHookError: + type: boolean + +type: object +additionalProperties: false +properties: + operation: {} + namespace: {} + subresource: {} + apiVersion: {} + kind: {} + name: {} + object: {} + jsonPatch: {} + jqFilter: {} + mergePatch: {} + ignoreMissingObject: {} + ignoreHookError: {} + +oneOf: +- allOf: + - properties: + operation: + type: string + enum: ["Create", "CreateOrUpdate", "CreateIfNotExists"] + - "$ref": "#/definitions/common" + - "$ref": "#/definitions/create" +- allOf: + - properties: + operation: + type: string + enum: ["Delete", "DeleteInBackground", "DeleteNonCascading"] + - "$ref": "#/definitions/common" + - "$ref": "#/definitions/delete" +- allOf: + - oneOf: + - required: + - operation + - jqFilter + properties: + operation: + type: string + enum: ["JQPatch"] + jqFilter: + type: string + minimum: 1 + - required: + - operation + - mergePatch + properties: + operation: + type: string + enum: ["MergePatch"] + mergePatch: + oneOf: + - type: object + minProperties: 1 + - type: string + - required: + - operation + - jsonPatch + properties: + operation: + type: string + enum: ["JSONPatch"] + jsonPatch: + oneOf: + - type: array + minItems: 1 + items: + - type: object + required: ["op", "path", "value"] + properties: + op: + type: string + minLength: 1 + path: + type: string + minLength: 1 + value: {} + - type: string + - "$ref": "#/definitions/common" + - "$ref": "#/definitions/patch" +`, +} + +var SchemasCache = map[string]*spec.Schema{} + +// GetSchema returns loaded schema. +func GetSchema(name string) *spec.Schema { + if s, ok := SchemasCache[name]; ok { + return s + } + if _, ok := Schemas[name]; !ok { + return nil + } + + // ignore error because load is guaranteed by tests + SchemasCache[name], _ = LoadSchema(name) + return SchemasCache[name] +} + +// LoadSchema returns spec.Schema object loaded from yaml in Schemas map. +func LoadSchema(name string) (*spec.Schema, error) { + yml, err := swag.BytesToYAMLDoc([]byte(Schemas[name])) + if err != nil { + return nil, fmt.Errorf("yaml unmarshal: %v", err) + } + d, err := swag.YAMLToJSON(yml) + if err != nil { + return nil, fmt.Errorf("yaml to json: %v", err) + } + + s := new(spec.Schema) + + if err := json.Unmarshal(d, s); err != nil { + return nil, fmt.Errorf("json unmarshal: %v", err) + } + + err = spec.ExpandSchema(s, s, nil /*new(noopResCache)*/) + if err != nil { + return nil, fmt.Errorf("expand schema: %v", err) + } + + return s, nil +} + +// See https://github.com/kubernetes/apiextensions-apiserver/blob/1bb376f70aa2c6f2dec9a8c7f05384adbfac7fbb/pkg/apiserver/validation/validation.go#L47 +func ValidateOperationSpec(obj interface{}, s *spec.Schema, rootName string) (multiErr error) { + if s == nil { + return fmt.Errorf("validate kubernetes patch spec: schema is not provided") + } + + validator := validate.NewSchemaValidator(s, nil, rootName, strfmt.Default) + + result := validator.Validate(obj) + if result.IsValid() { + return nil + } + + allErrs := &multierror.Error{Errors: make([]error, 1)} + for _, err := range result.Errors { + allErrs = multierror.Append(allErrs, err) + } + // NOTE: no validation errors, but kubernetes patch spec is not valid! + if allErrs.Len() == 1 { + allErrs = multierror.Append(allErrs, fmt.Errorf("kubernetes patch spec is not valid")) + } + + if allErrs.Len() > 1 { + yamlObj, _ := yaml.Marshal(obj) + allErrs.Errors[0] = fmt.Errorf("can't validate document:\n%s", yamlObj) + } + + return allErrs +} diff --git a/pkg/kube/object-patch/validation_test.go b/pkg/kube/object-patch/validation_test.go new file mode 100644 index 00000000..5240744d --- /dev/null +++ b/pkg/kube/object-patch/validation_test.go @@ -0,0 +1,28 @@ +package objectpatch + +import ( + "testing" +) + +func Test_GetSchema(t *testing.T) { + schemas := []string{"v0"} + + for _, schema := range schemas { + s := GetSchema(schema) + if s == nil { + t.Fatalf("schema '%s' should not be nil", schema) + } + } +} + +func Test_LoadSchema_From_Schemas(t *testing.T) { + for schemaVer := range Schemas { + s, err := LoadSchema(schemaVer) + if err != nil { + t.Fatalf("schema '%s' should load: %v", schemaVer, err) + } + if s == nil { + t.Fatalf("schema '%s' should not be nil: %v", schemaVer, err) + } + } +} diff --git a/pkg/shell-operator/kube_client.go b/pkg/shell-operator/kube_client.go index 9ad2cb79..7aa74224 100644 --- a/pkg/shell-operator/kube_client.go +++ b/pkg/shell-operator/kube_client.go @@ -4,11 +4,11 @@ import ( "fmt" "github.com/deckhouse/deckhouse/pkg/log" - "github.com/deckhouse/module-sdk/pkg/kube/object-patch" - "github.com/deckhouse/module-sdk/pkg/metric-storage" + metricstorage "github.com/deckhouse/module-sdk/pkg/metric-storage" klient "github.com/flant/kube-client/client" "github.com/flant/shell-operator/pkg/app" + objectpatch "github.com/flant/shell-operator/pkg/kube/object-patch" utils "github.com/flant/shell-operator/pkg/utils/labels" ) diff --git a/pkg/shell-operator/operator.go b/pkg/shell-operator/operator.go index eddf57ed..af27fd5d 100644 --- a/pkg/shell-operator/operator.go +++ b/pkg/shell-operator/operator.go @@ -6,12 +6,11 @@ import ( "time" "github.com/deckhouse/deckhouse/pkg/log" - "github.com/deckhouse/module-sdk/pkg/hook/binding-context" + bindingcontext "github.com/deckhouse/module-sdk/pkg/hook/binding-context" "github.com/deckhouse/module-sdk/pkg/hook/types" - "github.com/deckhouse/module-sdk/pkg/kube-events-manager" + kubeeventsmanager "github.com/deckhouse/module-sdk/pkg/kube-events-manager" kemTypes "github.com/deckhouse/module-sdk/pkg/kube-events-manager/types" - "github.com/deckhouse/module-sdk/pkg/kube/object-patch" - "github.com/deckhouse/module-sdk/pkg/metric-storage" + metricstorage "github.com/deckhouse/module-sdk/pkg/metric-storage" "github.com/deckhouse/module-sdk/pkg/webhook/admission" "github.com/deckhouse/module-sdk/pkg/webhook/conversion" "github.com/gofrs/uuid/v5" @@ -21,6 +20,7 @@ import ( "github.com/flant/shell-operator/pkg/hook" "github.com/flant/shell-operator/pkg/hook/controller" "github.com/flant/shell-operator/pkg/hook/task_metadata" + objectpatch "github.com/flant/shell-operator/pkg/kube/object-patch" "github.com/flant/shell-operator/pkg/schedule_manager" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" diff --git a/test/integration/kubeclient/object_patch_test.go b/test/integration/kubeclient/object_patch_test.go index 3a5cf3c5..137a5965 100644 --- a/test/integration/kubeclient/object_patch_test.go +++ b/test/integration/kubeclient/object_patch_test.go @@ -7,6 +7,8 @@ import ( "context" "encoding/json" + objectpatch "github.com/flant/shell-operator/pkg/kube/object-patch" + . "github.com/flant/shell-operator/test/integration/suite" uuid "github.com/gofrs/uuid/v5" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -16,9 +18,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" - - "github.com/deckhouse/module-sdk/pkg/kube/object-patch" - . "github.com/flant/shell-operator/test/integration/suite" ) const ( diff --git a/test/integration/suite/run.go b/test/integration/suite/run.go index 46754060..f8b1ffc4 100644 --- a/test/integration/suite/run.go +++ b/test/integration/suite/run.go @@ -7,12 +7,11 @@ import ( "os" "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/deckhouse/deckhouse/pkg/log" - "github.com/deckhouse/module-sdk/pkg/kube/object-patch" klient "github.com/flant/kube-client/client" + objectpatch "github.com/flant/shell-operator/pkg/kube/object-patch" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) var ( From 92e19d2e60ead4bf7aefa233d8eb77ca501895ab Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Thu, 14 Nov 2024 13:20:58 +0300 Subject: [PATCH 4/4] return objectpath to sdk Signed-off-by: Pavel Okhlopkov --- go.mod | 4 +- go.sum | 4 +- pkg/kube/object-patch/helpers.go | 144 --- pkg/kube/object-patch/operation.go | 318 ------ pkg/kube/object-patch/options.go | 122 --- pkg/kube/object-patch/patch.go | 302 ------ pkg/kube/object-patch/patch_collector.go | 86 -- pkg/kube/object-patch/patch_test.go | 949 ------------------ .../serialized_operations/invalid_create.yaml | 8 - .../serialized_operations/invalid_delete.yaml | 12 - .../serialized_operations/invalid_patch.yaml | 15 - .../serialized_operations/valid_create.yaml | 16 - .../serialized_operations/valid_delete.yaml | 18 - .../serialized_operations/valid_patch.yaml | 27 - pkg/kube/object-patch/validation.go | 214 ---- pkg/kube/object-patch/validation_test.go | 28 - pkg/shell-operator/kube_client.go | 2 +- pkg/shell-operator/operator.go | 2 +- .../kubeclient/object_patch_test.go | 2 +- test/integration/suite/run.go | 2 +- 20 files changed, 8 insertions(+), 2267 deletions(-) delete mode 100644 pkg/kube/object-patch/helpers.go delete mode 100644 pkg/kube/object-patch/operation.go delete mode 100644 pkg/kube/object-patch/options.go delete mode 100644 pkg/kube/object-patch/patch.go delete mode 100644 pkg/kube/object-patch/patch_collector.go delete mode 100644 pkg/kube/object-patch/patch_test.go delete mode 100644 pkg/kube/object-patch/testdata/serialized_operations/invalid_create.yaml delete mode 100644 pkg/kube/object-patch/testdata/serialized_operations/invalid_delete.yaml delete mode 100644 pkg/kube/object-patch/testdata/serialized_operations/invalid_patch.yaml delete mode 100644 pkg/kube/object-patch/testdata/serialized_operations/valid_create.yaml delete mode 100644 pkg/kube/object-patch/testdata/serialized_operations/valid_delete.yaml delete mode 100644 pkg/kube/object-patch/testdata/serialized_operations/valid_patch.yaml delete mode 100644 pkg/kube/object-patch/validation.go delete mode 100644 pkg/kube/object-patch/validation_test.go diff --git a/go.mod b/go.mod index e0eda4aa..05dfef8e 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/kennygrant/sanitize v1.2.4 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.35.1 - github.com/pkg/errors v0.9.1 + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.20.5 github.com/stretchr/testify v1.9.0 golang.org/x/time v0.7.0 @@ -34,7 +34,7 @@ require ( replace github.com/go-openapi/validate => github.com/flant/go-openapi-validate v0.19.12-flant.0 require ( - github.com/deckhouse/module-sdk v0.0.0-20241114091625-9e129814d38d + github.com/deckhouse/module-sdk v0.0.0-20241114101830-23c1625a3180 github.com/gojuno/minimock/v3 v3.4.1 ) diff --git a/go.sum b/go.sum index 0bdc01b7..8522b304 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241106140903-258b93b3334e h1:QUQy+5Bv7/UzhfrytiG3c5gfLGhPppepVbRpbMisVIw= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241106140903-258b93b3334e/go.mod h1:Mk5HRzkc5pIcDIZ2JJ6DPuuqnwhXVkb3you8M8Mg+4w= -github.com/deckhouse/module-sdk v0.0.0-20241114091625-9e129814d38d h1:aHOxJAQcs2GgB7H5Lu6AfFiqrxBO+dyu7GxOn1Rg+OY= -github.com/deckhouse/module-sdk v0.0.0-20241114091625-9e129814d38d/go.mod h1:q9jIxjVSVhPRJLWC001c7ujYZIzoPKjiF4LV0Zm+XCE= +github.com/deckhouse/module-sdk v0.0.0-20241114101830-23c1625a3180 h1:uZQFTYdEc/4ypHv+aO4ep5m41pVVUnWj+ZBknsmjR+M= +github.com/deckhouse/module-sdk v0.0.0-20241114101830-23c1625a3180/go.mod h1:q9jIxjVSVhPRJLWC001c7ujYZIzoPKjiF4LV0Zm+XCE= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= diff --git a/pkg/kube/object-patch/helpers.go b/pkg/kube/object-patch/helpers.go deleted file mode 100644 index 453cc260..00000000 --- a/pkg/kube/object-patch/helpers.go +++ /dev/null @@ -1,144 +0,0 @@ -package objectpatch - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - - "github.com/deckhouse/module-sdk/pkg/app" - "github.com/deckhouse/module-sdk/pkg/jq" - "gopkg.in/yaml.v3" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - k8yaml "sigs.k8s.io/yaml" - - "github.com/flant/kube-client/manifest" -) - -func unmarshalFromJSONOrYAML(specs []byte) ([]OperationSpec, error) { - fromJsonSpecs, err := unmarshalFromJson(specs) - if err != nil { - return unmarshalFromYaml(specs) - } - - return fromJsonSpecs, nil -} - -func unmarshalFromJson(jsonSpecs []byte) ([]OperationSpec, error) { - var specSlice []OperationSpec - - dec := json.NewDecoder(bytes.NewReader(jsonSpecs)) - for { - var doc OperationSpec - err := dec.Decode(&doc) - if err == io.EOF { - break - } - if err != nil { - return nil, err - } - - specSlice = append(specSlice, doc) - } - - return specSlice, nil -} - -func unmarshalFromYaml(yamlSpecs []byte) ([]OperationSpec, error) { - var specSlice []OperationSpec - - dec := yaml.NewDecoder(bytes.NewReader(yamlSpecs)) - for { - var doc OperationSpec - err := dec.Decode(&doc) - if err == io.EOF { - break - } - if err != nil { - return nil, err - } - - specSlice = append(specSlice, doc) - } - - return specSlice, nil -} - -func applyJQPatch(jqFilter string, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - objBytes, err := obj.MarshalJSON() - if err != nil { - return nil, err - } - - filterResult, err := jq.ApplyJqFilter(jqFilter, objBytes, app.JqLibraryPath) - if err != nil { - return nil, fmt.Errorf("failed to apply jqFilter:\n%sto Object:\n%s\n"+ - "error: %s", jqFilter, obj, err) - } - - retObj := &unstructured.Unstructured{} - _, _, err = unstructured.UnstructuredJSONScheme.Decode([]byte(filterResult), nil, retObj) - if err != nil { - return nil, fmt.Errorf("failed to convert filterResult:\n%s\nto Unstructured Object\nerror: %s", filterResult, err) - } - - return retObj, nil -} - -func generateSubresources(subresource string) (ret []string) { - if subresource != "" { - ret = append(ret, subresource) - } - - return -} - -func toUnstructured(obj interface{}) (*unstructured.Unstructured, error) { - switch v := obj.(type) { - case []byte: - mft, err := manifest.NewFromYAML(string(v)) - if err != nil { - return nil, err - } - return mft.Unstructured(), nil - case string: - mft, err := manifest.NewFromYAML(v) - if err != nil { - return nil, err - } - return mft.Unstructured(), nil - case map[string]interface{}: - return &unstructured.Unstructured{Object: v}, nil - default: - objectContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) - if err != nil { - return nil, fmt.Errorf("convert to unstructured: %v", err) - } - return &unstructured.Unstructured{Object: objectContent}, nil - } -} - -func convertPatchToBytes(patch interface{}) ([]byte, error) { - var err error - var intermediate interface{} - switch v := patch.(type) { - case []byte: - err = k8yaml.Unmarshal(v, &intermediate) - case string: - err = k8yaml.Unmarshal([]byte(v), &intermediate) - default: - intermediate = v - } - if err != nil { - return nil, err - } - - // Try to encode to JSON. - var patchBytes []byte - patchBytes, err = json.Marshal(intermediate) - if err != nil { - return nil, err - } - return patchBytes, nil -} diff --git a/pkg/kube/object-patch/operation.go b/pkg/kube/object-patch/operation.go deleted file mode 100644 index 981464d6..00000000 --- a/pkg/kube/object-patch/operation.go +++ /dev/null @@ -1,318 +0,0 @@ -package objectpatch - -import ( - "fmt" - - "github.com/deckhouse/deckhouse/pkg/log" - objectpatch "github.com/deckhouse/module-sdk/pkg/go-hook/object-patch" - "github.com/hashicorp/go-multierror" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" -) - -// A JSON and YAML representation of the operation for shell hooks -type OperationSpec struct { - Operation OperationType `json:"operation" yaml:"operation"` - ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Subresource string `json:"subresource,omitempty" yaml:"subresource,omitempty"` - - Object interface{} `json:"object,omitempty" yaml:"object,omitempty"` - JQFilter string `json:"jqFilter,omitempty" yaml:"jqFilter,omitempty"` - MergePatch interface{} `json:"mergePatch,omitempty" yaml:"mergePatch,omitempty"` - JSONPatch interface{} `json:"jsonPatch,omitempty" yaml:"jsonPatch,omitempty"` - - IgnoreMissingObject bool `json:"ignoreMissingObject" yaml:"ignoreMissingObject"` - IgnoreHookError bool `json:"ignoreHookError" yaml:"ignoreHookError"` -} - -type OperationType string - -const ( - CreateOrUpdate OperationType = "CreateOrUpdate" - Create OperationType = "Create" - CreateIfNotExists OperationType = "CreateIfNotExists" - - Delete OperationType = "Delete" - DeleteInBackground OperationType = "DeleteInBackground" - DeleteNonCascading OperationType = "DeleteNonCascading" - - JQPatch OperationType = "JQPatch" - MergePatch OperationType = "MergePatch" - JSONPatch OperationType = "JSONPatch" -) - -// GetPatchStatusOperationsOnHookError returns list of Patch/Filter operations eligible for execution on Hook Error -func GetPatchStatusOperationsOnHookError(operations []objectpatch.Operation) []objectpatch.Operation { - patchStatusOperations := make([]objectpatch.Operation, 0) - for _, op := range operations { - switch operation := op.(type) { - case *filterOperation: - if operation.subresource == "/status" && operation.ignoreHookError { - patchStatusOperations = append(patchStatusOperations, operation) - } - case *patchOperation: - if operation.subresource == "/status" && operation.ignoreHookError { - patchStatusOperations = append(patchStatusOperations, operation) - } - } - } - - return patchStatusOperations -} - -func ParseOperations(specBytes []byte) ([]objectpatch.Operation, error) { - log.Debugf("parsing patcher operations:\n%s", specBytes) - - specs, err := unmarshalFromJSONOrYAML(specBytes) - if err != nil { - return nil, err - } - - validationErrors := &multierror.Error{} - ops := make([]objectpatch.Operation, 0) - for _, spec := range specs { - err = ValidateOperationSpec(spec, GetSchema("v0"), "") - if err != nil { - validationErrors = multierror.Append(validationErrors, err) - break - } - ops = append(ops, NewFromOperationSpec(spec)) - } - - return ops, validationErrors.ErrorOrNil() -} - -type createOperation struct { - object interface{} - subresource string - - ignoreIfExists bool - updateIfExists bool -} - -func (op *createOperation) Description() string { - return "Create object" -} - -func (op *createOperation) SetSubresource(subres string) { - op.subresource = subres -} - -func (op *createOperation) SetIgnoreIfExists(ignore bool) { - op.ignoreIfExists = ignore -} - -func (op *createOperation) SetUpdateIfExists(update bool) { - op.updateIfExists = update -} - -type deleteOperation struct { - // Object coordinates. - apiVersion string - kind string - namespace string - name string - subresource string - - // Delete options. - deletionPropagation metav1.DeletionPropagation -} - -func (op *deleteOperation) Description() string { - return fmt.Sprintf("Delete object %s/%s/%s/%s", op.apiVersion, op.kind, op.namespace, op.name) -} - -func (op *deleteOperation) SetSubresource(subres string) { - op.subresource = subres -} - -func (op *deleteOperation) SetDeletePropagation(prop metav1.DeletionPropagation) { - op.deletionPropagation = prop -} - -type patchOperation struct { - // Object coordinates for patch and delete. - apiVersion string - kind string - namespace string - name string - subresource string - - // Patch options. - patchType types.PatchType - patch interface{} - ignoreMissingObject bool - ignoreHookError bool -} - -func (op *patchOperation) Description() string { - return fmt.Sprintf("Patch object %s/%s/%s/%s using %s patch", op.apiVersion, op.kind, op.namespace, op.name, op.patchType) -} - -func (op *patchOperation) SetSubresource(subres string) { - op.subresource = subres -} - -func (op *patchOperation) SetIgnoreHookError(ignore bool) { - op.ignoreHookError = ignore -} - -func (op *patchOperation) SetIgnoreMissingObject(ignore bool) { - op.ignoreMissingObject = ignore -} - -type filterOperation struct { - // Object coordinates for patch and delete. - apiVersion string - kind string - namespace string - name string - subresource string - - // Patch options. - filterFunc func(*unstructured.Unstructured) (*unstructured.Unstructured, error) - ignoreMissingObject bool - ignoreHookError bool -} - -func (op *filterOperation) Description() string { - return fmt.Sprintf("Filter object %s/%s/%s/%s", op.apiVersion, op.kind, op.namespace, op.name) -} - -func (op *filterOperation) SetSubresource(subres string) { - op.subresource = subres -} - -func (op *filterOperation) SetIgnoreHookError(ignore bool) { - op.ignoreHookError = ignore -} - -func (op *filterOperation) SetIgnoreMissingObject(ignore bool) { - op.ignoreMissingObject = ignore -} - -func NewFromOperationSpec(spec OperationSpec) objectpatch.Operation { - switch spec.Operation { - case Create: - return NewCreateOperation(spec.Object, - WithSubresource(spec.Subresource)) - case CreateIfNotExists: - return NewCreateOperation(spec.Object, - WithSubresource(spec.Subresource), - IgnoreIfExists()) - case CreateOrUpdate: - return NewCreateOperation(spec.Object, - WithSubresource(spec.Subresource), - UpdateIfExists()) - case Delete: - return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource)) - case DeleteInBackground: - return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - InBackground()) - case DeleteNonCascading: - return NewDeleteOperation(spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - NonCascading()) - case JQPatch: - return NewFilterPatchOperation( - func(u *unstructured.Unstructured) (*unstructured.Unstructured, error) { - return applyJQPatch(spec.JQFilter, u) - }, - spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - WithIgnoreMissingObject(spec.IgnoreMissingObject), - WithIgnoreHookError(spec.IgnoreHookError), - ) - case MergePatch: - return NewMergePatchOperation(spec.MergePatch, - spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - WithIgnoreMissingObject(spec.IgnoreMissingObject), - WithIgnoreHookError(spec.IgnoreHookError), - ) - case JSONPatch: - return NewJSONPatchOperation(spec.JSONPatch, - spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, - WithSubresource(spec.Subresource), - WithIgnoreMissingObject(spec.IgnoreMissingObject), - WithIgnoreHookError(spec.IgnoreHookError), - ) - } - - // Should not be reached! - return nil -} - -func NewCreateOperation(obj interface{}, options ...objectpatch.CreateOption) objectpatch.Operation { - op := &createOperation{ - object: obj, - } - for _, option := range options { - option.ApplyToCreate(op) - } - return op -} - -func NewDeleteOperation(apiVersion, kind, namespace, name string, options ...objectpatch.DeleteOption) objectpatch.Operation { - op := &deleteOperation{ - apiVersion: apiVersion, - kind: kind, - namespace: namespace, - name: name, - deletionPropagation: metav1.DeletePropagationForeground, - } - for _, option := range options { - option.ApplyToDelete(op) - } - return op -} - -func NewMergePatchOperation(mergePatch interface{}, apiVersion, kind, namespace, name string, options ...objectpatch.PatchOption) objectpatch.Operation { - op := &patchOperation{ - apiVersion: apiVersion, - kind: kind, - namespace: namespace, - name: name, - patch: mergePatch, - patchType: types.MergePatchType, - } - for _, option := range options { - option.ApplyToPatch(op) - } - return op -} - -func NewJSONPatchOperation(jsonPatch interface{}, apiVersion, kind, namespace, name string, options ...objectpatch.PatchOption) objectpatch.Operation { - op := &patchOperation{ - apiVersion: apiVersion, - kind: kind, - namespace: namespace, - name: name, - patch: jsonPatch, - patchType: types.JSONPatchType, - } - for _, option := range options { - option.ApplyToPatch(op) - } - return op -} - -func NewFilterPatchOperation(filter func(*unstructured.Unstructured) (*unstructured.Unstructured, error), apiVersion, kind, namespace, name string, options ...objectpatch.FilterOption) objectpatch.Operation { - op := &filterOperation{ - apiVersion: apiVersion, - kind: kind, - namespace: namespace, - name: name, - filterFunc: filter, - } - for _, option := range options { - option.ApplyToFilter(op) - } - return op -} diff --git a/pkg/kube/object-patch/options.go b/pkg/kube/object-patch/options.go deleted file mode 100644 index eaff3056..00000000 --- a/pkg/kube/object-patch/options.go +++ /dev/null @@ -1,122 +0,0 @@ -package objectpatch - -import ( - objectpatch "github.com/deckhouse/module-sdk/pkg/go-hook/object-patch" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type subresourceHolder struct { - subresource string -} - -// WithSubresource options specifies a subresource to operate on. -func WithSubresource(s string) *subresourceHolder { - return &subresourceHolder{subresource: s} -} - -func (s *subresourceHolder) ApplyToCreate(operation objectpatch.CreateOperation) { - operation.SetSubresource(s.subresource) -} - -func (s *subresourceHolder) ApplyToDelete(operation objectpatch.DeleteOperation) { - operation.SetSubresource(s.subresource) -} - -func (s *subresourceHolder) ApplyToPatch(operation objectpatch.PatchOperation) { - operation.SetSubresource(s.subresource) -} - -func (s *subresourceHolder) ApplyToFilter(operation objectpatch.FilterOperation) { - operation.SetSubresource(s.subresource) -} - -type ignoreHookError struct { - ignoreError bool -} - -// IgnoreHookError allows Applying patches for a Status subresource even if the hook fails -func IgnoreHookError() *ignoreHookError { - return WithIgnoreHookError(true) -} - -func WithIgnoreHookError(ignoreError bool) *ignoreHookError { - return &ignoreHookError{ignoreError: ignoreError} -} - -type ignoreMissingObject struct { - ignore bool -} - -func (i *ignoreHookError) ApplyToPatch(operation objectpatch.PatchOperation) { - operation.SetIgnoreHookError(i.ignoreError) -} - -func (i *ignoreHookError) ApplyToFilter(operation objectpatch.FilterOperation) { - operation.SetIgnoreHookError(i.ignoreError) -} - -// IgnoreMissingObject do not return error if object exists for Patch and Filter operations. -func IgnoreMissingObject() *ignoreMissingObject { - return WithIgnoreMissingObject(true) -} - -func WithIgnoreMissingObject(ignore bool) *ignoreMissingObject { - return &ignoreMissingObject{ignore: ignore} -} - -func (i *ignoreMissingObject) ApplyToPatch(operation objectpatch.PatchOperation) { - operation.SetIgnoreMissingObject(i.ignore) -} - -func (i *ignoreMissingObject) ApplyToFilter(operation objectpatch.FilterOperation) { - operation.SetIgnoreMissingObject(i.ignore) -} - -type ignoreIfExists struct { - ignore bool -} - -// IgnoreIfExists is an option for Create to not return error if object is already exists. -func IgnoreIfExists() objectpatch.CreateOption { - return &ignoreIfExists{ignore: true} -} - -func (i *ignoreIfExists) ApplyToCreate(operation objectpatch.CreateOperation) { - operation.SetIgnoreIfExists(i.ignore) -} - -type updateIfExists struct { - update bool -} - -// UpdateIfExists is an option for Create to update object if it already exists. -func UpdateIfExists() objectpatch.CreateOption { - return &updateIfExists{update: true} -} - -func (u *updateIfExists) ApplyToCreate(operation objectpatch.CreateOperation) { - operation.SetUpdateIfExists(u.update) -} - -type deletePropogation struct { - propagation metav1.DeletionPropagation -} - -func (d *deletePropogation) ApplyToDelete(operation objectpatch.DeleteOperation) { - operation.SetDeletePropagation(d.propagation) -} - -// InForeground is a default propagation option for Delete -func InForeground() objectpatch.DeleteOption { - return &deletePropogation{propagation: metav1.DeletePropagationForeground} -} - -// InBackground is a propagation option for Delete -func InBackground() objectpatch.DeleteOption { - return &deletePropogation{propagation: metav1.DeletePropagationBackground} -} - -// NonCascading is a propagation option for Delete -func NonCascading() objectpatch.DeleteOption { - return &deletePropogation{propagation: metav1.DeletePropagationOrphan} -} diff --git a/pkg/kube/object-patch/patch.go b/pkg/kube/object-patch/patch.go deleted file mode 100644 index 2e929197..00000000 --- a/pkg/kube/object-patch/patch.go +++ /dev/null @@ -1,302 +0,0 @@ -package objectpatch - -import ( - "bytes" - "context" - "fmt" - "time" - - "github.com/deckhouse/deckhouse/pkg/log" - objectpatch "github.com/deckhouse/module-sdk/pkg/go-hook/object-patch" - "github.com/hashicorp/go-multierror" - gerror "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/util/retry" -) - -type ObjectPatcher struct { - kubeClient KubeClient - logger *log.Logger -} - -type KubeClient interface { - kubernetes.Interface - Dynamic() dynamic.Interface - GroupVersionResource(apiVersion string, kind string) (schema.GroupVersionResource, error) -} - -func NewObjectPatcher(kubeClient KubeClient, logger *log.Logger) *ObjectPatcher { - return &ObjectPatcher{ - kubeClient: kubeClient, - logger: logger.With("operator.component", "KubernetesObjectPatcher"), - } -} - -func (o *ObjectPatcher) ExecuteOperations(ops []objectpatch.Operation) error { - log.Debug("Starting execute operations process") - defer log.Debug("Finished execute operations process") - - applyErrors := &multierror.Error{} - for _, op := range ops { - log.Debugf("Applying operation: %s", op.Description()) - if err := o.ExecuteOperation(op); err != nil { - err = gerror.WithMessage(err, op.Description()) - applyErrors = multierror.Append(applyErrors, err) - } - } - - return applyErrors.ErrorOrNil() -} - -func (o *ObjectPatcher) ExecuteOperation(operation objectpatch.Operation) error { - if operation == nil { - return nil - } - - switch v := operation.(type) { - case *createOperation: - return o.executeCreateOperation(v) - case *deleteOperation: - return o.executeDeleteOperation(v) - case *patchOperation: - return o.executePatchOperation(v) - case *filterOperation: - return o.executeFilterOperation(v) - } - - return nil -} - -func (o *ObjectPatcher) executeCreateOperation(op *createOperation) error { - if op.object == nil { - return fmt.Errorf("cannot create empty object") - } - - // Convert object from interface{}. - object, err := toUnstructured(op.object) - if err != nil { - return err - } - - apiVersion := object.GetAPIVersion() - kind := object.GetKind() - - wrapErr := func(e error) error { - objectID := fmt.Sprintf("%s/%s/%s/%s", apiVersion, kind, object.GetNamespace(), object.GetName()) - return gerror.WithMessage(e, objectID) - } - - gvk, err := o.kubeClient.GroupVersionResource(apiVersion, kind) - if err != nil { - return wrapErr(err) - } - - log.Debug("Started Create API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(object.GetNamespace()). - Create(context.TODO(), object, metav1.CreateOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Create API call") - - objectExists := errors.IsAlreadyExists(err) - - if objectExists && op.ignoreIfExists { - log.Debug("resource already exists, exiting without error") - return nil - } - - if objectExists && op.updateIfExists { - log.Debug("Object already exists, attempting to Update it with optimistic lock") - - return retry.RetryOnConflict(retry.DefaultBackoff, func() error { - log.Debug("Started Get API call") - existingObj, err := o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(object.GetNamespace()). - Get(context.TODO(), object.GetName(), metav1.GetOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Get API call") - if err != nil { - return wrapErr(err) - } - - objCopy := object.DeepCopy() - objCopy.SetResourceVersion(existingObj.GetResourceVersion()) - - log.Debug("Started Update API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(objCopy.GetNamespace()). - Update(context.TODO(), objCopy, metav1.UpdateOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Update API call") - return wrapErr(err) - }) - } - - // Simply return result of a Create call if no ignore options are in play. - return wrapErr(err) -} - -// executePatchOperation applies a patch to the specified object using API call Patch. -// -// There 2 types of patches: -// - Merge — use Patch API call with MergePatchType. -// - JSON — use Patch API call with JSONPatchType. -// -// Other options: -// - WithSubresource — a subresource argument for Patch or Update API call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -func (o *ObjectPatcher) executePatchOperation(op *patchOperation) error { - if op.patchType == types.MergePatchType { - log.Debug("Started MergePatchObject") - defer log.Debug("Finished MergePatchObject") - } - if op.patchType == types.JSONPatchType { - log.Debug("Started JSONPatchObject") - defer log.Debug("Finished JSONPatchObject") - } - - patchBytes, err := convertPatchToBytes(op.patch) - if err != nil { - return fmt.Errorf("encode %s patch for %s/%s/%s/%s: %v", op.patchType, op.apiVersion, op.kind, op.namespace, op.name, err) - } - if patchBytes == nil { - return fmt.Errorf("%s patch is nil for %s/%s/%s/%s", op.patchType, op.apiVersion, op.kind, op.namespace, op.name) - } - - gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) - if err != nil { - return err - } - - log.Debug("Started Patch API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Patch(context.TODO(), op.name, op.patchType, patchBytes, metav1.PatchOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Patch API call") - - if op.ignoreMissingObject && errors.IsNotFound(err) { - return nil - } - return err -} - -// executeFilterOperation retrieves a specified object, modified it with -// filterFunc and calls update. - -// Other options: -// - WithSubresource — a subresource argument for Patch or Update API call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -func (o *ObjectPatcher) executeFilterOperation(op *filterOperation) error { - var err error - - if op.filterFunc == nil { - return fmt.Errorf("FilterFunc is nil") - } - - gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) - if err != nil { - return err - } - - err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { - log.Debug("Started Get API call") - obj, err := o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Get(context.TODO(), op.name, metav1.GetOptions{}) - log.Debug("Finished Get API call") - if op.ignoreMissingObject && errors.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - - log.Debug("Started filtering object") - filteredObj, err := op.filterFunc(obj) - log.Debug("Finished filtering object") - if err != nil { - return err - } - - if equality.Semantic.DeepEqual(obj, filteredObj) { - return nil - } - - var filteredObjBuf bytes.Buffer - err = unstructured.UnstructuredJSONScheme.Encode(filteredObj, &filteredObjBuf) - if err != nil { - return err - } - - log.Debug("Started Update API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Update(context.TODO(), filteredObj, metav1.UpdateOptions{}, generateSubresources(op.subresource)...) - log.Debug("Finished Update API call") - if err != nil { - return err - } - - return nil - }) - - return err -} - -func (o *ObjectPatcher) executeDeleteOperation(op *deleteOperation) error { - gvk, err := o.kubeClient.GroupVersionResource(op.apiVersion, op.kind) - if err != nil { - return err - } - - log.Debug("Started Delete API call") - err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Delete(context.TODO(), op.name, metav1.DeleteOptions{PropagationPolicy: &op.deletionPropagation}, op.subresource) - - log.Debug("Finished Delete API call") - if errors.IsNotFound(err) { - return nil - } - - if err != nil { - return err - } - - if op.deletionPropagation != metav1.DeletePropagationForeground { - return nil - } - - log.Debug("Waiting for object deletion") - - err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 20*time.Second, false, func(ctx context.Context) (done bool, err error) { - log.Debug("Started Get API call") - _, err = o.kubeClient.Dynamic(). - Resource(gvk). - Namespace(op.namespace). - Get(ctx, op.name, metav1.GetOptions{}) - - log.Debug("Finished Get API call") - if errors.IsNotFound(err) { - return true, nil - } - - return false, err - }) - - return err -} diff --git a/pkg/kube/object-patch/patch_collector.go b/pkg/kube/object-patch/patch_collector.go deleted file mode 100644 index ebb1ecae..00000000 --- a/pkg/kube/object-patch/patch_collector.go +++ /dev/null @@ -1,86 +0,0 @@ -package objectpatch - -import ( - objectpatch "github.com/deckhouse/module-sdk/pkg/go-hook/object-patch" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -type PatchCollector struct { - patchOperations []objectpatch.Operation -} - -// NewPatchCollector creates Operation collector to use within Go hooks. -func NewPatchCollector() *PatchCollector { - return &PatchCollector{ - patchOperations: make([]objectpatch.Operation, 0), - } -} - -// Create or update an object. -// -// Options: -// - WithSubresource - create a specified subresource -// - IgnoreIfExists - do not return error if the specified object exists -// - UpdateIfExists - call Update if the specified object exists -func (dop *PatchCollector) Create(object interface{}, options ...objectpatch.CreateOption) { - dop.add(NewCreateOperation(object, options...)) -} - -// Delete uses apiVersion, kind, namespace and name to delete object from cluster. -// -// Options: -// - WithSubresource - delete a specified subresource -// - InForeground - remove object when all dependants are removed (default) -// - InBackground - remove object immediately, dependants remove in background -// - NonCascading - remove object, dependants become orphan -// -// Missing object is ignored by default. -func (dop *PatchCollector) Delete(apiVersion, kind, namespace, name string, options ...objectpatch.DeleteOption) { - dop.add(NewDeleteOperation(apiVersion, kind, namespace, name, options...)) -} - -// MergePatch applies a merge patch to the specified object using API call Patch. -// -// Options: -// - WithSubresource — a subresource argument for Patch call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -func (dop *PatchCollector) MergePatch(mergePatch interface{}, apiVersion, kind, namespace, name string, options ...objectpatch.PatchOption) { - dop.add(NewMergePatchOperation(mergePatch, apiVersion, kind, namespace, name, options...)) -} - -// JSONPatch applies a json patch to the specified object using API call Patch. -// -// Options: -// - WithSubresource — a subresource argument for Patch call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -func (dop *PatchCollector) JSONPatch(jsonPatch interface{}, apiVersion, kind, namespace, name string, options ...objectpatch.PatchOption) { - dop.add(NewJSONPatchOperation(jsonPatch, apiVersion, kind, namespace, name, options...)) -} - -// Filter retrieves a specified object, modified it with -// filterFunc and calls update. -// -// Options: -// - WithSubresource — a subresource argument for Patch call. -// - IgnoreMissingObject — do not return error if the specified object is missing. -// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails -// -// Note: do not modify and return argument in filterFunc, -// use FromUnstructured to instantiate a concrete type or modify after DeepCopy. -func (dop *PatchCollector) Filter( - filterFunc func(*unstructured.Unstructured) (*unstructured.Unstructured, error), - apiVersion, kind, namespace, name string, options ...objectpatch.FilterOption, -) { - dop.add(NewFilterPatchOperation(filterFunc, apiVersion, kind, namespace, name, options...)) -} - -// Operations returns all collected operations -func (dop *PatchCollector) Operations() []objectpatch.Operation { - return dop.patchOperations -} - -func (dop *PatchCollector) add(operation objectpatch.Operation) { - dop.patchOperations = append(dop.patchOperations, operation) -} diff --git a/pkg/kube/object-patch/patch_test.go b/pkg/kube/object-patch/patch_test.go deleted file mode 100644 index ec0b5f43..00000000 --- a/pkg/kube/object-patch/patch_test.go +++ /dev/null @@ -1,949 +0,0 @@ -package objectpatch - -import ( - "context" - "fmt" - "os" - "testing" - - "github.com/deckhouse/deckhouse/pkg/log" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/flant/kube-client/fake" - "github.com/flant/kube-client/manifest" -) - -func mustReadFile(t *testing.T, filePath string) []byte { - t.Helper() - content, err := os.ReadFile(filePath) - require.NoError(t, err) - return content -} - -func Test_ParseOperations(t *testing.T) { - const ( - shouldNotBeError = false - shouldBeError = true - ) - - tests := []struct { - name string - testFilePath string - expectError bool - }{ - { - "valid create", - "testdata/serialized_operations/valid_create.yaml", - shouldNotBeError, - }, - { - "invalid create", - "testdata/serialized_operations/invalid_create.yaml", - shouldBeError, - }, - { - "valid delete", - "testdata/serialized_operations/valid_delete.yaml", - shouldNotBeError, - }, - { - "invalid delete", - "testdata/serialized_operations/invalid_delete.yaml", - shouldBeError, - }, - { - "valid patch", - "testdata/serialized_operations/valid_patch.yaml", - shouldNotBeError, - }, - { - "invalid patch", - "testdata/serialized_operations/invalid_patch.yaml", - shouldBeError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testSpecs := mustReadFile(t, tt.testFilePath) - - _, err := ParseOperations(testSpecs) - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func Test_PatchOperations(t *testing.T) { - const ( - namespace = "default" - name = "testcm" - missingName = "missing-object" - configMap = ` -apiVersion: v1 -kind: ConfigMap -metadata: - name: testcm -data: - foo: "bar" -` - newField = "baz" - newValue = "quux" - shouldNotAdd = false - shouldAdd = true - shouldNotBeError = false - shouldBeError = true - ) - - // Filter func to add a new field. - filter := func(u *unstructured.Unstructured) (*unstructured.Unstructured, error) { - res := u.DeepCopy() - data := res.Object["data"].(map[string]interface{}) - data[newField] = newValue - res.Object["data"] = data - return res, nil - } - - tests := []struct { - name string - fn func(patcher *ObjectPatcher) error - expectAdd bool - expectError bool - }{ - { - "merge patch", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewMergePatchOperation( - fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), - "v1", "ConfigMap", namespace, name, - )) - }, - shouldAdd, - shouldNotBeError, - }, - { - "merge patch a missing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewMergePatchOperation( - fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), - "v1", "ConfigMap", namespace, missingName, - )) - }, - shouldNotAdd, - shouldBeError, - }, - { - "merge patch with ignoreMissingObject", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewMergePatchOperation( - fmt.Sprintf(`{"data":{"%s":"%s"}}`, newField, newValue), - "v1", "ConfigMap", namespace, missingName, - IgnoreMissingObject(), - )) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "merge patch using map", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewMergePatchOperation( - map[string]interface{}{ - "data": map[string]interface{}{ - newField: newValue, - }, - }, - "v1", "ConfigMap", namespace, name, - )) - }, - shouldAdd, - shouldNotBeError, - }, - { - "merge patch via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -mergePatch: - data: - %s: "%s" -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "merge patch a missing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -mergePatch: - data: - %s: "%s" -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldBeError, - }, - { - "merge patch with ignoreMissingObject via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -ignoreMissingObject: true -mergePatch: - data: - %s: "%s" -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "merge patch via string in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -mergePatch: | - data: - %s: "%s" -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "merge patch via stringified JSON in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: MergePatch -kind: ConfigMap -namespace: %s -name: %s -mergePatch: | - {"data":{"%s":"%s"}} -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "json patch", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewJSONPatchOperation( - // [{ "op": "replace", "path": "/data/firstField", "value": "jsonPatched"}] - fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), - "v1", "ConfigMap", namespace, name, - )) - }, - shouldAdd, - shouldNotBeError, - }, - { - "json patch a missing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewJSONPatchOperation( - fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), - "v1", "ConfigMap", namespace, missingName, - )) - }, - shouldNotAdd, - shouldBeError, - }, - { - "json patch with ignoreMissingObject", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewJSONPatchOperation( - fmt.Sprintf(`[{ "op": "add", "path": "/data/%s", "value": "%s"}]`, newField, newValue), - "v1", "ConfigMap", namespace, missingName, - IgnoreMissingObject(), - )) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "json patch via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JSONPatch -kind: ConfigMap -namespace: %s -name: %s -jsonPatch: - - op: add - path: /data/%s - value: %s -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "json patch a missing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JSONPatch -kind: ConfigMap -namespace: %s -name: %s -jsonPatch: - - op: add - path: /data/%s - value: %s -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldBeError, - }, - { - "json patch with ignoreMissingObject via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JSONPatch -kind: ConfigMap -namespace: %s -name: %s -ignoreMissingObject: true -jsonPatch: - - op: add - path: /data/%s - value: %s -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "json patch via stringified JSON in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JSONPatch -kind: ConfigMap -namespace: %s -name: %s -jsonPatch: | - [{"op":"add", "path":"/data/%s", "value":"%s"}] -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "filter patch", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewFilterPatchOperation( - filter, - "v1", "ConfigMap", namespace, name, - )) - }, - shouldAdd, - shouldNotBeError, - }, - { - "filter patch missing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewFilterPatchOperation( - filter, - "v1", "ConfigMap", namespace, missingName, - )) - }, - shouldNotAdd, - shouldBeError, - }, - { - "filter patch with ignoreMissingObject", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewFilterPatchOperation( - filter, - "v1", "ConfigMap", namespace, missingName, - IgnoreMissingObject(), - )) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "JQ patch via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JQPatch -kind: ConfigMap -namespace: %s -name: %s -jqFilter: | - .data.%s = "%s" -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - { - "JQ patch missing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JQPatch -kind: ConfigMap -namespace: %s -name: %s -jqFilter: | - .data.%s = "%s" -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldBeError, - }, - { - "JQ patch with ignoreMissingObject via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: JQPatch -kind: ConfigMap -namespace: %s -name: %s -ignoreMissingObject: true -jqFilter: | - .data.%s = "%s" -`, namespace, missingName, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotAdd, - shouldNotBeError, - }, - { - "update existing object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(fmt.Sprintf(` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: %s - name: %s -data: - foo: "bar" - %s: "%s" -`, namespace, name, newField, newValue)).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj, UpdateIfExists())) - }, - shouldAdd, - shouldNotBeError, - }, - { - "update existing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: CreateOrUpdate -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: "bar" - %s: "%s" -`, namespace, name, newField, newValue))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldAdd, - shouldNotBeError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Prepare fake cluster: create a Namespace and a ConfigMap. - cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, configMap) - - // Apply MergePatch: add a new field in data section. - patcher := NewObjectPatcher(cluster.Client, log.NewNop()) - - err := tt.fn(patcher) - - // Check error expectation. - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - // Fetch updated object and check fields in data section. - cmObj := new(v1.ConfigMap) - fetchObject(t, cluster, namespace, configMap, cmObj) - - require.Equal(t, "bar", cmObj.Data["foo"]) - if tt.expectAdd { - require.Equal(t, newValue, cmObj.Data[newField]) - } else { - require.NotContains(t, cmObj.Data, newField) - } - }) - } -} - -func Test_CreateOperations(t *testing.T) { - const ( - namespace = "default" - existingName = "testcm" - existingConfigMap = ` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: default - name: testcm -data: - foo: "bar" -` - newName = "newtestcm" - newConfigMap = ` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: default - name: newtestcm -data: - foo: "bar" -` - shouldNotCreateNew = false - shouldCreateNew = true - shouldNotBeError = false - shouldBeError = true - ) - - tests := []struct { - name string - fn func(patcher *ObjectPatcher) error - expectNewExists bool - expectError bool - }{ - { - "create new object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(newConfigMap).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj)) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create new object ignore existing object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(newConfigMap).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj, IgnoreIfExists())) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create existing object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(existingConfigMap).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj)) - }, - shouldNotCreateNew, - shouldBeError, - }, - { - "create ignore existing object", - func(patcher *ObjectPatcher) error { - obj := manifest.MustFromYAML(existingConfigMap).Unstructured() - return patcher.ExecuteOperation(NewCreateOperation(obj, IgnoreIfExists())) - }, - shouldNotCreateNew, - shouldNotBeError, - }, - { - "create new object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Create -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create existing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Create -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, existingName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotCreateNew, - shouldBeError, - }, - { - "create ignore existing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: CreateIfNotExists -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, existingName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotCreateNew, - shouldNotBeError, - }, - { - "create or update new object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: CreateOrUpdate -object: - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create new object via string in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Create -object: | - apiVersion: v1 - kind: ConfigMap - metadata: - namespace: %s - name: %s - data: - foo: bar -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create new object via stringified JSON in YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Create -object: | - {"apiVersion":"v1", "kind":"ConfigMap", "metadata": { - "namespace":"%s", "name":"%s"}, - "data":{"foo":"bar"}} -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - { - "create new object via stringified JSON in JSON spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -{"operation": "Create", -"object": "{ - \"apiVersion\":\"v1\", \"kind\":\"ConfigMap\", - \"metadata\": {\"namespace\":\"%s\", \"name\":\"%s\"}, - \"data\":{\"foo\":\"bar\"}} -"} -`, namespace, newName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldCreateNew, - shouldNotBeError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Prepare fake cluster: create a Namespace and a ConfigMap. - cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, existingConfigMap) - - // Apply MergePatch: add a new field in data section. - patcher := NewObjectPatcher(cluster.Client, log.NewNop()) - - err := tt.fn(patcher) - - // Check error expectation. - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - // Check if new object is created. - exists := existObject(t, cluster, namespace, newConfigMap) - - if tt.expectNewExists { - require.True(t, exists) - } else { - require.False(t, exists) - } - }) - } -} - -func Test_DeleteOperations(t *testing.T) { - const ( - namespace = "default" - existingName = "testcm" - existingConfigMap = ` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: default - name: testcm -data: - foo: "bar" -` - missingName = "missing-object" - - shouldNotDelete = false - shouldDelete = true - shouldNotBeError = false - shouldBeError = true - ) - - tests := []struct { - name string - fn func(patcher *ObjectPatcher) error - expectDeleted bool - expectError bool - }{ - { - "delete existing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewDeleteOperation("", "ConfigMap", namespace, existingName)) - }, - shouldDelete, - shouldNotBeError, - }, - { - "delete missing object", - func(patcher *ObjectPatcher) error { - return patcher.ExecuteOperation(NewDeleteOperation("", "ConfigMap", namespace, missingName)) - }, - shouldNotDelete, - shouldNotBeError, - }, - { - "delete existing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Delete -kind: ConfigMap -namespace: %s -name: %s -`, namespace, existingName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldDelete, - shouldNotBeError, - }, - { - "delete missing object via YAML spec", - func(patcher *ObjectPatcher) error { - operations, err := ParseOperations([]byte(fmt.Sprintf(` -operation: Delete -kind: ConfigMap -namespace: %s -name: %s -`, namespace, missingName))) - if err != nil { - return err - } - return patcher.ExecuteOperations(operations) - }, - shouldNotDelete, - shouldNotBeError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Prepare fake cluster: create a Namespace and a ConfigMap. - cluster := newFakeClusterWithNamespaceAndObjects(t, namespace, existingConfigMap) - - // Apply MergePatch: add a new field in data section. - patcher := NewObjectPatcher(cluster.Client, log.NewNop()) - - err := tt.fn(patcher) - - // Check error expectation. - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - // Check if new object is created. - exists := existObject(t, cluster, namespace, existingConfigMap) - - if tt.expectDeleted { - require.False(t, exists) - } else { - require.True(t, exists) - } - }) - } -} - -func newFakeClusterWithNamespaceAndObjects(t *testing.T, ns string, objects ...string) *fake.Cluster { - t.Helper() - - // Prepare fake cluster: create a Namespace and a ConfigMap. - cluster := fake.NewFakeCluster(fake.ClusterVersionV119) - cluster.CreateNs(ns) - - for _, object := range objects { - mft := manifest.MustFromYAML(object) - err := cluster.Create(ns, mft) - require.NoError(t, err) - } - - return cluster -} - -func fetchObject(t *testing.T, cluster *fake.Cluster, ns string, objYAML string, object interface{}) { - t.Helper() - mft, err := manifest.NewFromYAML(objYAML) - require.NoError(t, err) - - gvk := cluster.MustFindGVR(mft.ApiVersion(), mft.Kind()) - obj, err := cluster.Client.Dynamic().Resource(*gvk).Namespace(ns).Get(context.TODO(), mft.Name(), metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, obj) - - err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), object) - require.NoError(t, err) -} - -func existObject(t *testing.T, cluster *fake.Cluster, ns string, objYAML string) bool { - mft := manifest.MustFromYAML(objYAML) - gvk := cluster.MustFindGVR(mft.ApiVersion(), mft.Kind()) - obj, err := cluster.Client.Dynamic().Resource(*gvk).Namespace(ns).Get(context.TODO(), mft.Name(), metav1.GetOptions{}) - if errors.IsNotFound(err) { - return false - } - require.NoError(t, err) - return obj != nil -} diff --git a/pkg/kube/object-patch/testdata/serialized_operations/invalid_create.yaml b/pkg/kube/object-patch/testdata/serialized_operations/invalid_create.yaml deleted file mode 100644 index 319e2f4e..00000000 --- a/pkg/kube/object-patch/testdata/serialized_operations/invalid_create.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -operation: Create -namespace: default -object: ---- -operation: CreateOrUpdate -namespace: default -object: \ No newline at end of file diff --git a/pkg/kube/object-patch/testdata/serialized_operations/invalid_delete.yaml b/pkg/kube/object-patch/testdata/serialized_operations/invalid_delete.yaml deleted file mode 100644 index 926f9333..00000000 --- a/pkg/kube/object-patch/testdata/serialized_operations/invalid_delete.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -operation: Delete -kind: ConfigMap -name: "test" ---- -operation: DeleteInBackground -apiversion: core/v1 -name: "test" ---- -operation: DeleteNonCascading -apiversion: core/v1 -kind: ConfigMap diff --git a/pkg/kube/object-patch/testdata/serialized_operations/invalid_patch.yaml b/pkg/kube/object-patch/testdata/serialized_operations/invalid_patch.yaml deleted file mode 100644 index f7b042c3..00000000 --- a/pkg/kube/object-patch/testdata/serialized_operations/invalid_patch.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -operation: JQPatch -apiversion: core/v1 -kind: ConfigMap -name: "test" ---- -operation: MergePatch -apiversion: core/v1 -kind: ConfigMap -name: "test" ---- -operation: jsonPatch -apiversion: core/v1 -kind: ConfigMap -name: "test" diff --git a/pkg/kube/object-patch/testdata/serialized_operations/valid_create.yaml b/pkg/kube/object-patch/testdata/serialized_operations/valid_create.yaml deleted file mode 100644 index 8ed6904b..00000000 --- a/pkg/kube/object-patch/testdata/serialized_operations/valid_create.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -operation: Create -namespace: default -object: - apiVersion: core/v1 - kind: ConfigMap - data: - test: test ---- -operation: CreateOrUpdate -namespace: default -object: - apiVersion: core/v1 - kind: ConfigMap - data: - test: test \ No newline at end of file diff --git a/pkg/kube/object-patch/testdata/serialized_operations/valid_delete.yaml b/pkg/kube/object-patch/testdata/serialized_operations/valid_delete.yaml deleted file mode 100644 index 73ae807c..00000000 --- a/pkg/kube/object-patch/testdata/serialized_operations/valid_delete.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -operation: Delete -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" ---- -operation: DeleteInBackground -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" ---- -operation: DeleteNonCascading -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" \ No newline at end of file diff --git a/pkg/kube/object-patch/testdata/serialized_operations/valid_patch.yaml b/pkg/kube/object-patch/testdata/serialized_operations/valid_patch.yaml deleted file mode 100644 index 3a45eb76..00000000 --- a/pkg/kube/object-patch/testdata/serialized_operations/valid_patch.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -operation: JQPatch -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" -jqFilter: '.data = {"test": test}' ---- -operation: MergePatch -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" -mergePatch: - data: - test: test ---- -operation: JSONPatch -apiVersion: core/v1 -kind: ConfigMap -name: "test" -namespace: "default" -jsonPatch: -- op: replace - path: /data - value: - test: test diff --git a/pkg/kube/object-patch/validation.go b/pkg/kube/object-patch/validation.go deleted file mode 100644 index a651b89f..00000000 --- a/pkg/kube/object-patch/validation.go +++ /dev/null @@ -1,214 +0,0 @@ -package objectpatch - -import ( - "encoding/json" - "fmt" - - "github.com/go-openapi/spec" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" - "github.com/hashicorp/go-multierror" - "sigs.k8s.io/yaml" -) - -var Schemas = map[string]string{ - "v0": ` -definitions: - common: - type: object - properties: - subresource: - type: string - create: - required: - - object - properties: - object: - oneOf: - - type: object - additionalProperties: true - minProperties: 1 - - type: string - delete: - type: object - required: - - kind - - name - properties: - apiVersion: - type: string - kind: - type: string - name: - type: string - patch: - type: object - required: - - kind - - name - properties: - apiVersion: - type: string - kind: - type: string - name: - type: string - ignoreMissingObject: - type: boolean - ignoreHookError: - type: boolean - -type: object -additionalProperties: false -properties: - operation: {} - namespace: {} - subresource: {} - apiVersion: {} - kind: {} - name: {} - object: {} - jsonPatch: {} - jqFilter: {} - mergePatch: {} - ignoreMissingObject: {} - ignoreHookError: {} - -oneOf: -- allOf: - - properties: - operation: - type: string - enum: ["Create", "CreateOrUpdate", "CreateIfNotExists"] - - "$ref": "#/definitions/common" - - "$ref": "#/definitions/create" -- allOf: - - properties: - operation: - type: string - enum: ["Delete", "DeleteInBackground", "DeleteNonCascading"] - - "$ref": "#/definitions/common" - - "$ref": "#/definitions/delete" -- allOf: - - oneOf: - - required: - - operation - - jqFilter - properties: - operation: - type: string - enum: ["JQPatch"] - jqFilter: - type: string - minimum: 1 - - required: - - operation - - mergePatch - properties: - operation: - type: string - enum: ["MergePatch"] - mergePatch: - oneOf: - - type: object - minProperties: 1 - - type: string - - required: - - operation - - jsonPatch - properties: - operation: - type: string - enum: ["JSONPatch"] - jsonPatch: - oneOf: - - type: array - minItems: 1 - items: - - type: object - required: ["op", "path", "value"] - properties: - op: - type: string - minLength: 1 - path: - type: string - minLength: 1 - value: {} - - type: string - - "$ref": "#/definitions/common" - - "$ref": "#/definitions/patch" -`, -} - -var SchemasCache = map[string]*spec.Schema{} - -// GetSchema returns loaded schema. -func GetSchema(name string) *spec.Schema { - if s, ok := SchemasCache[name]; ok { - return s - } - if _, ok := Schemas[name]; !ok { - return nil - } - - // ignore error because load is guaranteed by tests - SchemasCache[name], _ = LoadSchema(name) - return SchemasCache[name] -} - -// LoadSchema returns spec.Schema object loaded from yaml in Schemas map. -func LoadSchema(name string) (*spec.Schema, error) { - yml, err := swag.BytesToYAMLDoc([]byte(Schemas[name])) - if err != nil { - return nil, fmt.Errorf("yaml unmarshal: %v", err) - } - d, err := swag.YAMLToJSON(yml) - if err != nil { - return nil, fmt.Errorf("yaml to json: %v", err) - } - - s := new(spec.Schema) - - if err := json.Unmarshal(d, s); err != nil { - return nil, fmt.Errorf("json unmarshal: %v", err) - } - - err = spec.ExpandSchema(s, s, nil /*new(noopResCache)*/) - if err != nil { - return nil, fmt.Errorf("expand schema: %v", err) - } - - return s, nil -} - -// See https://github.com/kubernetes/apiextensions-apiserver/blob/1bb376f70aa2c6f2dec9a8c7f05384adbfac7fbb/pkg/apiserver/validation/validation.go#L47 -func ValidateOperationSpec(obj interface{}, s *spec.Schema, rootName string) (multiErr error) { - if s == nil { - return fmt.Errorf("validate kubernetes patch spec: schema is not provided") - } - - validator := validate.NewSchemaValidator(s, nil, rootName, strfmt.Default) - - result := validator.Validate(obj) - if result.IsValid() { - return nil - } - - allErrs := &multierror.Error{Errors: make([]error, 1)} - for _, err := range result.Errors { - allErrs = multierror.Append(allErrs, err) - } - // NOTE: no validation errors, but kubernetes patch spec is not valid! - if allErrs.Len() == 1 { - allErrs = multierror.Append(allErrs, fmt.Errorf("kubernetes patch spec is not valid")) - } - - if allErrs.Len() > 1 { - yamlObj, _ := yaml.Marshal(obj) - allErrs.Errors[0] = fmt.Errorf("can't validate document:\n%s", yamlObj) - } - - return allErrs -} diff --git a/pkg/kube/object-patch/validation_test.go b/pkg/kube/object-patch/validation_test.go deleted file mode 100644 index 5240744d..00000000 --- a/pkg/kube/object-patch/validation_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package objectpatch - -import ( - "testing" -) - -func Test_GetSchema(t *testing.T) { - schemas := []string{"v0"} - - for _, schema := range schemas { - s := GetSchema(schema) - if s == nil { - t.Fatalf("schema '%s' should not be nil", schema) - } - } -} - -func Test_LoadSchema_From_Schemas(t *testing.T) { - for schemaVer := range Schemas { - s, err := LoadSchema(schemaVer) - if err != nil { - t.Fatalf("schema '%s' should load: %v", schemaVer, err) - } - if s == nil { - t.Fatalf("schema '%s' should not be nil: %v", schemaVer, err) - } - } -} diff --git a/pkg/shell-operator/kube_client.go b/pkg/shell-operator/kube_client.go index 7aa74224..f1a9086e 100644 --- a/pkg/shell-operator/kube_client.go +++ b/pkg/shell-operator/kube_client.go @@ -6,9 +6,9 @@ import ( "github.com/deckhouse/deckhouse/pkg/log" metricstorage "github.com/deckhouse/module-sdk/pkg/metric-storage" + objectpatch "github.com/deckhouse/module-sdk/pkg/object-patch" klient "github.com/flant/kube-client/client" "github.com/flant/shell-operator/pkg/app" - objectpatch "github.com/flant/shell-operator/pkg/kube/object-patch" utils "github.com/flant/shell-operator/pkg/utils/labels" ) diff --git a/pkg/shell-operator/operator.go b/pkg/shell-operator/operator.go index af27fd5d..7d4cbe8d 100644 --- a/pkg/shell-operator/operator.go +++ b/pkg/shell-operator/operator.go @@ -16,11 +16,11 @@ import ( "github.com/gofrs/uuid/v5" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + objectpatch "github.com/deckhouse/module-sdk/pkg/object-patch" klient "github.com/flant/kube-client/client" "github.com/flant/shell-operator/pkg/hook" "github.com/flant/shell-operator/pkg/hook/controller" "github.com/flant/shell-operator/pkg/hook/task_metadata" - objectpatch "github.com/flant/shell-operator/pkg/kube/object-patch" "github.com/flant/shell-operator/pkg/schedule_manager" "github.com/flant/shell-operator/pkg/task" "github.com/flant/shell-operator/pkg/task/queue" diff --git a/test/integration/kubeclient/object_patch_test.go b/test/integration/kubeclient/object_patch_test.go index 137a5965..eb9a62b7 100644 --- a/test/integration/kubeclient/object_patch_test.go +++ b/test/integration/kubeclient/object_patch_test.go @@ -7,7 +7,7 @@ import ( "context" "encoding/json" - objectpatch "github.com/flant/shell-operator/pkg/kube/object-patch" + objectpatch "github.com/deckhouse/module-sdk/pkg/object-patch" . "github.com/flant/shell-operator/test/integration/suite" uuid "github.com/gofrs/uuid/v5" . "github.com/onsi/ginkgo" diff --git a/test/integration/suite/run.go b/test/integration/suite/run.go index f8b1ffc4..94773b42 100644 --- a/test/integration/suite/run.go +++ b/test/integration/suite/run.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" + objectpatch "github.com/deckhouse/module-sdk/pkg/object-patch" klient "github.com/flant/kube-client/client" - objectpatch "github.com/flant/shell-operator/pkg/kube/object-patch" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" )