diff --git a/crds/embedded/cdi.yaml b/crds/embedded/cdi.yaml
index 1f1de8316..af3ef6f6d 100644
--- a/crds/embedded/cdi.yaml
+++ b/crds/embedded/cdi.yaml
@@ -3,17 +3,19 @@ kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.13.0
- name: cdis.x.virtualization.deckhouse.io
+ name: dvpinternalcdis.internal.virtualization.deckhouse.io
spec:
- group: x.virtualization.deckhouse.io
+ group: internal.virtualization.deckhouse.io
names:
- kind: CDI
- listKind: CDIList
- plural: cdis
+ categories:
+ - dvpinternal
+ kind: DVPInternalCDI
+ listKind: DVPInternalCDIList
+ plural: dvpinternalcdis
shortNames:
- - xcdi
- - xcdis
- singular: cdi
+ - dvpcdi
+ - dvpcdis
+ singular: dvpinternalcdi
scope: Cluster
versions:
- additionalPrinterColumns:
diff --git a/crds/embedded/kubevirt.yaml b/crds/embedded/kubevirt.yaml
index ec52253f0..5ced54ae6 100644
--- a/crds/embedded/kubevirt.yaml
+++ b/crds/embedded/kubevirt.yaml
@@ -1,25 +1,28 @@
# Source:
# https://github.com/kubevirt/kubevirt/releases/download/v1.0.0/kubevirt-operator.yaml
# Changes:
+# - 2024.04.16
+# - Add prefixes xinternal, XInternal
# - 2023.12.12
# - Add x.virtualization.deckhouse prefix
# - Remove short names.
# - Remove all category, add kubevirt category.
+
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
labels:
operator.kubevirt.io: ""
- name: kubevirts.x.virtualization.deckhouse.io
+ name: dvpinternalkubevirts.internal.virtualization.deckhouse.io
spec:
- group: x.virtualization.deckhouse.io
+ group: internal.virtualization.deckhouse.io
names:
categories:
- - kubevirt
- kind: KubeVirt
- plural: kubevirts
- singular: kubevirt
+ - dvpinternal
+ kind: DVPInternalKubeVirt
+ plural: dvpinternalkubevirts
+ singular: dvpinternalkubevirt
scope: Namespaced
versions:
- additionalPrinterColumns:
diff --git a/images/cdi-apiserver/werf.inc.yaml b/images/cdi-apiserver/werf.inc.yaml
index bc3e7c863..f2ec11b5d 100644
--- a/images/cdi-apiserver/werf.inc.yaml
+++ b/images/cdi-apiserver/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: cdi-artifact
+- image: cdi-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/cdi-artifact/patches/005-override-crds.patch b/images/cdi-artifact/patches/005-override-crds.patch
deleted file mode 100644
index f8ba49877..000000000
--- a/images/cdi-artifact/patches/005-override-crds.patch
+++ /dev/null
@@ -1,372 +0,0 @@
-diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go
-index ef8b993f5..16b680fee 100644
---- a/pkg/apiserver/apiserver.go
-+++ b/pkg/apiserver/apiserver.go
-@@ -44,6 +44,7 @@ import (
-
- snapclient "github.com/kubernetes-csi/external-snapshotter/client/v6/clientset/versioned"
- cdiuploadv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/upload/v1beta1"
-+
- pkgcdiuploadv1 "kubevirt.io/containerized-data-importer/pkg/apis/upload/v1beta1"
- "kubevirt.io/containerized-data-importer/pkg/apiserver/webhooks"
- cdiclient "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned"
-diff --git a/pkg/operator/resources/cluster/apiserver.go b/pkg/operator/resources/cluster/apiserver.go
-index d6e14f339..bdcb1c593 100644
---- a/pkg/operator/resources/cluster/apiserver.go
-+++ b/pkg/operator/resources/cluster/apiserver.go
-@@ -30,6 +30,7 @@ import (
-
- cdicorev1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
- cdiuploadv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/upload/v1beta1"
-+
- "kubevirt.io/containerized-data-importer/pkg/operator/resources/utils"
- )
-
-@@ -118,7 +119,7 @@ func getAPIServerClusterPolicyRules() []rbacv1.PolicyRule {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "datavolumes",
-@@ -130,7 +131,7 @@ func getAPIServerClusterPolicyRules() []rbacv1.PolicyRule {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "datasources",
-@@ -141,7 +142,7 @@ func getAPIServerClusterPolicyRules() []rbacv1.PolicyRule {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "cdis",
-@@ -152,7 +153,7 @@ func getAPIServerClusterPolicyRules() []rbacv1.PolicyRule {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "cdis/finalizers",
-diff --git a/pkg/operator/resources/cluster/controller.go b/pkg/operator/resources/cluster/controller.go
-index d29b0dd16..2097a7768 100644
---- a/pkg/operator/resources/cluster/controller.go
-+++ b/pkg/operator/resources/cluster/controller.go
-@@ -154,7 +154,7 @@ func getControllerClusterPolicyRules() []rbacv1.PolicyRule {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "*",
-diff --git a/pkg/operator/resources/cluster/cronjob.go b/pkg/operator/resources/cluster/cronjob.go
-index c21285c4a..995bb9657 100644
---- a/pkg/operator/resources/cluster/cronjob.go
-+++ b/pkg/operator/resources/cluster/cronjob.go
-@@ -38,7 +38,7 @@ func getCronJobClusterPolicyRules() []rbacv1.PolicyRule {
- return []rbacv1.PolicyRule{
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "dataimportcrons",
-diff --git a/pkg/operator/resources/cluster/rbac.go b/pkg/operator/resources/cluster/rbac.go
-index 264b83891..f63aa0efe 100644
---- a/pkg/operator/resources/cluster/rbac.go
-+++ b/pkg/operator/resources/cluster/rbac.go
-@@ -38,7 +38,7 @@ func getAdminPolicyRules() []rbacv1.PolicyRule {
- return []rbacv1.PolicyRule{
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "datavolumes",
-@@ -54,7 +54,7 @@ func getAdminPolicyRules() []rbacv1.PolicyRule {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "datavolumes/source",
-@@ -87,7 +87,7 @@ func getViewPolicyRules() []rbacv1.PolicyRule {
- return []rbacv1.PolicyRule{
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "cdiconfigs",
-@@ -108,7 +108,7 @@ func getViewPolicyRules() []rbacv1.PolicyRule {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "datavolumes/source",
-@@ -124,7 +124,7 @@ func createConfigReaderClusterRole(name string) *rbacv1.ClusterRole {
- rules := []rbacv1.PolicyRule{
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "cdiconfigs",
-diff --git a/pkg/operator/resources/crds_generated.go b/pkg/operator/resources/crds_generated.go
-index dc4ba2ced..6148d5dc6 100644
---- a/pkg/operator/resources/crds_generated.go
-+++ b/pkg/operator/resources/crds_generated.go
-@@ -8,16 +8,16 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: cdis.cdi.kubevirt.io
-+ name: cdis.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- kind: CDI
- listKind: CDIList
- plural: cdis
- shortNames:
-- - cdi
-- - cdis
-+ - xcdi
-+ - xcdis
- singular: cdi
- scope: Cluster
- versions:
-@@ -4514,9 +4514,9 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: cdiconfigs.cdi.kubevirt.io
-+ name: cdiconfigs.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- kind: CDIConfig
- listKind: CDIConfigList
-@@ -4925,18 +4925,18 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: dataimportcrons.cdi.kubevirt.io
-+ name: dataimportcrons.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- categories:
-- - all
-+ - kubevirt
- kind: DataImportCron
- listKind: DataImportCronList
- plural: dataimportcrons
- shortNames:
-- - dic
-- - dics
-+ - xdic
-+ - xdics
- singular: dataimportcron
- scope: Namespaced
- versions:
-@@ -5816,17 +5816,17 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: datasources.cdi.kubevirt.io
-+ name: datasources.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- categories:
-- - all
-+ - kubevirt
- kind: DataSource
- listKind: DataSourceList
- plural: datasources
- shortNames:
-- - das
-+ - xdas
- singular: datasource
- scope: Namespaced
- versions:
-@@ -5967,18 +5967,18 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: datavolumes.cdi.kubevirt.io
-+ name: datavolumes.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- categories:
-- - all
-+ - kubevirt
- kind: DataVolume
- listKind: DataVolumeList
- plural: datavolumes
- shortNames:
-- - dv
-- - dvs
-+ - xdv
-+ - xdvs
- singular: datavolume
- scope: Namespaced
- versions:
-@@ -6716,16 +6716,16 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: objecttransfers.cdi.kubevirt.io
-+ name: objecttransfers.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- kind: ObjectTransfer
- listKind: ObjectTransferList
- plural: objecttransfers
- shortNames:
-- - ot
-- - ots
-+ - xot
-+ - xots
- singular: objecttransfer
- scope: Cluster
- versions:
-@@ -6848,9 +6848,9 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: storageprofiles.cdi.kubevirt.io
-+ name: storageprofiles.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- kind: StorageProfile
- listKind: StorageProfileList
-@@ -6975,9 +6975,9 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: volumeclonesources.cdi.kubevirt.io
-+ name: volumeclonesources.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- kind: VolumeCloneSource
- listKind: VolumeCloneSourceList
-@@ -7055,9 +7055,9 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: volumeimportsources.cdi.kubevirt.io
-+ name: volumeimportsources.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- kind: VolumeImportSource
- listKind: VolumeImportSourceList
-@@ -7296,9 +7296,9 @@ metadata:
- annotations:
- controller-gen.kubebuilder.io/version: v0.13.0
- creationTimestamp: null
-- name: volumeuploadsources.cdi.kubevirt.io
-+ name: volumeuploadsources.x.virtualization.deckhouse.io
- spec:
-- group: cdi.kubevirt.io
-+ group: x.virtualization.deckhouse.io
- names:
- kind: VolumeUploadSource
- listKind: VolumeUploadSourceList
-diff --git a/pkg/operator/resources/operator/operator.go b/pkg/operator/resources/operator/operator.go
-index 1ad35841f..bd6a40d77 100644
---- a/pkg/operator/resources/operator/operator.go
-+++ b/pkg/operator/resources/operator/operator.go
-@@ -97,7 +97,7 @@ func getClusterPolicyRules() []rbacv1.PolicyRule {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- "upload.cdi.kubevirt.io",
- },
- Resources: []string{
-@@ -523,7 +523,7 @@ _The CDI Operator does not support updates yet._
- "alm-examples": `
- [
- {
-- "apiVersion":"cdi.kubevirt.io/v1beta1",
-+ "apiVersion":"x.virtualization.deckhouse.io/v1beta1",
- "kind":"CDI",
- "metadata": {
- "name":"cdi",
-diff --git a/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1alpha1/register.go b/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1alpha1/register.go
-index 2b59307be..dc8a31bca 100644
---- a/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1alpha1/register.go
-+++ b/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1alpha1/register.go
-@@ -4,14 +4,12 @@ import (
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/runtime/schema"
--
-- "kubevirt.io/containerized-data-importer-api/pkg/apis/core"
- )
-
- // SchemeGroupVersion is group version used to register these objects
--var SchemeGroupVersion = schema.GroupVersion{Group: core.GroupName, Version: "v1alpha1"}
-+var SchemeGroupVersion = schema.GroupVersion{Group: "x.virtualization.deckhouse.io", Version: "v1alpha1"}
-
--//CDIGroupVersionKind group version kind
-+// CDIGroupVersionKind group version kind
- var CDIGroupVersionKind = schema.GroupVersionKind{Group: SchemeGroupVersion.Group, Version: SchemeGroupVersion.Version, Kind: "CDI"}
-
- // Kind takes an unqualified kind and returns back a Group qualified GroupKind
-diff --git a/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/register.go b/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/register.go
-index 8aa80f3c5..f9806b5b5 100644
---- a/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/register.go
-+++ b/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/register.go
-@@ -4,12 +4,10 @@ import (
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/runtime/schema"
--
-- "kubevirt.io/containerized-data-importer-api/pkg/apis/core"
- )
-
- // SchemeGroupVersion is group version used to register these objects
--var SchemeGroupVersion = schema.GroupVersion{Group: core.GroupName, Version: "v1beta1"}
-+var SchemeGroupVersion = schema.GroupVersion{Group: "x.virtualization.deckhouse.io", Version: "v1beta1"}
-
- // CDIGroupVersionKind group version kind
- var CDIGroupVersionKind = schema.GroupVersionKind{Group: SchemeGroupVersion.Group, Version: SchemeGroupVersion.Version, Kind: "CDI"}
diff --git a/images/cdi-artifact/patches/007-content-type-json.patch b/images/cdi-artifact/patches/007-content-type-json.patch
new file mode 100644
index 000000000..591e78f5f
--- /dev/null
+++ b/images/cdi-artifact/patches/007-content-type-json.patch
@@ -0,0 +1,100 @@
+diff --git a/cmd/cdi-apiserver/apiserver.go b/cmd/cdi-apiserver/apiserver.go
+index 156b6fc..048e538 100644
+--- a/cmd/cdi-apiserver/apiserver.go
++++ b/cmd/cdi-apiserver/apiserver.go
+@@ -25,9 +25,9 @@ import (
+ "os"
+
+ "github.com/kelseyhightower/envconfig"
+-
+ snapclient "github.com/kubernetes-csi/external-snapshotter/client/v6/clientset/versioned"
+ "github.com/pkg/errors"
++ apiruntime "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/tools/clientcmd"
+ "k8s.io/klog/v2"
+@@ -107,6 +107,7 @@ func main() {
+ if err != nil {
+ klog.Fatalf("Unable to get kube config: %v\n", errors.WithStack(err))
+ }
++ cfg.ContentType = apiruntime.ContentTypeJSON
+
+ client, err := kubernetes.NewForConfig(cfg)
+ if err != nil {
+diff --git a/cmd/cdi-controller/controller.go b/cmd/cdi-controller/controller.go
+index dbf6295..4c665eb 100644
+--- a/cmd/cdi-controller/controller.go
++++ b/cmd/cdi-controller/controller.go
+@@ -154,7 +154,7 @@ func start() {
+ if err != nil {
+ klog.Fatalf("Unable to get kube config: %v\n", errors.WithStack(err))
+ }
+-
++ cfg.ContentType = apiruntime.ContentTypeJSON
+ client, err := kubernetes.NewForConfig(cfg)
+ if err != nil {
+ klog.Fatalf("Unable to get kube client: %v\n", errors.WithStack(err))
+@@ -178,8 +178,9 @@ func start() {
+ NewCache: getNewManagerCache(namespace),
+ Scheme: scheme,
+ }
+-
+- mgr, err := manager.New(config.GetConfigOrDie(), opts)
++ cfg = config.GetConfigOrDie()
++ cfg.ContentType = apiruntime.ContentTypeJSON
++ mgr, err := manager.New(cfg, opts)
+ if err != nil {
+ klog.Errorf("Unable to setup controller manager: %v", err)
+ os.Exit(1)
+diff --git a/cmd/cdi-operator/operator.go b/cmd/cdi-operator/operator.go
+index 0a9b30d..211f8cf 100644
+--- a/cmd/cdi-operator/operator.go
++++ b/cmd/cdi-operator/operator.go
+@@ -29,6 +29,7 @@ import (
+ promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
+ "go.uber.org/zap/zapcore"
+ extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
++ apiruntime "k8s.io/apimachinery/pkg/runtime"
+ apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
+ "sigs.k8s.io/controller-runtime/pkg/client/config"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+@@ -82,6 +83,7 @@ func main() {
+ log.Error(err, "")
+ os.Exit(1)
+ }
++ cfg.ContentType = apiruntime.ContentTypeJSON
+
+ managerOpts := manager.Options{
+ Namespace: namespace,
+diff --git a/cmd/cdi-uploadproxy/uploadproxy.go b/cmd/cdi-uploadproxy/uploadproxy.go
+index fc55ae1..2d4fe58 100644
+--- a/cmd/cdi-uploadproxy/uploadproxy.go
++++ b/cmd/cdi-uploadproxy/uploadproxy.go
+@@ -7,17 +7,17 @@ import (
+
+ "github.com/kelseyhightower/envconfig"
+ "github.com/pkg/errors"
++ apiruntime "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/tools/clientcmd"
+ "k8s.io/klog/v2"
+- "sigs.k8s.io/controller-runtime/pkg/manager/signals"
+-
+ cdiclient "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned"
+ "kubevirt.io/containerized-data-importer/pkg/uploadproxy"
+ "kubevirt.io/containerized-data-importer/pkg/util"
+ certfetcher "kubevirt.io/containerized-data-importer/pkg/util/cert/fetcher"
+ certwatcher "kubevirt.io/containerized-data-importer/pkg/util/cert/watcher"
+ cryptowatch "kubevirt.io/containerized-data-importer/pkg/util/tls-crypto-watch"
++ "sigs.k8s.io/controller-runtime/pkg/manager/signals"
+ )
+
+ const (
+@@ -79,6 +79,7 @@ func main() {
+ if err != nil {
+ klog.Fatalf("Unable to get kube config: %v\n", errors.WithStack(err))
+ }
++ cfg.ContentType = apiruntime.ContentTypeJSON
+ client, err := kubernetes.NewForConfig(cfg)
+ if err != nil {
+ klog.Fatalf("Unable to get kube client: %v\n", errors.WithStack(err))
diff --git a/images/cdi-artifact/patches/README.md b/images/cdi-artifact/patches/README.md
index 1224d9439..808661a9f 100644
--- a/images/cdi-artifact/patches/README.md
+++ b/images/cdi-artifact/patches/README.md
@@ -14,4 +14,7 @@ Also, remove short names and change categories. Just in case.
Add `spec.customizeComponents` to the crd cdi to customize resources.
-https://github.com/kubevirt/containerized-data-importer/pull/3070
\ No newline at end of file
+https://github.com/kubevirt/containerized-data-importer/pull/3070
+
+#### `007-content-type-json.patch`
+set ContentTypeJson for kubernetes clients.
\ No newline at end of file
diff --git a/images/cdi-artifact/werf.inc.yaml b/images/cdi-artifact/werf.inc.yaml
index 82226096c..8d53932ac 100644
--- a/images/cdi-artifact/werf.inc.yaml
+++ b/images/cdi-artifact/werf.inc.yaml
@@ -3,7 +3,7 @@
{{- $builderImage := "quay.io/kubevirt/kubevirt-cdi-bazel-builder:2310202104-20cced838" }}
{{- $version := "1.58.0" }}
-artifact: {{ $.ImageName }}
+image: {{ $.ImageName }}
from: {{ $builderImage }}
git:
- add: /images/{{ $.ImageName }}
@@ -18,7 +18,11 @@ shell:
setup:
- git clone --depth 1 --branch v{{ $version }} https://github.com/kubevirt/containerized-data-importer.git /containerized-data-importer
- cd /containerized-data-importer
- - git apply /patches/*.patch
+ - |
+ for p in /patches/*.patch ; do
+ echo -n "Apply ${p} ... "
+ git apply ${p} || echo FAIL && echo OK
+ done
- /entrypoint.sh make bazel-build-images DOCKER=0
- /entrypoint.sh bazel build --config=x86_64 --define container_prefix=kubevirt --define image_prefix= --define container_tag=latest //:container-images-bundle.tar
- tar -C / --one-top-level -xf /containerized-data-importer/bazel-bin/container-images-bundle.tar
diff --git a/images/cdi-cloner/werf.inc.yaml b/images/cdi-cloner/werf.inc.yaml
index 1fdba5d75..1ab1ebc39 100644
--- a/images/cdi-cloner/werf.inc.yaml
+++ b/images/cdi-cloner/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: cdi-artifact
+- image: cdi-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/cdi-controller/werf.inc.yaml b/images/cdi-controller/werf.inc.yaml
index aa04c5dac..431c271e0 100644
--- a/images/cdi-controller/werf.inc.yaml
+++ b/images/cdi-controller/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: cdi-artifact
+- image: cdi-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/cdi-importer/werf.inc.yaml b/images/cdi-importer/werf.inc.yaml
index 411d6712c..f4cec75e2 100644
--- a/images/cdi-importer/werf.inc.yaml
+++ b/images/cdi-importer/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: cdi-artifact
+- image: cdi-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/cdi-operator/werf.inc.yaml b/images/cdi-operator/werf.inc.yaml
index 4741798d8..3cd4e6447 100644
--- a/images/cdi-operator/werf.inc.yaml
+++ b/images/cdi-operator/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: cdi-artifact
+- image: cdi-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/cdi-uploadproxy/werf.inc.yaml b/images/cdi-uploadproxy/werf.inc.yaml
index b47426583..82dc1887f 100644
--- a/images/cdi-uploadproxy/werf.inc.yaml
+++ b/images/cdi-uploadproxy/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: cdi-artifact
+- image: cdi-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/cdi-uploadserver/werf.inc.yaml b/images/cdi-uploadserver/werf.inc.yaml
index 61b518826..e99d4895b 100644
--- a/images/cdi-uploadserver/werf.inc.yaml
+++ b/images/cdi-uploadserver/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: cdi-artifact
+- image: cdi-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/kube-api-proxy/.dockerignore b/images/kube-api-proxy/.dockerignore
new file mode 100644
index 000000000..e5a9ac0a4
--- /dev/null
+++ b/images/kube-api-proxy/.dockerignore
@@ -0,0 +1,9 @@
+.git
+*.log
+*.swp
+
+templates
+Chart.yaml
+
+golangci-lint
+proxy
diff --git a/images/kube-api-proxy/STRUCTURE.md b/images/kube-api-proxy/STRUCTURE.md
new file mode 100644
index 000000000..51746e639
--- /dev/null
+++ b/images/kube-api-proxy/STRUCTURE.md
@@ -0,0 +1,451 @@
+# kube-api-proxy structure
+
+The idea of the proxy is simple: make controller connect to the local
+proxy in the sidecar, so proxy will pass requests to real Kubernetes API Server.
+Proxy may rewrite JSON payloads for different purposes, e.g. resources renaming.
+
+Proxy contains 2 proxy handlers:
+- "client" proxy to handle usual API requests from the proxied controller to the Kubernetes API Server.
+- "webhook" proxy to handle webhook requests from the Kubernetes API Server to the proxied controller.
+
+
+Example setup: rename resources for Kubevirt.
+```mermaid
+%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart TB
+ NoProxy-.->WithProxy
+
+ subgraph NoProxy ["`**Original Kubevirt setup**`"]
+ direction TB
+
+ subgraph np-virt-operator-deploy ["`Deploy/virt-operator`"]
+ np-virt-operator("`container
+ name: virt-operator`")
+ end
+
+ subgraph np-virt-controller-deploy ["`Deploy/virt-controller`"]
+ np-virt-controller("`container
+ name: virt-controller`")
+ end
+
+ np-kube-api["`Kubernetes API Server
+ with resources in apiGroup
+ *.kubevirt.io*`"]
+
+ np-virt-operator <-- "Original resources
+ in API calls" --> np-kube-api
+ np-virt-controller <-- "Original resources
+ in API calls" --> np-kube-api
+ end
+ subgraph WithProxy ["`**Kubevirt with proxy**`"]
+ direction TB
+
+ subgraph p-virt-operator-deploy ["`Deploy/virt-operator`"]
+ p-virt-operator("`container
+ name: virt-operator`")
+ p-virt-operator-proxy{{"container
+ name: proxy"}}
+ p-virt-operator -- "Original resources
+ in API calls" --> p-virt-operator-proxy
+ p-virt-operator-proxy -- "Restored resources
+ in API responses" --> p-virt-operator
+ end
+
+ subgraph p-virt-controller-deploy ["`Deploy/virt-controller`"]
+ p-virt-controller("`container
+ name: virt-controller`")
+ p-virt-controller-proxy{{"container
+ name: proxy"}}
+ p-virt-controller -- "Original resources
+in API calls" --> p-virt-controller-proxy
+ p-virt-controller-proxy -- "Restored resources
+ in API responses" --> p-virt-controller
+ end
+
+ p-kube-api["`Kubernetes API Server
+ with resources in apiGroup
+ *.x.virtualization.deckhouse.io*`"]
+
+ p-virt-operator-proxy <-- "Renamed resources in
+ API calls" --> p-kube-api
+ p-virt-controller-proxy <-- "Renamed resources in
+ API calls" --> p-kube-api
+ end
+```
+
+All DVP components:
+```mermaid
+%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart
+ subgraph kubevirt ["Kubevirt"]
+ subgraph virt-operator-deploy ["`Deploy/virt-operator`"]
+ virt-operator("`container:
+ virt-operator`")
+ virt-operator-proxy{{"container:
+ proxy"}}
+ virt-operator --> virt-operator-proxy
+ virt-operator-proxy --> virt-operator
+ end
+
+ subgraph p-virt-controller-deploy ["`Deploy/virt-controller`"]
+ virt-controller("`container:
+ virt-controller`")
+ virt-controller-proxy{{"container:
+ proxy"}}
+ virt-controller --> virt-controller-proxy
+ virt-controller-proxy --> virt-controller
+ end
+ subgraph p-virt-api-deploy ["`Deploy/virt-api`"]
+ virt-api("`container:
+ virt-api`")
+ virt-api-proxy{{"container:
+ proxy"}}
+ virt-api --> virt-api-proxy
+ virt-api-proxy --> virt-api
+ end
+
+ subgraph p-virt-handler-deploy ["`DaemonSet/virt-handler`"]
+ virt-handler("`container:
+ virt-handler`")
+ virt-handler-proxy{{"container:
+ proxy"}}
+ virt-handler --> virt-handler-proxy
+ virt-handler-proxy --> virt-handler
+ end
+ end
+
+ subgraph kubeapi ["control-plane"]
+ kube-api["`Kubernetes API Server`"]
+ end
+
+ virt-operator-proxy <----> kube-api
+ virt-controller-proxy <----> kube-api
+ virt-api-proxy <----> kube-api
+ virt-handler-proxy <----> kube-api
+
+ subgraph cdi ["CDI"]
+ subgraph cdi-operator-deploy ["`Deploy/cdi-operator`"]
+ cdi-operator-proxy{{"container:
+ proxy"}}
+ cdi-operator("`container:
+ virt-handler`")
+ cdi-operator --> cdi-operator-proxy
+ cdi-operator-proxy --> cdi-operator
+ end
+
+ subgraph cdi-deployment-deploy ["`Deploy/cdi-deployment`"]
+ cdi-deployment-proxy{{"container:
+ proxy"}}
+ cdi-deployment("`container:
+ cdi-eployment`")
+ cdi-deployment --> cdi-deployment-proxy
+ cdi-deployment-proxy --> cdi-deployment
+ end
+
+ subgraph cdi-api-deploy ["`Deploy/cdi-api`"]
+ cdi-api-proxy{{"container:
+ proxy"}}
+ cdi-api("`container:
+ cdi-api`")
+ cdi-api --> cdi-api-proxy
+ cdi-api-proxy --> cdi-api
+ end
+
+ subgraph cdi-exportproxy-deploy ["`Deploy/cdi-exportproxy`"]
+ cdi-exportproxy-proxy{{"container:
+ proxy"}}
+ cdi-exportproxy("`container:
+ cdi-exportproxy`")
+ cdi-exportproxy --> cdi-exportproxy-proxy
+ cdi-exportproxy-proxy --> cdi-exportproxy
+ end
+ end
+ kube-api <----> cdi-operator-proxy
+ kube-api <----> cdi-deployment-proxy
+ kube-api <----> cdi-api-proxy
+ kube-api <----> cdi-exportproxy-proxy
+
+
+ subgraph d8virt ["D8 API"]
+ subgraph d8-virt-deploy ["Deploy/virtualization-controller"]
+ d8-virt-controller-proxy("`container:
+ proxy`")
+ d8-virt-controller("`container:
+ virtualization-controller`")
+ d8-virt-controller --> d8-virt-controller-proxy
+ d8-virt-controller-proxy --> d8-virt-controller
+ end
+ end
+
+ kube-api <----> d8-virt-controller-proxy
+```
+
+Variation (block diagram seems not so powerful as flowchart)
+```mermaid
+block-beta
+ columns 5
+
+ %% Main containers in kubevirt Pods
+ virtoperator["virt-operator"]
+ virtapi["virt-api"]
+ virtcontroller["virt-controller"]
+ virthandler["virt-handler"]
+ virtexportproxy["virt-exportproxy"]
+
+ %% Space for links.
+ space:5
+ %% Links between containers.
+ virtoperator --> virtoperatorproxy
+ %%virtoperatorproxy --> virtoperator
+ virtapi --> virtapiproxy
+ virtcontroller --> virtcontrollerproxy
+ virthandler --> virthandlerproxy
+ virtexportproxy --> virtexportproxyproxy
+
+ %% Proxies in kubevirt Pods.
+ virtoperatorproxy(["proxy"])
+ virtapiproxy(["proxy"])
+ virtcontrollerproxy(["proxy"])
+ virthandlerproxy(["proxy"])
+ virtexportproxyproxy(["proxy"])
+
+ space:5
+
+ space
+ kubeapiserver{{"Kubernetes API Server"}}:3
+ space
+
+ virtoperatorproxy --> kubeapiserver
+ %%kubeapiserver --> virtoperatorproxy
+ virtapiproxy --> kubeapiserver
+ virtcontrollerproxy --> kubeapiserver
+ virthandlerproxy --> kubeapiserver
+ virtexportproxyproxy --> kubeapiserver
+
+ space:5
+ cdioperatorproxy --> kubeapiserver
+ cdiapiproxy --> kubeapiserver
+ cdideploymentproxy --> kubeapiserver
+ cdiuploadproxyproxy --> kubeapiserver
+ virtualizationcontrollerproxy --> kubeapiserver
+
+ %% Proxies in CDI Pods.
+ cdioperatorproxy(["proxy"])
+ cdiapiproxy(["proxy"])
+ cdideploymentproxy(["proxy"])
+ cdiuploadproxyproxy(["proxy"])
+ virtualizationcontrollerproxy(["proxy"])
+
+ %% Links inside CDI Pods.
+ space:5
+ cdioperator --> cdioperatorproxy
+ cdiapi--> cdiapiproxy
+ cdideployment --> cdideploymentproxy
+ cdiuploadproxy --> cdiuploadproxyproxy
+ virtualizationcontroller --> virtualizationcontrollerproxy
+
+ cdioperator["cdi-operator"]
+ cdiapi["cdi-api"]
+ cdideployment["cdi-deployment"]
+ cdiuploadproxy["cdi-uploadproxy"]
+ virtualizationcontroller["virtualization-
+ controller"]
+```
+
+### Changes to add proxy to the Pod
+- Add a ConfigMap with a simple kubeconfig points to the local proxy.
+ ```
+ ...
+ clusters:
+ - cluster:
+ server: http://127.0.0.1:23915
+ ...
+ ```
+- Add a volume and a volumeMount to pass new kubeconfig as file to the main container.
+- Set KUBECONFIG variable in the main container. File should contain configuration to connect to proxy port.
+ - Note: kubevirt containers use --kubeconfig flag, cdi containers use KUBECONFIG env variable.
+- Add a new sidecar container with the proxy.
+ - Set WEBHOOK_ADDRESS if webhook proxying is required.
+ - Add volumeMount with a certificate and set WEBHOOK_CERT_FILE and WEBHOOK_KEY_FILE to use the certificate.
+ - Add port 24192 to the webhook Service to use the certificate without issuing new one with changed ServerName.
+
+## API client proxying
+
+Implemented rewrites:
+- apiGroup, kind, metadata.ownerReferences for Kubevirt and CDI Custom Resources.
+- metadata.ownerReferences for Pod
+- rules for Role, ClusterRole
+- webhooks[].rules for ValidatingWebhookConfiguration, MutatingWebhookConfiguration
+- metadata.name, spec.group, spec.names for CustomResourceDefinition.
+- patch /spec for CustomResourceDefinition.
+- fieldSelector=metadata.name=&watch=true for CRD.
+- request.resource, request.object, request.kind, etc. for AdmissionReview.
+
+TODO:
+- labels and annotations for Kubevirt and CDI CRs and all kubevirt related resources, Nodes and Pods.
+- patches in general.
+- SubjectAccessReview https://dev-k8sref-io.web.app/docs/authorization/subjectaccessreview-v1/
+
+```plantuml
+@startuml
+box "Pod with Controller" #fff
+participant "container\nname: controller" as ctrl
+note over ctrl
+Use KUBECONFIG file to connect
+to local proxy instead of
+directly using API server:
+""clusters:""
+""- cluster:""
+"" server: http://127.0.0.1:23915""
+endnote
+queue "additional container\nname: proxy" as proxy
+/ note over proxy
+Listen on ""127.0.0.1:23915""
+and pass requests to
+Kubernetes API Server
+endnote
+endbox
+box "Control Plane" #fff
+participant "Kubernetes\nAPI Server" as kube_api
+endbox
+
+== Get, List, Delete operations ==
+
+ctrl -> proxy : Request operation via endpoint:\n\n/apis/kubevirt.io/v1/virtualmachines
+proxy -> kube_api : Rewrite endpoint, pass request to:\n\n/apis/x.virtualization.deckhouse.io↩︎\n/v1/prefixedvirtualmachines
+
+kube_api -> proxy : Response with renamed resources:\n\napiVersion: x.virtualization.deckhouse.io/v1\nkind: PrefixedVirtualMachine
+proxy -> ctrl : Rewrite payload, pass\nresponse with restored resources:\n\napiVersion: kubevirt.io/v1\nkind: VirtualMachine
+
+== Create, Update, Patch operations ==
+
+ctrl -> proxy : Request operation via endpoint:\n\n/apis/kubevirt.io/v1/virtualmachines\n\nA payload contains original resources:\n\napiVersion: kubevirt.io/v1\nkind: VirtualMachine
+proxy -> kube_api : Rewrite endpoint and payload,\npass request with renamed resources:\n\n/apis/x.virtualization.deckhouse.io↩︎\n/v1/prefixedvirtualmachines\n\napiVersion: x.virtualization.deckhouse.io/v1\nkind: PrefixedVirtualMachine
+
+kube_api -> proxy : Response with renamed resources:\n\napiVersion: x.virtualization.deckhouse.io/v1\nkind: PrefixedVirtualMachine
+proxy -> ctrl : Rewrite payload, pass\nresponse with restored resources:\n\napiVersion: kubevirt.io/v1\nkind: VirtualMachine
+
+== Watch operation ==
+
+ctrl -> proxy : Request WATCH operation via endpoint:\n\n/apis/kubevirt.io↩︎\n/v1/virtualmachines?watch=true
+activate proxy
+proxy -> kube_api : Rewrite endpoint, pass request to:\n\n/apis/x.virtualization.deckhouse.io↩︎\n/v1/prefixedvirtualmachines?watch=true
+activate kube_api
+
+kube_api -> kube_api : Generate\nWATCH\nevents
+
+kube_api -> proxy : ADDED, MODIFIED or DELETED\nevent with renamed resource:\n\napiVersion: x.virtualization.deckhouse.io/v1\nkind: PrefixedVirtualMachine
+activate proxy
+proxy -> ctrl : Rewrite payload, pass\nevent with restored resource:\n\napiVersion: kubevirt.io/v1\nkind: VirtualMachine
+deactivate proxy
+
+kube_api -> proxy : BOOKMARK event with renamed resource:\n\napiVersion: x.virtualization.deckhouse.io/v1\nkind: PrefixedVirtualMachine
+activate proxy
+proxy -> ctrl : Rewrite payload, pass\nevent with restored resource:\n\napiVersion: kubevirt.io/v1\nkind: VirtualMachine
+deactivate proxy
+
+kube_api -> proxy : Stop WATCH operation
+deactivate kube_api
+proxy -> ctrl : Stop WATCH operation
+deactivate proxy
+
+@endplantuml
+```
+
+
+## Webhook proxying
+
+Kubernetes API Server connects to proxy, so proxy will pass AdmissionReview to real webhook. Proxy may rewrite JSON payloads
+for different purposes, e.g. resources renaming.
+
+Additional changes:
+
+- A targetPort in the webhook Service should point to proxy container.
+- A proxy container should mount secret with certificates.
+
+```plantuml
+@startuml
+box "Pod with Controller" #fff
+participant "container\nname: controller" as ctrl
+queue "additional container\nname: proxy" as proxy
+endbox
+box "Control Plane" #fff
+participant "Kubernetes\nAPI Server" as kube_api
+endbox
+
+note over ctrl
+Listen on ""0.0.0.0:9443""
+endnote
+/ note over proxy
+Listen on ""0.0.0.0:24192""
+and pass requests to
+the controller ""127.0.0.1:9443""
+endnote
+/ note over kube_api
+Pass AdmissionReview to Pod
+endnote
+
+== Webhook handling ==
+
+kube_api -> proxy : Request admission review via\nconfigured endpoint:\n\n/validate-x-virtualization-↩︎\ndeckhouse-io-prefixed-virtualmachines\n\nA payload contains renamed resource:\n\napiVersion: x.virtualization.deckhouse.io/v1\nkind: PrefixedVirtualMachine
+proxy -> ctrl : Rewrite admission review, pass\nrequest with restored resource:\n\napiVersion: kubevirt.io/v1\nkind: VirtualMachine
+
+... Validating webhook response ...
+ctrl -> proxy : AdmissionReview response
+proxy -> kube_api : No rewrite, pass as-is.
+
+... Mutating webhook response ...
+ctrl -> proxy : AdmissionReview response\nwith the patch
+proxy -> kube_api : Rewrite ownerRef patch if\nresponse.patchType == JSONPatch\nand patch operates on the ownerRef content
+
+
+@enduml
+```
+
+```mermaid
+---
+config:
+ htmlLabels: false
+---
+
+sequenceDiagram
+
+ box Pod with controller
+ participant ctrl as container
name: controller
+ participant proxy as container
name: proxy
+ end
+
+ Note over ctrl: Listen on 0.0.0.0:9443
+ Note over proxy: Listen on 0.0.0.0:24192
and pass requests to
127.0.0.1:9443
+
+ box Control plane
+ participant kubeapi as Kubernetes
API Server
+ end
+ note over kubeapi: Request webhook with AdmissionReview
+
+ kubeapi --> ctrl: Webhook handling
+
+ kubeapi ->>+ proxy: Send AdmissionReview with
renamed resources
apiVersion: x.virtualization.deckhouse.io
PrefixedVirtualMachine
+
+ proxy ->>+ ctrl: Proxy restores resource:
apiGroup, kind, ownerReferences
apiVersion: kubevirt.io
kind: VirtualMachine
+
+ ctrl ->>- proxy: AdmissionReview
with webhook response
+
+ alt Validating webhook response
+ proxy ->> kubeapi: No rewrite, pass as-is
+ else Mutating webhook response
+ proxy ->>- kubeapi: Rewrite patch if
ownerReferences is modified
+ end
+
+
+
+ %%participant Bob
+ %% ctrl->>John: "`This **is** _Markdown_`"
+ %%loop HealthCheck
+ %% John->>John: Fight against hypochondria
+ %%end
+ %%Note right of John: Rational thoughts
prevail!
+ %%John-->>ctrl: Great!
+ %%John->>Bob: How about you?
+ %%Bob-->>John: Jolly good!
+```
diff --git a/images/kube-api-proxy/Taskfile.dist.yaml b/images/kube-api-proxy/Taskfile.dist.yaml
new file mode 100644
index 000000000..eca31a942
--- /dev/null
+++ b/images/kube-api-proxy/Taskfile.dist.yaml
@@ -0,0 +1,82 @@
+version: "3"
+
+silent: true
+
+includes:
+ my:
+ taskfile: Taskfile.my.yaml
+ optional: true
+
+vars:
+ DevRegistry: "dev-registry.deckhouse.io/virt/dev/$USER"
+
+tasks:
+ dev:build:
+ desc: "build latest image with proxy and test-controller"
+ cmds:
+ - |
+ docker build . -t {{.DevRegistry}}/kube-api-proxy:latest -f local/Dockerfile
+ docker push {{.DevRegistry}}/kube-api-proxy:latest
+
+ dev:deploy:
+ desc: "apply manifest with proxy and test-controller"
+ cmds:
+ - |
+ if ! kubectl get no 2>&1 >/dev/null ; then
+ echo Restart cluster connection
+ exit 1
+ fi
+ - |
+ kubectl -n kproxy apply -f local/proxy.yaml
+
+ dev:restart:
+ desc: "restart deployment"
+ cmds:
+ - |
+ if ! kubectl get no 2>&1 >/dev/null ; then
+ echo Restart cluster connection
+ exit 1
+ fi
+ - |
+ kubectl -n kproxy scale deployment/kube-api-proxy --replicas=0
+ kubectl -n kproxy scale deployment/kube-api-proxy --replicas=1
+
+ dev:redeploy:
+ desc: "build, deploy, restart"
+ cmds:
+ - |
+ if ! kubectl get no 2>&1 >/dev/null ; then
+ echo Restart cluster connection
+ exit 1
+ fi
+ - task: dev:build
+ - task: dev:deploy
+ - task: dev:restart
+ - |
+ sleep 3
+ kubectl -n kproxy get all
+
+ dev:curl:
+ desc: "run curl in proxy deployment"
+ cmds:
+ - |
+ kubectl -n kproxy exec -t deploy/kube-api-proxy -- curl {{.CLI_ARGS}}
+
+ dev:kubectl:
+ desc: "run kubectl in proxy deployment"
+ cmds:
+ - |
+ kubectl -n kproxy exec -ti deploy/kube-api-proxy -- kubectl -s 127.0.0.1:23916 {{.CLI_ARGS}}
+ #kubectl -n d8-virtualization exec -ti deploy/virt-operator -- kubectl -s 127.0.0.1:23915 {{.CLI_ARGS}}
+
+ logs:proxy:
+ desc: "Show proxy logs"
+ cmds:
+ - |
+ kubectl -n kproxy logs deployments/kube-api-proxy -c proxy-only -f
+
+ logs:controller:
+ desc: "Show test-controller logs"
+ cmds:
+ - |
+ kubectl -n kproxy logs deployments/kube-api-proxy -c controller -f
diff --git a/images/kube-api-proxy/cmd/kube-api-proxy/main.go b/images/kube-api-proxy/cmd/kube-api-proxy/main.go
new file mode 100644
index 000000000..4f2b7fe91
--- /dev/null
+++ b/images/kube-api-proxy/cmd/kube-api-proxy/main.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+ log "log/slog"
+ "os"
+
+ "kube-api-proxy/pkg/kubevirt"
+ logutil "kube-api-proxy/pkg/log"
+ "kube-api-proxy/pkg/proxy"
+ "kube-api-proxy/pkg/rewriter"
+ "kube-api-proxy/pkg/server"
+ "kube-api-proxy/pkg/target"
+)
+
+// This proxy is a proof-of-concept of proxying Kubernetes API requests
+// with rewrites.
+//
+// It assumes presence of KUBERNETES_* environment variables and files
+// in /var/run/secrets/kubernetes.io/serviceaccount (token and ca.crt).
+//
+// A client behind the proxy should connect to 127.0.0.1:$PROXY_PORT
+// using plain http. Example of kubeconfig file:
+// apiVersion: v1
+// kind: Config
+// clusters:
+// - cluster:
+// server: http://127.0.0.1:23915
+// name: proxy.api.server
+// contexts:
+// - context:
+// cluster: proxy.api.server
+// name: proxy.api.server
+// current-context: proxy.api.server
+
+const (
+ loopbackAddr = "127.0.0.1"
+ anyAddr = "0.0.0.0"
+ defaultAPIClientProxyPort = "23915"
+ defaultWebhookProxyPort = "24192"
+)
+
+func main() {
+ log.Info("Start proxy 20240404.01")
+
+ // Load rules from file or use default kubevirt rules.
+ rewriteRules := kubevirt.KubevirtRewriteRules
+ if os.Getenv("RULES_PATH") != "" {
+ rulesFromFile, err := rewriter.LoadRules(os.Getenv("RULES_PATH"))
+ if err != nil {
+ log.Error("Load rules from %s: %v", os.Getenv("RULES_PATH"), err)
+ os.Exit(1)
+ }
+ rewriteRules = rulesFromFile
+ }
+
+ proxies := make([]*server.HTTPServer, 0)
+
+ // Register direct proxy from local Kubernetes API client to Kubernetes API server.
+ if os.Getenv("CLIENT_PROXY") == "no" {
+ log.Info("Will not start client proxy: CLIENT_PROXY=no")
+ } else {
+ config, err := target.NewKubernetesTarget()
+ if err != nil {
+ log.Error("Load Kubernetes REST", logutil.SlogErr(err))
+ os.Exit(1)
+ }
+ lAddr := server.ConstructListenAddr(
+ os.Getenv("CLIENT_PROXY_ADDRESS"), os.Getenv("CLIENT_PROXY_PORT"),
+ loopbackAddr, defaultAPIClientProxyPort)
+ rwr := &rewriter.RuleBasedRewriter{
+ Rules: rewriteRules,
+ }
+ proxyHandler := &proxy.Handler{
+ Name: "kube-api",
+ TargetClient: config.Client,
+ TargetURL: config.APIServerURL,
+ ProxyMode: proxy.ToRenamed,
+ Rewriter: rwr,
+ }
+ proxySrv := &server.HTTPServer{
+ InstanceDesc: "API Client proxy",
+ ListenAddr: lAddr,
+ RootHandler: proxyHandler,
+ }
+ proxies = append(proxies, proxySrv)
+ }
+
+ // Register reverse proxy from Kubernetes API server to local webhook server.
+ if os.Getenv("WEBHOOK_ADDRESS") == "" {
+ log.Info("Will not start webhook proxy for empty WEBHOOK_ADDRESS")
+ } else {
+ config, err := target.NewWebhookTarget()
+ if err != nil {
+ log.Error("Configure webhook client", logutil.SlogErr(err))
+ os.Exit(1)
+ }
+ lAddr := server.ConstructListenAddr(
+ os.Getenv("WEBHOOK_PROXY_ADDRESS"), os.Getenv("WEBHOOK_PROXY_PORT"),
+ anyAddr, defaultWebhookProxyPort)
+ rwr := &rewriter.RuleBasedRewriter{
+ Rules: rewriteRules,
+ }
+ proxyHandler := &proxy.Handler{
+ Name: "webhook",
+ TargetClient: config.Client,
+ TargetURL: config.URL,
+ ProxyMode: proxy.ToOriginal,
+ Rewriter: rwr,
+ }
+ proxySrv := &server.HTTPServer{
+ InstanceDesc: "Webhook proxy",
+ ListenAddr: lAddr,
+ RootHandler: proxyHandler,
+ CertFile: config.CertFile,
+ KeyFile: config.KeyFile,
+ }
+ proxies = append(proxies, proxySrv)
+ }
+
+ if len(proxies) == 0 {
+ log.Info("No proxies to start, exit")
+ return
+ }
+
+ // Start proxies and block the main process until at least one proxy stops.
+ proxyGroup := server.NewRunnableGroup()
+ for i := range proxies {
+ proxyGroup.Add(proxies[i])
+ }
+ // Block while proxies are running.
+ proxyGroup.Start()
+
+ // Log errors for each instance and exit.
+ exitCode := 0
+ for _, srv := range proxies {
+ if srv.Err != nil {
+ log.Error(srv.InstanceDesc, logutil.SlogErr(srv.Err))
+ exitCode = 1
+ }
+ }
+ os.Exit(exitCode)
+}
diff --git a/images/kube-api-proxy/go.mod b/images/kube-api-proxy/go.mod
new file mode 100644
index 000000000..bb2faa632
--- /dev/null
+++ b/images/kube-api-proxy/go.mod
@@ -0,0 +1,45 @@
+module kube-api-proxy
+
+go 1.21
+
+require (
+ github.com/stretchr/testify v1.8.4
+ github.com/tidwall/gjson v1.17.1
+ github.com/tidwall/sjson v1.2.5
+ k8s.io/apimachinery v0.28.0-beta.0
+ k8s.io/client-go v0.28.0-beta.0
+ sigs.k8s.io/controller-runtime v0.15.1-0.20230728161957-7f0c6dc440f3
+ sigs.k8s.io/yaml v1.3.0
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/gofuzz v1.2.0 // indirect
+ github.com/imdario/mergo v0.3.12 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.1 // indirect
+ golang.org/x/net v0.12.0 // indirect
+ golang.org/x/oauth2 v0.8.0 // indirect
+ golang.org/x/sys v0.10.0 // indirect
+ golang.org/x/term v0.10.0 // indirect
+ golang.org/x/text v0.11.0 // indirect
+ golang.org/x/time v0.3.0 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/api v0.28.0-beta.0 // indirect
+ k8s.io/klog/v2 v2.100.1 // indirect
+ k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
+ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+)
diff --git a/images/kube-api-proxy/go.sum b/images/kube-api-proxy/go.sum
new file mode 100644
index 000000000..75f59f874
--- /dev/null
+++ b/images/kube-api-proxy/go.sum
@@ -0,0 +1,167 @@
+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/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
+github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
+github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
+github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
+github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
+github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+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/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
+github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
+github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
+github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
+github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
+go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
+golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
+golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+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.28.0-beta.0 h1:RQib3xI/dxXb2TPvSVRLvAjBjOMnU7jD0GwIAbKwBqU=
+k8s.io/api v0.28.0-beta.0/go.mod h1:AF3hqmc5wvnZD2G4klXcRB9jBn8XEkr+2KvFbpwbvnw=
+k8s.io/apimachinery v0.28.0-beta.0 h1:n3ksD30Isi22awAww6cnQVC8JhnID1Ow4Jhi7ylEHNY=
+k8s.io/apimachinery v0.28.0-beta.0/go.mod h1:xhQIsaL3hXneGluH+0pzF7kr+VYuLS/VcYJxF1xQf+g=
+k8s.io/client-go v0.28.0-beta.0 h1:qOEJLbK9Keyf3VUwwKap8VvXAcqsITDrRzhaWb/0GHY=
+k8s.io/client-go v0.28.0-beta.0/go.mod h1:oCV0v/fHTnc/TUMH0XJORY6kUDh2H6t5DcLv2ISSL/4=
+k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
+k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
+k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/controller-runtime v0.15.1-0.20230728161957-7f0c6dc440f3 h1:tvLrlfTOKA84WNRHf9N6AmGVQ+MBAbHlSqX2pDow6D8=
+sigs.k8s.io/controller-runtime v0.15.1-0.20230728161957-7f0c6dc440f3/go.mod h1:l6pJAr9U2OCzEFeSM37u7Av5pYS5NNXDV5Mesb4Ybgg=
+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.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/images/kube-api-proxy/local/Dockerfile b/images/kube-api-proxy/local/Dockerfile
new file mode 100644
index 000000000..8c7e3690e
--- /dev/null
+++ b/images/kube-api-proxy/local/Dockerfile
@@ -0,0 +1,39 @@
+# Go builder.
+FROM golang:1.21-alpine3.19 AS builder
+
+# Cache-friendly download of go dependencies.
+ADD go.mod go.sum /app/
+WORKDIR /app
+RUN go mod download
+
+ADD . /app
+
+RUN GOOS=linux \
+ go build -o proxy ./cmd/kube-api-proxy
+
+# Go builder.
+FROM golang:1.21-alpine3.19 AS builder-test-controller
+
+# Cache-friendly download of go dependencies.
+ADD local/test-controller/go.mod local/test-controller/go.sum /app/
+WORKDIR /app
+RUN go mod download
+
+ADD local/test-controller/main.go /app/
+
+RUN GOOS=linux \
+ go build -o test-controller .
+
+FROM alpine:3.19
+RUN apk --no-cache add ca-certificates bash sed tini curl && \
+ kubectlArch=linux/amd64 && \
+ echo "Download kubectl for ${kubectlArch}" && \
+ wget https://storage.googleapis.com/kubernetes-release/release/v1.27.5/bin/${kubectlArch}/kubectl -O /bin/kubectl && \
+ chmod +x /bin/kubectl
+COPY --from=builder /app/proxy /
+COPY --from=builder-test-controller /app/test-controller /
+ADD local/proxy.kubeconfig /
+
+# Use user nobody.
+USER 65534:65534
+WORKDIR /
diff --git a/images/kube-api-proxy/local/proxy-gen-certs.sh b/images/kube-api-proxy/local/proxy-gen-certs.sh
new file mode 100755
index 000000000..68d12bd3a
--- /dev/null
+++ b/images/kube-api-proxy/local/proxy-gen-certs.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+
+NAMESPACE=kproxy
+SERVICE_NAME=test-admission-webhook
+CN="api proxying tests for validating webhook"
+OUTDIR=proxy-certs
+
+COMMON_NAME=${SERVICE_NAME}.${NAMESPACE}
+
+set -eo pipefail
+
+echo =================================================================
+echo THIS SCRIPT IS NOT SECURE! USE IT ONLY FOR DEMONSTATION PURPOSES.
+echo =================================================================
+echo
+
+mkdir -p ${OUTDIR} && cd ${OUTDIR}
+
+if [[ -e ca.csr ]] ; then
+ read -p "Regenerate certificates? (yes/no) [no]: "
+ if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]
+ then
+ exit 0
+ fi
+fi
+
+RM_FILES="ca* cert*"
+echo ">>> Remove ${RM_FILES}"
+rm -f $RM_FILES
+
+echo ">>> Generate CA key and certificate"
+cat <>> Generate cert.key and cert.crt"
+cat < ./../../../../api
+
+// TODO: delete this replaces after fixing https://github.com/golang/go/issues/66403.
+replace (
+ github.com/cilium/proxy => github.com/cilium/proxy v0.0.0-20231202123106-38b645b854f3
+ github.com/markbates/safe => github.com/markbates/safe v1.0.1
+ k8s.io/api => k8s.io/api v0.29.2
+ k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.29.2
+ k8s.io/apimachinery => k8s.io/apimachinery v0.29.2
+ k8s.io/apiserver => k8s.io/apiserver v0.29.2
+ k8s.io/code-generator => k8s.io/code-generator v0.29.2
+ k8s.io/component-base => k8s.io/component-base v0.29.2
+ k8s.io/kms => k8s.io/kms v0.29.2
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/emicklei/go-restful/v3 v3.11.0 // indirect
+ github.com/evanphx/json-patch/v5 v5.8.0 // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/go-logr/zapr v1.3.0 // indirect
+ github.com/go-openapi/jsonpointer v0.19.6 // indirect
+ github.com/go-openapi/jsonreference v0.20.2 // indirect
+ github.com/go-openapi/swag v0.22.3 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ 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/imdario/mergo v0.3.12 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
+ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 // indirect
+ github.com/openshift/custom-resource-status v1.1.2 // indirect
+ github.com/pborman/uuid v1.2.1 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/prometheus/client_golang v1.18.0 // indirect
+ github.com/prometheus/client_model v0.5.0 // indirect
+ github.com/prometheus/common v0.45.0 // indirect
+ github.com/prometheus/procfs v0.12.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
+ golang.org/x/net v0.19.0 // indirect
+ golang.org/x/oauth2 v0.12.0 // indirect
+ golang.org/x/sys v0.16.0 // indirect
+ golang.org/x/term v0.15.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ golang.org/x/time v0.3.0 // indirect
+ gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/component-base v0.29.2 // 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
+ kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 // indirect
+ kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect
+ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
+ sigs.k8s.io/yaml v1.4.0 // indirect
+)
diff --git a/images/kube-api-proxy/local/test-controller/go.sum b/images/kube-api-proxy/local/test-controller/go.sum
new file mode 100644
index 000000000..e0ca07bdf
--- /dev/null
+++ b/images/kube-api-proxy/local/test-controller/go.sum
@@ -0,0 +1,484 @@
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckhouse/virtualization/api v0.0.0-20240417135227-efb465e54575 h1:FdSicGvp9Gz1dvrzV7vVkMAlEMYUWMKq/QLKeZxZOtw=
+github.com/deckhouse/virtualization/api v0.0.0-20240417135227-efb465e54575/go.mod h1:1tfoFeZmlKqq6jEuSfIpdrxsBpOcMajYaCbO94pVQLs=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+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 v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+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 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro=
+github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
+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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
+github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
+github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
+github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
+github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
+github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/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/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
+github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
+github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
+github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
+github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
+github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
+github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=
+github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
+github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
+github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
+github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
+github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
+github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
+github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
+github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=
+github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
+github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
+github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
+github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
+github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
+github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
+github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
+github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
+github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=
+github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
+github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
+github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
+github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
+github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
+github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
+github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
+github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
+github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
+github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 h1:t/CahSnpqY46sQR01SoS+Jt0jtjgmhgE6lFmRnO4q70=
+github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183/go.mod h1:4VWG+W22wrB4HfBL88P40DxLEpSOaiBVxUnfalfJo9k=
+github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4=
+github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA=
+github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
+github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+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/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
+github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
+github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
+go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
+golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
+golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
+golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
+golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
+golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
+golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
+golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
+golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
+golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
+golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
+gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A=
+k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0=
+k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg=
+k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8=
+k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8=
+k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
+k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg=
+k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA=
+k8s.io/code-generator v0.29.2/go.mod h1:FwFi3C9jCrmbPjekhaCYcYG1n07CYiW1+PAPCockaos=
+k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8=
+k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM=
+k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
+k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
+k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
+k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
+k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
+k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+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-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
+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-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+kubevirt.io/api v1.0.0 h1:RBdXP5CDhE0v5qL2OUQdrYyRrHe/F68Z91GWqBDF6nw=
+kubevirt.io/api v1.0.0/go.mod h1:CJ4vZsaWhVN3jNbyc9y3lIZhw8nUHbWjap0xHABQiqc=
+kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 h1:IWo12+ei3jltSN5jQN1xjgakfvRSF3G3Rr4GXVOOy2I=
+kubevirt.io/containerized-data-importer-api v1.57.0-alpha1/go.mod h1:Y/8ETgHS1GjO89bl682DPtQOYEU/1ctPFBz6Sjxm4DM=
+kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc=
+kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc=
+sigs.k8s.io/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0=
+sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s=
+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.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/images/kube-api-proxy/local/test-controller/main.go b/images/kube-api-proxy/local/test-controller/main.go
new file mode 100644
index 000000000..916880a3a
--- /dev/null
+++ b/images/kube-api-proxy/local/test-controller/main.go
@@ -0,0 +1,353 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "runtime"
+ "strconv"
+
+ virtv1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2"
+ "github.com/go-logr/logr"
+ "go.uber.org/zap/zapcore"
+ corev1 "k8s.io/api/core/v1"
+ extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+ apiruntime "k8s.io/apimachinery/pkg/runtime"
+ clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+ "k8s.io/client-go/tools/record"
+ kvv1 "kubevirt.io/api/core/v1"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+ "sigs.k8s.io/controller-runtime/pkg/cache"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/config"
+ "sigs.k8s.io/controller-runtime/pkg/controller"
+ "sigs.k8s.io/controller-runtime/pkg/event"
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+ "sigs.k8s.io/controller-runtime/pkg/manager"
+ "sigs.k8s.io/controller-runtime/pkg/manager/signals"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
+ "sigs.k8s.io/controller-runtime/pkg/source"
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+)
+
+var (
+ log = logf.Log.WithName("cmd")
+ resourcesSchemeFuncs = []func(*apiruntime.Scheme) error{
+ clientgoscheme.AddToScheme,
+ extv1.AddToScheme,
+ kvv1.AddToScheme,
+ virtv1alpha2.AddToScheme,
+ }
+)
+
+const (
+ podNamespaceVar = "POD_NAMESPACE"
+ defaultVerbosity = "1"
+)
+
+func setupLogger() {
+ verbose := defaultVerbosity
+ if verboseEnvVarVal := os.Getenv("VERBOSITY"); verboseEnvVarVal != "" {
+ verbose = verboseEnvVarVal
+ }
+ // visit actual flags passed in and if passed check -v and set verbose
+ if fv := flag.Lookup("v"); fv != nil {
+ verbose = fv.Value.String()
+ }
+ if verbose == defaultVerbosity {
+ log.V(1).Info(fmt.Sprintf("Note: increase the -v level in the controller deployment for more detailed logging, eg. -v=%d or -v=%d\n", 2, 3))
+ }
+ verbosityLevel, err := strconv.Atoi(verbose)
+ debug := false
+ if err == nil && verbosityLevel > 1 {
+ debug = true
+ }
+
+ // The logger instantiated here can be changed to any logger
+ // implementing the logr.Logger interface. This logger will
+ // be propagated through the whole operator, generating
+ // uniform and structured logs.
+ logf.SetLogger(zap.New(zap.Level(zapcore.Level(-1*verbosityLevel)), zap.UseDevMode(debug)))
+}
+
+func printVersion() {
+ log.Info(fmt.Sprintf("Go Version: %s", runtime.Version()))
+ log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH))
+}
+
+func main() {
+ flag.Parse()
+
+ setupLogger()
+ printVersion()
+
+ // Get a config to talk to the apiserver
+ cfg, err := config.GetConfig()
+ if err != nil {
+ log.Error(err, "")
+ os.Exit(1)
+ }
+
+ leaderElectionNS := os.Getenv(podNamespaceVar)
+ if leaderElectionNS == "" {
+ leaderElectionNS = "default"
+ }
+
+ // Setup scheme for all resources
+ scheme := apiruntime.NewScheme()
+ for _, f := range resourcesSchemeFuncs {
+ err = f(scheme)
+ if err != nil {
+ log.Error(err, "Failed to add to scheme")
+ os.Exit(1)
+ }
+ }
+
+ managerOpts := manager.Options{
+ // This controller watches resources in all namespaces.
+ LeaderElection: false,
+ LeaderElectionNamespace: leaderElectionNS,
+ LeaderElectionID: "test-controller-leader-election-helper",
+ LeaderElectionResourceLock: "leases",
+ Scheme: scheme,
+ }
+
+ // Create a new Manager to provide shared dependencies and start components
+ mgr, err := manager.New(cfg, managerOpts)
+ if err != nil {
+ log.Error(err, "")
+ os.Exit(1)
+ }
+
+ log.Info("Bootstrapping the Manager.")
+
+ // Setup context to gracefully handle termination.
+ ctx := signals.SetupSignalHandler()
+
+ // Add initial lister to sync rules and routes at start.
+ initLister := &InitialLister{
+ client: mgr.GetClient(),
+ log: log,
+ }
+ err = mgr.Add(initLister)
+ if err != nil {
+ log.Error(err, "add initial lister to the manager")
+ }
+
+ //
+ if _, err := NewController(ctx, mgr, log); err != nil {
+ log.Error(err, "")
+ os.Exit(1)
+ }
+
+ // Start the Manager.
+ if err := mgr.Start(ctx); err != nil {
+ log.Error(err, "manager exited non-zero")
+ os.Exit(1)
+ }
+}
+
+// InitialLister is a Runnable implementatin to access existing objects
+// before handling any event with Reconcile method.
+type InitialLister struct {
+ log logr.Logger
+ client client.Client
+}
+
+func (i *InitialLister) Start(ctx context.Context) error {
+ cl := i.client
+
+ // List VMs, Pods, CRDs before starting manager.
+ vms := virtv1alpha2.VirtualMachineList{}
+ err := cl.List(ctx, &vms)
+ if err != nil {
+ i.log.Error(err, "list VMs")
+ return err
+ }
+ log.Info(fmt.Sprintf("List returns %d VMs", len(vms.Items)))
+ for _, vm := range vms.Items {
+ i.log.Info(fmt.Sprintf("observe VM %s/%s at start", vm.GetNamespace(), vm.GetName()))
+ }
+
+ pods := corev1.PodList{}
+ err = cl.List(ctx, &pods, client.InNamespace(""))
+ if err != nil {
+ i.log.Error(err, "list Pods")
+ return err
+ }
+ log.Info(fmt.Sprintf("List returns %d Pods", len(pods.Items)))
+ for _, pod := range pods.Items {
+ i.log.Info(fmt.Sprintf("observe Pod %s/%s at start", pod.GetNamespace(), pod.GetName()))
+ }
+
+ crds := extv1.CustomResourceDefinitionList{}
+ err = cl.List(ctx, &crds, client.InNamespace(""))
+ if err != nil {
+ i.log.Error(err, "list Pods")
+ return err
+ }
+ log.Info(fmt.Sprintf("List returns %d CRDs", len(crds.Items)))
+ for _, crd := range crds.Items {
+ i.log.Info(fmt.Sprintf("observe CRD %s/%s at start", crd.GetNamespace(), crd.GetName()))
+ }
+
+ i.log.Info("Initial listing done, proceed to manager Start")
+ return nil
+}
+
+const (
+ controllerName = "test-controller"
+)
+
+func NewController(
+ ctx context.Context,
+ mgr manager.Manager,
+ log logr.Logger,
+) (controller.Controller, error) {
+ reconciler := &VMReconciler{
+ Client: mgr.GetClient(),
+ Cache: mgr.GetCache(),
+ Recorder: mgr.GetEventRecorderFor(controllerName),
+ Scheme: mgr.GetScheme(),
+ Log: log,
+ }
+
+ c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: reconciler})
+ if err != nil {
+ return nil, err
+ }
+
+ if err = SetupWatches(ctx, mgr, c, log); err != nil {
+ return nil, err
+ }
+
+ if err = SetupWebhooks(ctx, mgr, reconciler); err != nil {
+ return nil, err
+ }
+
+ log.Info("Initialized controller with test watches")
+ return c, nil
+}
+
+// SetupWatches subscripts controller to Pods, CRDs and DVP VMs.
+func SetupWatches(ctx context.Context, mgr manager.Manager, ctr controller.Controller, log logr.Logger) error {
+ if err := ctr.Watch(source.Kind(mgr.GetCache(), &virtv1alpha2.VirtualMachine{}), &handler.EnqueueRequestForObject{},
+ //if err := ctr.Watch(source.Kind(mgr.GetCache(), &corev1.Pod{}), &handler.EnqueueRequestForObject{},
+ predicate.Funcs{
+ CreateFunc: func(e event.CreateEvent) bool {
+ log.Info(fmt.Sprintf("Got CREATE event for VM %s/%s gvk %v", e.Object.GetNamespace(), e.Object.GetName(), e.Object.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ DeleteFunc: func(e event.DeleteEvent) bool {
+ log.Info(fmt.Sprintf("Got DELETE event for VM %s/%s gvk %v", e.Object.GetNamespace(), e.Object.GetName(), e.Object.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ UpdateFunc: func(e event.UpdateEvent) bool {
+ log.Info(fmt.Sprintf("Got UPDATE event for VM %s/%s gvk %v", e.ObjectNew.GetNamespace(), e.ObjectNew.GetName(), e.ObjectNew.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ },
+ ); err != nil {
+ return fmt.Errorf("error setting watch on DVP VMs: %w", err)
+ }
+
+ if err := ctr.Watch(source.Kind(mgr.GetCache(), &corev1.Pod{}), &handler.EnqueueRequestForObject{},
+ predicate.Funcs{
+ CreateFunc: func(e event.CreateEvent) bool {
+ log.Info(fmt.Sprintf("Got CREATE event for Pod %s/%s gvk %v", e.Object.GetNamespace(), e.Object.GetName(), e.Object.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ DeleteFunc: func(e event.DeleteEvent) bool {
+ log.Info(fmt.Sprintf("Got DELETE event for Pod %s/%s gvk %v", e.Object.GetNamespace(), e.Object.GetName(), e.Object.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ UpdateFunc: func(e event.UpdateEvent) bool {
+ log.Info(fmt.Sprintf("Got UPDATE event for Pod %s/%s gvk %v", e.ObjectNew.GetNamespace(), e.ObjectNew.GetName(), e.ObjectNew.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ },
+ ); err != nil {
+ return fmt.Errorf("error setting watch on Pods: %w", err)
+ }
+
+ if err := ctr.Watch(source.Kind(mgr.GetCache(), &extv1.CustomResourceDefinition{}), &handler.EnqueueRequestForObject{},
+ predicate.Funcs{
+ CreateFunc: func(e event.CreateEvent) bool {
+ log.Info(fmt.Sprintf("Got CREATE event for CRD %s/%s gvk %v", e.Object.GetNamespace(), e.Object.GetName(), e.Object.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ DeleteFunc: func(e event.DeleteEvent) bool {
+ log.Info(fmt.Sprintf("Got DELETE event for CRD %s/%s gvk %v", e.Object.GetNamespace(), e.Object.GetName(), e.Object.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ UpdateFunc: func(e event.UpdateEvent) bool {
+ log.Info(fmt.Sprintf("Got UPDATE event for CRD %s/%s gvk %v", e.ObjectNew.GetNamespace(), e.ObjectNew.GetName(), e.ObjectNew.GetObjectKind().GroupVersionKind()))
+ return true
+ },
+ },
+ ); err != nil {
+ return fmt.Errorf("error setting watch on CRDs: %w", err)
+ }
+
+ return nil
+}
+
+func SetupWebhooks(ctx context.Context, mgr manager.Manager, validator admission.CustomValidator) error {
+ return builder.WebhookManagedBy(mgr).
+ For(&kvv1.VirtualMachine{}).
+ WithValidator(validator).
+ Complete()
+}
+
+type VMReconciler struct {
+ Client client.Client
+ Cache cache.Cache
+ Recorder record.EventRecorder
+ Scheme *apiruntime.Scheme
+ Log logr.Logger
+}
+
+func (r *VMReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
+ r.Log.Info(fmt.Sprintf("Got request for %s", req.String()))
+ return reconcile.Result{}, nil
+}
+
+func (r *VMReconciler) ValidateCreate(ctx context.Context, obj apiruntime.Object) (admission.Warnings, error) {
+ vm, ok := obj.(*kvv1.VirtualMachine)
+ if !ok {
+ return nil, fmt.Errorf("expected a new VirtualMachine but got a %T", obj)
+ }
+
+ warnings := admission.Warnings{
+ fmt.Sprintf("Validate new VM %s is OK, got kind %s, apiVersion %s", vm.GetName(), vm.GetObjectKind(), vm.APIVersion),
+ }
+ return warnings, nil
+}
+
+func (r *VMReconciler) ValidateUpdate(ctx context.Context, _, newObj apiruntime.Object) (admission.Warnings, error) {
+ vm, ok := newObj.(*kvv1.VirtualMachine)
+ if !ok {
+ return nil, fmt.Errorf("expected a new VirtualMachine but got a %T", newObj)
+ }
+
+ warnings := admission.Warnings{
+ fmt.Sprintf("Validate updated VM %s is OK, got kind %s, apiVersion %s", vm.GetName(), vm.GetObjectKind(), vm.APIVersion),
+ }
+ return warnings, nil
+}
+
+func (v *VMReconciler) ValidateDelete(_ context.Context, obj apiruntime.Object) (admission.Warnings, error) {
+ vm, ok := obj.(*kvv1.VirtualMachine)
+ if !ok {
+ return nil, fmt.Errorf("expected a deleted VirtualMachine but got a %T", obj)
+ }
+
+ warnings := admission.Warnings{
+ fmt.Sprintf("Validate deleted VM %s is OK, got kind %s, apiVersion %s", vm.GetName(), vm.GetObjectKind(), vm.APIVersion),
+ }
+ return warnings, nil
+}
diff --git a/images/kube-api-proxy/pkg/kubevirt/kubevirt_rules.go b/images/kube-api-proxy/pkg/kubevirt/kubevirt_rules.go
new file mode 100644
index 000000000..81d735bc5
--- /dev/null
+++ b/images/kube-api-proxy/pkg/kubevirt/kubevirt_rules.go
@@ -0,0 +1,551 @@
+package kubevirt
+
+import (
+ . "kube-api-proxy/pkg/rewriter"
+)
+
+var KubevirtRewriteRules = &RewriteRules{
+ KindPrefix: "DVPInternal", // KV
+ ResourceTypePrefix: "dvpinternal", // kv
+ ShortNamePrefix: "dvp",
+ Categories: []string{"dvpinternal"},
+ RenamedGroup: "internal.virtualization.deckhouse.io",
+ Rules: KubevirtAPIGroupsRules,
+ Webhooks: KubevirtWebhooks,
+}
+
+// TODO create generator in golang to produce below rules from Kubevirt and CDI sources so proxy can work with future versions.
+
+var KubevirtAPIGroupsRules = map[string]APIGroupRule{
+ "cdi.kubevirt.io": {
+ GroupRule: GroupRule{
+ Group: "cdi.kubevirt.io",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ // cdiconfigs.cdi.kubevirt.io
+ "cdiconfigs": {
+ Kind: "CDIConfig",
+ ListKind: "CDIConfigList",
+ Plural: "cdiconfigs",
+ Singular: "cdiconfig",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{},
+ ShortNames: []string{},
+ },
+ // cdis.cdi.kubevirt.io
+ "cdis": {
+ Kind: "CDI",
+ ListKind: "CDIList",
+ Plural: "cdis",
+ Singular: "cdi",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{},
+ ShortNames: []string{"cdi", "cdis"},
+ },
+ // dataimportcrons.cdi.kubevirt.io
+ "dataimportcrons": {
+ Kind: "DataImportCron",
+ ListKind: "DataImportCronList",
+ Plural: "dataimportcrons",
+ Singular: "dataimportcron",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{"all"},
+ ShortNames: []string{"dic", "dics"},
+ },
+ // datasources.cdi.kubevirt.io
+ "datasources": {
+ Kind: "DataSource",
+ ListKind: "DataSourceList",
+ Plural: "datasources",
+ Singular: "datasource",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{"all"},
+ ShortNames: []string{"das"},
+ },
+ // datavolumes.cdi.kubevirt.io
+ "datavolumes": {
+ Kind: "DataVolume",
+ ListKind: "DataVolumeList",
+ Plural: "datavolumes",
+ Singular: "datavolume",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{"all"},
+ ShortNames: []string{"dv", "dvs"},
+ },
+ // objecttransfers.cdi.kubevirt.io
+ "objecttransfers": {
+ Kind: "ObjectTransfer",
+ ListKind: "ObjectTransferList",
+ Plural: "objecttransfers",
+ Singular: "objecttransfer",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{},
+ ShortNames: []string{"ot", "ots"},
+ },
+ // storageprofiles.cdi.kubevirt.io
+ "storageprofiles": {
+ Kind: "StorageProfile",
+ ListKind: "StorageProfileList",
+ Plural: "storageprofiles",
+ Singular: "storageprofile",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{},
+ ShortNames: []string{},
+ },
+ // volumeclonesources.cdi.kubevirt.io
+ "volumeclonesources": {
+ Kind: "VolumeCloneSource",
+ ListKind: "VolumeCloneSourceList",
+ Plural: "volumeclonesources",
+ Singular: "volumeclonesource",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{},
+ ShortNames: []string{},
+ },
+ // volumeimportsources.cdi.kubevirt.io
+ "volumeimportsources": {
+ Kind: "VolumeImportSource",
+ ListKind: "VolumeImportSourceList",
+ Plural: "volumeimportsources",
+ Singular: "volumeimportsource",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{},
+ ShortNames: []string{},
+ },
+ // volumeuploadsources.cdi.kubevirt.io
+ "volumeuploadsources": {
+ Kind: "VolumeUploadSource",
+ ListKind: "VolumeUploadSourceList",
+ Plural: "volumeuploadsources",
+ Singular: "volumeuploadsource",
+ Versions: []string{"v1beta1"},
+ PreferredVersion: "v1beta1",
+ Categories: []string{},
+ ShortNames: []string{},
+ },
+ },
+ },
+ "kubevirt.io": {
+ GroupRule: GroupRule{
+ Group: "kubevirt.io",
+ Versions: []string{"v1", "v1alpha3"},
+ PreferredVersion: "v1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ // kubevirts.kubevirt.io
+ "kubevirts": {
+ Kind: "KubeVirt",
+ ListKind: "KubeVirtList",
+ Plural: "kubevirts",
+ Singular: "kubevirt",
+ Versions: []string{"v1", "v1alpha3"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"kv", "kvs"},
+ },
+ // virtualmachines.kubevirt.io
+ "virtualmachines": {
+ Kind: "VirtualMachine",
+ ListKind: "VirtualMachineList",
+ Plural: "virtualmachines",
+ Singular: "virtualmachine",
+ Versions: []string{"v1", "v1alpha3"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vm", "vms"},
+ },
+ // virtualmachineinstances.kubevirt.io
+ "virtualmachineinstances": {
+ Kind: "VirtualMachineInstance",
+ ListKind: "VirtualMachineInstanceList",
+ Plural: "virtualmachineinstances",
+ Singular: "virtualmachineinstance",
+ Versions: []string{"v1", "v1alpha3"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmi", "vmsi"},
+ },
+ // virtualmachineinstancemigrations.kubevirt.io
+ "virtualmachineinstancemigrations": {
+ Kind: "VirtualMachineInstanceMigration",
+ ListKind: "VirtualMachineInstanceMigrationList",
+ Plural: "virtualmachineinstancemigrations",
+ Singular: "virtualmachineinstancemigration",
+ Versions: []string{"v1", "v1alpha3"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmim", "vmims"},
+ },
+ // virtualmachineinstancepresets.kubevirt.io
+ "virtualmachineinstancepresets": {
+ Kind: "VirtualMachineInstancePreset",
+ ListKind: "VirtualMachineInstancePresetList",
+ Plural: "virtualmachineinstancepresets",
+ Singular: "virtualmachineinstancepreset",
+ Versions: []string{"v1", "v1alpha3"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmipreset", "vmipresets"},
+ },
+ // virtualmachineinstancereplicasets.kubevirt.io
+ "virtualmachineinstancereplicasets": {
+ Kind: "VirtualMachineInstanceReplicaSet",
+ ListKind: "VirtualMachineInstanceReplicaSetList",
+ Plural: "virtualmachineinstancereplicasets",
+ Singular: "virtualmachineinstancereplicaset",
+ Versions: []string{"v1", "v1alpha3"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmirs", "vmirss"},
+ },
+ },
+ },
+ "clone.kubevirt.io": {
+ GroupRule: GroupRule{
+ Group: "clone.kubevirt.io",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ // virtualmachineclones.clone.kubevirt.io
+ "virtualmachineclones": {
+ Kind: "VirtualMachineClone",
+ ListKind: "VirtualMachineCloneList",
+ Plural: "virtualmachineclones",
+ Singular: "virtualmachineclone",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmclone", "vmclones"},
+ },
+ },
+ },
+ "export.kubevirt.io": {
+ GroupRule: GroupRule{
+ Group: "export.kubevirt.io",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ // virtualmachineexports.export.kubevirt.io
+ "virtualmachineexports": {
+ Kind: "VirtualMachineExport",
+ ListKind: "VirtualMachineExportList",
+ Plural: "virtualmachineexports",
+ Singular: "virtualmachineexport",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmexport", "vmexports"},
+ },
+ },
+ },
+ "instancetype.kubevirt.io": {
+ GroupRule: GroupRule{
+ Group: "instancetype.kubevirt.io",
+ Versions: []string{"v1alpha1", "v1alpha2"},
+ PreferredVersion: "v1alpha2",
+ },
+ ResourceRules: map[string]ResourceRule{
+ // virtualmachineinstancetypes.instancetype.kubevirt.io
+ "virtualmachineinstancetypes": {
+ Kind: "VirtualMachineInstancetype",
+ ListKind: "VirtualMachineInstancetypeList",
+ Plural: "virtualmachineinstancetypes",
+ Singular: "virtualmachineinstancetype",
+ Versions: []string{"v1alpha1", "v1alpha2"},
+ PreferredVersion: "v1alpha2",
+ Categories: []string{"all"},
+ ShortNames: []string{"vminstancetype", "vminstancetypes", "vmf", "vmfs"},
+ },
+ // virtualmachinepreferences.instancetype.kubevirt.io
+ "virtualmachinepreferences": {
+ Kind: "VirtualMachinePreference",
+ ListKind: "VirtualMachinePreferenceList",
+ Plural: "virtualmachinepreferences",
+ Singular: "virtualmachinepreference",
+ Versions: []string{"v1alpha1", "v1alpha2"},
+ PreferredVersion: "v1alpha2",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmpref", "vmprefs", "vmp", "vmps"},
+ },
+ // virtualmachineclusterinstancetypes.instancetype.kubevirt.io
+ "virtualmachineclusterinstancetypes": {
+ Kind: "VirtualMachineClusterInstancetype",
+ ListKind: "VirtualMachineClusterInstancetypeList",
+ Plural: "virtualmachineclusterinstancetypes",
+ Singular: "virtualmachineclusterinstancetype",
+ Versions: []string{"v1alpha1", "v1alpha2"},
+ PreferredVersion: "v1alpha2",
+ Categories: []string{},
+ ShortNames: []string{"vmclusterinstancetype", "vmclusterinstancetypes", "vmcf", "vmcfs"},
+ },
+ // virtualmachineclusterpreferences.instancetype.kubevirt.io
+ "virtualmachineclusterpreferences": {
+ Kind: "VirtualMachineClusterPreference",
+ ListKind: "VirtualMachineClusterPreferenceList",
+ Plural: "virtualmachineclusterpreferences",
+ Singular: "virtualmachineclusterpreference",
+ Versions: []string{"v1alpha1", "v1alpha2"},
+ PreferredVersion: "v1alpha2",
+ Categories: []string{},
+ ShortNames: []string{"vmcp", "vmcps"},
+ },
+ },
+ },
+ "migrations.kubevirt.io": {
+ GroupRule: GroupRule{
+ Group: "migrations.kubevirt.io",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ // migrationpolicies.migrations.kubevirt.io
+ "migrationpolicies": {
+ Kind: "MigrationPolicy",
+ ListKind: "MigrationPolicyList",
+ Plural: "migrationpolicies",
+ Singular: "migrationpolicy",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ Categories: []string{"all"},
+ ShortNames: []string{},
+ },
+ },
+ },
+ "pool.kubevirt.io": {
+ GroupRule: GroupRule{
+ Group: "pool.kubevirt.io",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ // virtualmachinepools.pool.kubevirt.io
+ "virtualmachinepools": {
+ Kind: "VirtualMachinePool",
+ ListKind: "VirtualMachinePoolList",
+ Plural: "virtualmachinepools",
+ Singular: "virtualmachinepool",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmpool", "vmpools"},
+ },
+ },
+ },
+ "snapshot.kubevirt.io": {
+ GroupRule: GroupRule{
+ Group: "snapshot.kubevirt.io",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ // virtualmachinerestores.snapshot.kubevirt.io
+ "virtualmachinerestores": {
+ Kind: "VirtualMachineRestore",
+ ListKind: "VirtualMachineRestoreList",
+ Plural: "virtualmachinerestores",
+ Singular: "virtualmachinerestore",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmrestore", "vmrestores"},
+ },
+ // virtualmachinesnapshotcontents.snapshot.kubevirt.io
+ "virtualmachinesnapshotcontents": {
+ Kind: "VirtualMachineSnapshotContent",
+ ListKind: "VirtualMachineSnapshotContentList",
+ Plural: "virtualmachinesnapshotcontents",
+ Singular: "virtualmachinesnapshotcontent",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmsnapshotcontent", "vmsnapshotcontents"},
+ },
+ // virtualmachinesnapshots.snapshot.kubevirt.io
+ "virtualmachinesnapshots": {
+ Kind: "VirtualMachineSnapshot",
+ ListKind: "VirtualMachineSnapshotList",
+ Plural: "virtualmachinesnapshots",
+ Singular: "virtualmachinesnapshot",
+ Versions: []string{"v1alpha1"},
+ PreferredVersion: "v1alpha1",
+ Categories: []string{"all"},
+ ShortNames: []string{"vmsnapshot", "vmsnapshots"},
+ },
+ },
+ },
+}
+
+var KubevirtWebhooks = map[string]WebhookRule{
+ // CDI webhooks.
+ // Run this in original CDI installation:
+ // kubectl get validatingwebhookconfiguration,mutatingwebhookconfiguration -l cdi.kubevirt.io -o json | jq '.items[] | .webhooks[] | {"path": .clientConfig.service.path, "group": (.rules[]|.apiGroups|join(",")), "resource": (.rules[]|.resources|join(",")) } | "\""+.path +"\": {\nPath: \"" + .path + "\",\nGroup: \"" + .group + "\",\nResource: \"" + .resource + "\",\n}," ' -r
+ // TODO create generator in golang to extract these rules from resource definitions in the cdi-operator package.
+ "/datavolume-mutate": {
+ Path: "/datavolume-mutate",
+ Group: "cdi.kubevirt.io",
+ Resource: "datavolumes",
+ },
+ "/dataimportcron-validate": {
+ Path: "/dataimportcron-validate",
+ Group: "cdi.kubevirt.io",
+ Resource: "dvpinternaldataimportcrons",
+ },
+ "/datavolume-validate": {
+ Path: "/datavolume-validate",
+ Group: "cdi.kubevirt.io",
+ Resource: "datavolumes",
+ },
+ "/cdi-validate": {
+ Path: "/cdi-validate",
+ Group: "cdi.kubevirt.io",
+ Resource: "cdis",
+ },
+ "/objecttransfer-validate": {
+ Path: "/objecttransfer-validate",
+ Group: "cdi.kubevirt.io",
+ Resource: "objecttransfers",
+ },
+ "/populator-validate": {
+ Path: "/populator-validate",
+ Group: "cdi.kubevirt.io",
+ Resource: "volumeimportsources", // Also, volumeuploadsources. This field for logging only.
+ },
+
+ // Kubevirt webhooks.
+ // Run this in original Kubevirt installation:
+ // kubectl get validatingwebhookconfiguration,mutatingwebhookconfiguration -l kubevirt.io -o json | jq '.items[] | .webhooks[] | {"path": .clientConfig.service.path, "group": (.rules[]|.apiGroups|join(",")), "resource": (.rules[]|.resources|join(",")) } | "\""+.path +"\": {\nPath: \"" + .path + "\",\nGroup: \"" + .group + "\",\nResource: \"" + .resource + "\",\n}," '
+ // TODO create generator in golang to extract these rules from resource definitions in the virt-operator package.
+ "/virtualmachineinstances-validate-create": {
+ Path: "/virtualmachineinstances-validate-create",
+ Group: "kubevirt.io",
+ Resource: "virtualmachineinstances",
+ },
+ "/virtualmachineinstances-validate-update": {
+ Path: "/virtualmachineinstances-validate-update",
+ Group: "kubevirt.io",
+ Resource: "virtualmachineinstances",
+ },
+ "/virtualmachines-validate": {
+ Path: "/virtualmachines-validate",
+ Group: "kubevirt.io",
+ Resource: "virtualmachines",
+ },
+ "/virtualmachinereplicaset-validate": {
+ Path: "/virtualmachinereplicaset-validate",
+ Group: "kubevirt.io",
+ Resource: "virtualmachineinstancereplicasets",
+ },
+ "/virtualmachinepool-validate": {
+ Path: "/virtualmachinepool-validate",
+ Group: "pool.kubevirt.io",
+ Resource: "virtualmachinepools",
+ },
+ "/vmipreset-validate": {
+ Path: "/vmipreset-validate",
+ Group: "kubevirt.io",
+ Resource: "virtualmachineinstancepresets",
+ },
+ "/migration-validate-create": {
+ Path: "/migration-validate-create",
+ Group: "kubevirt.io",
+ Resource: "virtualmachineinstancemigrations",
+ },
+ "/migration-validate-update": {
+ Path: "/migration-validate-update",
+ Group: "kubevirt.io",
+ Resource: "virtualmachineinstancemigrations",
+ },
+ "/virtualmachinesnapshots-validate": {
+ Path: "/virtualmachinesnapshots-validate",
+ Group: "snapshot.kubevirt.io",
+ Resource: "virtualmachinesnapshots",
+ },
+ "/virtualmachinerestores-validate": {
+ Path: "/virtualmachinerestores-validate",
+ Group: "snapshot.kubevirt.io",
+ Resource: "virtualmachinerestores",
+ },
+ "/virtualmachineexports-validate": {
+ Path: "/virtualmachineexports-validate",
+ Group: "export.kubevirt.io",
+ Resource: "virtualmachineexports",
+ },
+ "/virtualmachineinstancetypes-validate": {
+ Path: "/virtualmachineinstancetypes-validate",
+ Group: "instancetype.kubevirt.io",
+ Resource: "virtualmachineinstancetypes",
+ },
+ "/virtualmachineclusterinstancetypes-validate": {
+ Path: "/virtualmachineclusterinstancetypes-validate",
+ Group: "instancetype.kubevirt.io",
+ Resource: "virtualmachineclusterinstancetypes",
+ },
+ "/virtualmachinepreferences-validate": {
+ Path: "/virtualmachinepreferences-validate",
+ Group: "instancetype.kubevirt.io",
+ Resource: "virtualmachinepreferences",
+ },
+ "/virtualmachineclusterpreferences-validate": {
+ Path: "/virtualmachineclusterpreferences-validate",
+ Group: "instancetype.kubevirt.io",
+ Resource: "virtualmachineclusterpreferences",
+ },
+ "/status-validate": {
+ Path: "/status-validate",
+ Group: "kubevirt.io",
+ Resource: "virtualmachines/status,virtualmachineinstancereplicasets/status,virtualmachineinstancemigrations/status",
+ },
+ "/migration-policy-validate-create": {
+ Path: "/migration-policy-validate-create",
+ Group: "migrations.kubevirt.io",
+ Resource: "migrationpolicies",
+ },
+ "/vm-clone-validate-create": {
+ Path: "/vm-clone-validate-create",
+ Group: "clone.kubevirt.io",
+ Resource: "virtualmachineclones",
+ },
+ "/kubevirt-validate-delete": {
+ Path: "/kubevirt-validate-delete",
+ Group: "kubevirt.io",
+ Resource: "kubevirts",
+ },
+ "/kubevirt-validate-update": {
+ Path: "/kubevirt-validate-update",
+ Group: "kubevirt.io",
+ Resource: "kubevirts",
+ },
+ "/virtualmachines-mutate": {
+ Path: "/virtualmachines-mutate",
+ Group: "kubevirt.io",
+ Resource: "virtualmachines",
+ },
+ "/virtualmachineinstances-mutate": {
+ Path: "/virtualmachineinstances-mutate",
+ Group: "kubevirt.io",
+ Resource: "virtualmachineinstances",
+ },
+ "/migration-mutate-create": {
+ Path: "/migration-mutate-create",
+ Group: "kubevirt.io",
+ Resource: "virtualmachineinstancemigrations",
+ },
+ "/vm-clone-mutate-create": {
+ Path: "/vm-clone-mutate-create",
+ Group: "clone.kubevirt.io",
+ Resource: "virtualmachineclones",
+ },
+}
diff --git a/images/kube-api-proxy/pkg/kubevirt/kubevirt_rules_test.go b/images/kube-api-proxy/pkg/kubevirt/kubevirt_rules_test.go
new file mode 100644
index 000000000..2303572b2
--- /dev/null
+++ b/images/kube-api-proxy/pkg/kubevirt/kubevirt_rules_test.go
@@ -0,0 +1,17 @@
+package kubevirt
+
+import (
+ "fmt"
+ "testing"
+
+ "sigs.k8s.io/yaml"
+)
+
+func TestKubevirtRulesToYAML(t *testing.T) {
+ b, err := yaml.Marshal(KubevirtRewriteRules)
+ if err != nil {
+ t.Fatalf("should marshal kubevirt rules without error: %v", err)
+ }
+
+ fmt.Printf("%s\n", string(b))
+}
diff --git a/images/kube-api-proxy/pkg/log/body.go b/images/kube-api-proxy/pkg/log/body.go
new file mode 100644
index 000000000..6902c6388
--- /dev/null
+++ b/images/kube-api-proxy/pkg/log/body.go
@@ -0,0 +1,55 @@
+package log
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+)
+
+type ReaderLogger struct {
+ wrappedReader io.ReadCloser
+ buf bytes.Buffer
+}
+
+func NewReaderLogger(r io.Reader) *ReaderLogger {
+ rdr := &ReaderLogger{}
+ rdr.wrappedReader = io.NopCloser(io.TeeReader(r, &rdr.buf))
+ return rdr
+}
+
+func (r *ReaderLogger) Read(p []byte) (n int, err error) {
+ return r.wrappedReader.Read(p)
+}
+
+func (r *ReaderLogger) Close() error {
+ return r.wrappedReader.Close()
+}
+
+func HeadString(obj interface{}, limit int) string {
+ readLog, ok := obj.(*ReaderLogger)
+ if !ok {
+ return ""
+ }
+ bufLen := readLog.buf.Len()
+ bufStr := readLog.buf.String()
+ if bufLen < limit {
+ return bufStr
+ }
+ return bufStr[0:limit]
+}
+
+func HeadStringEx(obj interface{}, limit int) string {
+ s := HeadString(obj, limit)
+ if s == "" {
+ return ""
+ }
+ return fmt.Sprintf("[%d] %s", len(s), s)
+}
+
+func HasData(obj interface{}) bool {
+ readLog, ok := obj.(*ReaderLogger)
+ if !ok {
+ return false
+ }
+ return readLog.buf.Len() > 0
+}
diff --git a/images/kube-api-proxy/pkg/log/error.go b/images/kube-api-proxy/pkg/log/error.go
new file mode 100644
index 000000000..07938567d
--- /dev/null
+++ b/images/kube-api-proxy/pkg/log/error.go
@@ -0,0 +1,7 @@
+package log
+
+import "log/slog"
+
+func SlogErr(err error) slog.Attr {
+ return slog.Any("err", err)
+}
diff --git a/images/kube-api-proxy/pkg/proxy/doc.go b/images/kube-api-proxy/pkg/proxy/doc.go
new file mode 100644
index 000000000..301388b37
--- /dev/null
+++ b/images/kube-api-proxy/pkg/proxy/doc.go
@@ -0,0 +1,39 @@
+package proxy
+
+// Proxy handler implements 2 types of proxy:
+// - proxy for client interaction with Kubernetes API Server
+// - proxy to deliver AdmissionReview requests from Kubernetes API Server to webhook server
+//
+// Proxy for webhooks acts as follows:
+// ServerHTTP method reads request from Kubernetes API Server, restores apiVersion, kind and
+// ownerRefs, sends it to real webhook, renames apiVersion, kind, and ownerRefs
+// and sends it back to Kubernetes API Server.
+//
+// +--------------------------------------------+
+// | Kubernetes API Server |
+// +--------------------------------------------+
+// | ^
+// | |
+// 1. AdmissionReview request 4. AdmissionReview response
+// webhook.srv:443/webhook-endpoint |
+// apiVersion: renamed-group.io |
+// kind: PrefixedResource |
+// | |
+// v |
+// +-----------------------------------------------------+
+// | Proxy |
+// | 2. Restore 3. Rename |
+// | apiVersion, kind field if Admission response |
+// | in Admission request has patchType: JSONPatch |
+// | in Admission request rename kind in ownerRef |
+// +-----------------------------------------------------+
+// | ^
+// 127.0.0.1:9443/webhook-endpoint |
+// apiVersion: original-group.io |
+// kind: Resource |
+// | |
+// v |
+// +-------------------------------------------------------+
+// | Webhook |
+// | handles request ---> sends response |
+// +-------------------------------------------------------+
diff --git a/images/kube-api-proxy/pkg/proxy/handler.go b/images/kube-api-proxy/pkg/proxy/handler.go
new file mode 100644
index 000000000..45ec68648
--- /dev/null
+++ b/images/kube-api-proxy/pkg/proxy/handler.go
@@ -0,0 +1,419 @@
+package proxy
+
+import (
+ "bytes"
+ "compress/flate"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "sync"
+
+ logutil "kube-api-proxy/pkg/log"
+ "kube-api-proxy/pkg/rewriter"
+)
+
+type ProxyMode string
+
+const (
+ // ToOriginal mode indicates that resource should be restored when passed to target and renamed when passing back to client.
+ ToOriginal ProxyMode = "original"
+ // ToRenamed mode indicates that resource should be renamed when passed to target and restored when passing back to client.
+ ToRenamed ProxyMode = "renamed"
+)
+
+func ToTargetAction(proxyMode ProxyMode) rewriter.Action {
+ if proxyMode == ToRenamed {
+ return rewriter.Rename
+ }
+ return rewriter.Restore
+}
+
+func FromTargetAction(proxyMode ProxyMode) rewriter.Action {
+ if proxyMode == ToRenamed {
+ return rewriter.Restore
+ }
+ return rewriter.Rename
+}
+
+type Handler struct {
+ Name string
+ // ProxyPass is a target http client to send requests to.
+ // An allusion to nginx proxy_pass directive.
+ TargetClient *http.Client
+ TargetURL *url.URL
+ ProxyMode ProxyMode
+ Rewriter *rewriter.RuleBasedRewriter
+ m sync.Mutex
+}
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ if req == nil {
+ slog.Error("req is nil. something wrong")
+ return
+ }
+ if req.URL == nil {
+ slog.Error(fmt.Sprintf("req.URL is nil. something wrong. method %s RequestURI '%s' Headers %+v", req.Method, req.RequestURI, req.Header))
+ return
+ }
+
+ // Parse request url, prepare path rewrite.
+ targetReq := rewriter.NewTargetRequest(h.Rewriter, req)
+
+ resource := targetReq.ResourceForLog()
+
+ logger := slog.With(
+ slog.String("request", fmt.Sprintf("%s %s", req.Method, req.URL.Path)),
+ slog.String("resource", resource),
+ slog.String("proxy.name", h.Name),
+ )
+
+ // Set target address, cleanup RequestURI.
+ req.RequestURI = ""
+ req.URL.Scheme = h.TargetURL.Scheme
+ req.URL.Host = h.TargetURL.Host
+
+ // Log request path.
+ rwrReq := " NO"
+ if targetReq.ShouldRewriteRequest() {
+ rwrReq = "REQ"
+ }
+ rwrResp := " NO"
+ if targetReq.ShouldRewriteResponse() {
+ rwrResp = "RESP"
+ }
+ if targetReq.Path() != req.URL.Path {
+ logger.Info(fmt.Sprintf("%s [%s,%s] %s -> %s", req.Method, rwrReq, rwrResp, req.URL.String(), targetReq.Path()))
+ } else {
+ logger.Info(fmt.Sprintf("%s [%s,%s] %s", req.Method, rwrReq, rwrResp, req.URL.String()))
+ }
+
+ // TODO(development): Mute some logging for development: election, non-rewritable resources.
+ isMute := false
+ if !targetReq.ShouldRewriteRequest() && !targetReq.ShouldRewriteResponse() {
+ isMute = true
+ }
+ switch resource {
+ case "leases":
+ isMute = true
+ case "endpoints":
+ isMute = true
+ case "clusterrolebindings":
+ isMute = false
+ case "clustervirtualmachineimages":
+ isMute = false
+ }
+ if isMute {
+ logger = slog.New(slog.NewTextHandler(io.Discard, nil))
+ }
+
+ logger.Info(fmt.Sprintf("Request: orig headers: %+v", req.Header))
+
+ // Modify req to send it to target.
+ err := h.transformRequest(targetReq, req)
+ //targetReq, err := p.Rewriter.RewriteToTarget(req)
+ if err != nil {
+ logger.Error(fmt.Sprintf("Error rewriting request: %s", req.URL.String()), logutil.SlogErr(err))
+ http.Error(w, "can't rewrite request", http.StatusBadRequest)
+ return
+ }
+
+ logger.Info(fmt.Sprintf("Request: target headers: %+v", req.Header))
+
+ // Wrap reader to log content after transferring request Body.
+ if req.Body != nil {
+ req.Body = logutil.NewReaderLogger(req.Body)
+ }
+
+ resp, err := h.TargetClient.Do(req)
+ if err != nil {
+ logger.Error("Proxy pass request error", logutil.SlogErr(err))
+ http.Error(w, "Proxy pass request error", http.StatusInternalServerError)
+ // TODO return apimachinery NewInternalError
+ // https://github.com/kubernetes/apimachinery/blob/master/pkg/api/errors/errors.go
+ return
+ }
+ defer resp.Body.Close()
+
+ // TODO handle resp.Status 3xx, 4xx, 5xx, etc.
+
+ // TODO delete after development: Log head of the request body.
+ if logutil.HasData(req.Body) {
+ limit := 512
+ switch resource {
+ case "virtualmachines",
+ "virtualmachines/status",
+ "virtualmachineinstances",
+ "virtualmachineinstances/status",
+ "clustervirtualmachineimages",
+ "clustervirtualmachineimages/status",
+ "clusterrolebindings",
+ "customresourcedefinitions":
+ limit = 32000
+ }
+ logger.Info(fmt.Sprintf("Request: Rewritten body: %s", logutil.HeadStringEx(req.Body, limit)))
+ }
+
+ if !targetReq.ShouldRewriteResponse() {
+ // Pass response as-is without rewriting.
+ logger.Info(fmt.Sprintf("RESPONSE PASS: Status %s, Headers %+v", resp.Status, resp.Header))
+ passResponse(w, resp, logger)
+ return
+ }
+
+ if targetReq.IsWatch() {
+ logger.Info(fmt.Sprintf("RESPONSE REWRITE STREAM Status %s, Headers %+v", resp.Status, resp.Header))
+
+ h.transformStream(targetReq, w, resp, logger)
+ return
+ }
+
+ // One-time rewrite is required for client or webhook requests.
+ logger.Info(fmt.Sprintf("RESPONSE REWRITE ONCE Status %s, Headers %+v", resp.Status, resp.Header))
+
+ h.transformResponse(targetReq, w, resp, logger)
+ return
+}
+
+func copyHeader(dst, src http.Header) {
+ for header, values := range src {
+ // Do not override dst header with the header from the src.
+ if len(dst.Values(header)) > 0 {
+ continue
+ }
+ for _, value := range values {
+ dst.Add(header, value)
+ }
+ }
+}
+
+func encodingAwareBodyReader(resp *http.Response) (io.ReadCloser, error) {
+ if resp == nil {
+ return nil, nil
+ }
+ body := resp.Body
+ if body == nil {
+ return nil, nil
+ }
+
+ var reader io.ReadCloser
+ var err error
+
+ encoding := resp.Header.Get("Content-Encoding")
+ switch encoding {
+ case "gzip":
+ reader, err = gzip.NewReader(body)
+ if err != nil {
+ return nil, fmt.Errorf("errorf making gzip reader: %v", err)
+ }
+ return io.NopCloser(reader), nil
+ case "deflate":
+ return flate.NewReader(body), nil
+ }
+
+ return body, nil
+}
+
+// transformRequest transforms request headers and rewrites request payload to use
+// request as client to the target.
+// TargetMode field defines either transformer should rename resources
+// if request is from the client, or restore resources if it is a call
+// from the API Server to the webhook.
+func (h *Handler) transformRequest(targetReq *rewriter.TargetRequest, req *http.Request) error {
+ if req == nil || req.URL == nil {
+ return fmt.Errorf("request to rewrite is empty")
+ }
+
+ // Rewrite incoming payload, e.g. create, put, etc.
+ if targetReq.ShouldRewriteRequest() && req.Body != nil {
+ // Read whole request body to rewrite.
+ bodyBytes, err := io.ReadAll(req.Body)
+ if err != nil {
+ return fmt.Errorf("read request body: %w", err)
+ }
+
+ var newBody []byte
+ switch req.Method {
+ case http.MethodPatch:
+ newBody, err = h.Rewriter.RewritePatch(targetReq, bodyBytes)
+ default:
+ newBody, err = h.Rewriter.RewriteJSONPayload(targetReq, bodyBytes, ToTargetAction(h.ProxyMode))
+ }
+ if err != nil {
+ return err
+ }
+
+ // Put new Body reader to req and fix Content-Length header.
+ newBodyLen := len(newBody)
+ if newBodyLen > 0 {
+ // Fix content-length if needed.
+ req.ContentLength = int64(newBodyLen)
+ if req.Header.Get("Content-Length") != "" {
+ req.Header.Set("Content-Length", strconv.Itoa(newBodyLen))
+ }
+ req.Body = io.NopCloser(bytes.NewBuffer(newBody))
+ }
+ }
+
+ if targetReq.ShouldRewriteResponse() {
+ // Rewriter not work with protobuf, force JSON
+ // in Accept header.
+ newAccept := make([]string, 0)
+ for _, hdr := range req.Header.Values("Accept") {
+ if strings.Contains(hdr, "application/vnd.kubernetes.protobuf") {
+ newAccept = append(newAccept, "application/json")
+ continue
+ }
+ // Quickly support kubectl with simple hack
+ if strings.Contains(hdr, "application/json") && strings.Contains(hdr, "as=Table") {
+ newAccept = append(newAccept, "application/json")
+ continue
+ }
+ newAccept = append(newAccept, hdr)
+ }
+ //req.Header.Set("Accept", newAccept)
+
+ //req.Header.Del("Accept")
+ req.Header["Accept"] = newAccept
+
+ // Force JSON for watches of core resources and CRDs.
+ if targetReq.IsWatch() && (targetReq.IsCRD() || targetReq.IsCore()) {
+ if len(req.Header.Values("Accept")) == 0 {
+ req.Header["Accept"] = []string{"application/json"}
+ }
+ }
+ }
+
+ // Set new endpoint path and query.
+ req.URL.Path = targetReq.Path()
+ req.URL.RawQuery = targetReq.RawQuery()
+
+ return nil
+}
+
+func passResponse(w http.ResponseWriter, resp *http.Response, logger *slog.Logger) {
+ copyHeader(w.Header(), resp.Header)
+ w.WriteHeader(resp.StatusCode)
+
+ if resp.StatusCode != http.StatusOK {
+ resp.Body = logutil.NewReaderLogger(resp.Body)
+ }
+
+ dst := &immediateWriter{dst: w}
+ _, err := io.Copy(dst, resp.Body)
+ if err != nil {
+ logger.Error(fmt.Sprintf("copy response: %v", err))
+ }
+
+ if logutil.HasData(resp.Body) {
+ limit := 1024
+ logger.Info(fmt.Sprintf("Pass through non 200 response: status %d %s", resp.StatusCode, logutil.HeadStringEx(resp.Body, limit)))
+ }
+
+ return
+}
+
+// transformResponse rewrites payloads in responses from the target.
+//
+// ProxyMode field defines either rewriter should restore, or rename resources.
+func (h *Handler) transformResponse(targetReq *rewriter.TargetRequest, w http.ResponseWriter, resp *http.Response, logger *slog.Logger) {
+ // Rewrite supports only json responses for now.
+ contentType := resp.Header.Get("Content-Type")
+ if !strings.HasPrefix(contentType, "application/json") {
+ logger.Error(fmt.Sprintf("Got non JSON message from target: Content-type=%s", contentType))
+ passResponse(w, resp, logger)
+ return
+ }
+
+ // Add gzip decoder if needed.
+ var err error
+ resp.Body, err = encodingAwareBodyReader(resp)
+ if err != nil {
+ logger.Error("Error decoding response body", logutil.SlogErr(err))
+ http.Error(w, "can't decode response body", http.StatusInternalServerError)
+ return
+ }
+
+ // Read response body to buffer. Wrap Body to log it later.
+ bodyReader := logutil.NewReaderLogger(resp.Body)
+ bodyBytes, err := io.ReadAll(bodyReader)
+ if err != nil {
+ logger.Error("Error reading response payload", logutil.SlogErr(err))
+ http.Error(w, "Error reading response payload", http.StatusBadGateway)
+ return
+ }
+
+ {
+ limit := 1024
+ logger.Info(fmt.Sprintf("Response: original body: [%d] %s", len(bodyBytes), logutil.HeadStringEx(bodyReader, limit)))
+ }
+
+ //return tr.Rewriter.RewriteJSONPayload(tr.TargetReq, bodyBytes, FromTargetAction(tr.ProxyMode))
+ //bodyBytes, err := h.Rewriter.RewriteFromTarget(targetReq, resp, logger)
+ bodyBytes, err = h.Rewriter.RewriteJSONPayload(targetReq, bodyBytes, FromTargetAction(h.ProxyMode))
+ if err != nil {
+ logger.Error("Error rewriting response", logutil.SlogErr(err))
+ http.Error(w, "can't rewrite response", http.StatusInternalServerError)
+ return
+ }
+
+ {
+ limit := 1024
+ if len(bodyBytes) < limit {
+ limit = len(bodyBytes)
+ }
+ logger.Info(fmt.Sprintf("Response: rewritten bytes: [%d] %s", len(bodyBytes), string(bodyBytes[:limit])))
+ }
+
+ copyHeader(w.Header(), resp.Header)
+ // Fix Content headers.
+ // bodyBytes are always decoded from gzip. Delete header to not break our client.
+ w.Header().Del("Content-Encoding")
+ if bodyBytes != nil {
+ w.Header().Set("Content-Length", strconv.Itoa(len(bodyBytes)))
+ }
+ w.WriteHeader(resp.StatusCode)
+
+ if bodyBytes != nil {
+ resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
+ _, err := io.Copy(w, resp.Body)
+ if err != nil {
+ logger.Error(fmt.Sprintf("error writing response from target: %v", err))
+ }
+ }
+
+ return
+}
+
+func (h *Handler) transformStream(targetReq *rewriter.TargetRequest, w http.ResponseWriter, resp *http.Response, logger *slog.Logger) {
+ copyHeader(w.Header(), resp.Header)
+ w.WriteHeader(resp.StatusCode)
+
+ // Start stream handler and lock ServeHTTP while proxying watch events stream.
+ wsr, err := NewStreamHandler(resp.Body, w, resp.Header.Get("Content-Type"), h.Rewriter, targetReq, logger)
+ if err != nil {
+ logger.Error("Error watching stream", logutil.SlogErr(err))
+ http.Error(w, fmt.Sprintf("watch stream: %v", err), http.StatusInternalServerError)
+ return
+ }
+ <-wsr.DoneChan()
+ return
+}
+
+type immediateWriter struct {
+ dst io.Writer
+}
+
+func (iw *immediateWriter) Write(p []byte) (n int, err error) {
+ n, err = iw.dst.Write(p)
+
+ if flusher, ok := iw.dst.(http.Flusher); ok {
+ flusher.Flush()
+ }
+
+ return
+}
diff --git a/images/kube-api-proxy/pkg/proxy/stream_handler.go b/images/kube-api-proxy/pkg/proxy/stream_handler.go
new file mode 100644
index 000000000..ec95dbf15
--- /dev/null
+++ b/images/kube-api-proxy/pkg/proxy/stream_handler.go
@@ -0,0 +1,219 @@
+package proxy
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ log "log/slog"
+ "mime"
+ "net/http"
+
+ "github.com/tidwall/gjson"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/serializer/streaming"
+ apiutilnet "k8s.io/apimachinery/pkg/util/net"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apimachinery/pkg/watch"
+ "k8s.io/client-go/kubernetes/scheme"
+
+ logutil "kube-api-proxy/pkg/log"
+ "kube-api-proxy/pkg/rewriter"
+)
+
+// StreamHandler reads a stream from the target, transforms events
+// and sends them to the client.
+type StreamHandler struct {
+ r io.ReadCloser
+ w io.Writer
+ rewriter *rewriter.RuleBasedRewriter
+ targetReq *rewriter.TargetRequest
+ decoder streaming.Decoder
+ done chan struct{}
+ log *log.Logger
+}
+
+// NewStreamHandler starts a go routine to pass rewritten Watch Events
+// from server to client.
+// Sources:
+// k8s.io/apimachinery@v0.26.1/pkg/watch/streamwatcher.go:100 receive method
+// k8s.io/kubernetes@v1.13.0/staging/src/k8s.io/client-go/rest/request.go:537 wrapperFn, create framer.
+// k8s.io/kubernetes@v1.13.0/staging/src/k8s.io/client-go/rest/request.go:598 instantiate watch NewDecoder
+func NewStreamHandler(r io.ReadCloser, w io.Writer, contentType string, rewriter *rewriter.RuleBasedRewriter, targetReq *rewriter.TargetRequest, logger *log.Logger) (*StreamHandler, error) {
+ reader := logutil.NewReaderLogger(r)
+ wsr := &StreamHandler{
+ r: reader,
+ w: w,
+ rewriter: rewriter,
+ targetReq: targetReq,
+ done: make(chan struct{}),
+ log: logger,
+ }
+ decoder, err := wsr.createWatchDecoder(contentType)
+ if err != nil {
+ return nil, err
+ }
+ wsr.decoder = decoder
+
+ // Start stream proxying.
+ go wsr.proxy()
+ return wsr, nil
+}
+
+// proxy reads result from the decoder in a loop, rewrites and writes to a client.
+// Sources
+// k8s.io/apimachinery@v0.26.1/pkg/watch/streamwatcher.go:100 receive method
+func (s *StreamHandler) proxy() {
+ defer utilruntime.HandleCrash()
+ defer s.Stop()
+ for {
+ // Read event from the server.
+ var got metav1.WatchEvent
+ s.log.Info("Start decode from stream")
+ res, _, err := s.decoder.Decode(nil, &got)
+ s.log.Info("Got decoded WatchEvent from stream")
+ if err != nil {
+ switch err {
+ case io.EOF:
+ // watch closed normally
+ s.log.Info("Catch EOF from target, stop proxying the stream")
+ case io.ErrUnexpectedEOF:
+ s.log.Error("Unexpected EOF during watch stream event decoding", logutil.SlogErr(err))
+ default:
+ if apiutilnet.IsProbableEOF(err) || apiutilnet.IsTimeout(err) {
+ s.log.Error("Unable to decode an event from the watch stream", logutil.SlogErr(err))
+ } else {
+ s.log.Error("Unable to decode an event from the watch stream", logutil.SlogErr(err))
+ //select {
+ //case <-sw.done:
+ //case sw.result <- Event{
+ // Type: Error,
+ // Object: sw.reporter.AsObject(fmt.Errorf("unable to decode an event from the watch stream: %v", err)),
+ //}:
+ //}
+ }
+ }
+ //s.log.Info("captured bytes from the stream", logutil.HeadStringEx(s.r, 65536))
+ return
+ }
+
+ if res != &got {
+ s.log.Error("unable to decode to metav1.Event")
+ continue
+ }
+
+ switch got.Type {
+ case string(watch.Added), string(watch.Modified), string(watch.Deleted), string(watch.Error), string(watch.Bookmark):
+ default:
+ s.log.Error(fmt.Sprintf("got invalid watch event type: %v", got.Type))
+ continue
+ }
+
+ {
+ group := gjson.GetBytes(got.Object.Raw, "apiVersion").String()
+ kind := gjson.GetBytes(got.Object.Raw, "kind").String()
+ name := gjson.GetBytes(got.Object.Raw, "metadata.name").String()
+ ns := gjson.GetBytes(got.Object.Raw, "metadata.namespace").String()
+ s.log.Info(fmt.Sprintf("Receive '%s' watch event with %s/%s %s/%s object", got.Type, group, kind, ns, name))
+ }
+
+ // TODO add pass-as-is for non rewritable objects.
+
+ // Restore object. Watch responses are always from the Kubernetes API server, no need to rename.
+ objBytes, err := s.rewriter.RewriteJSONPayload(s.targetReq, got.Object.Raw, rewriter.Restore)
+ //var objBytes []byte
+ //switch {
+ //case s.targetReq.IsCore():
+ // s.log.Info(fmt.Sprintf("Watch REWRITE CORE event '%s'", got.Type))
+ // objBytes, err = rewriter.RewriteOwnerReferences(s.rewriter.Rules, got.Object.Raw, rewriter.Restore)
+ //case s.targetReq.IsCRD():
+ // s.log.Info(fmt.Sprintf("Watch REWRITE CRD event '%s'", got.Type))
+ // objBytes, err = rewriter.RewriteCRDOrList(s.rewriter.Rules, got.Object.Raw, rewriter.Restore, s.targetReq.OrigGroup())
+ //case s.targetReq.OrigResourceType() != "":
+ // s.log.Info(fmt.Sprintf("Watch REWRITE event '%s'", got.Type))
+ // objBytes, err = rewriter.RestoreResource(s.rewriter.Rules, got.Object.Raw, s.targetReq.OrigGroup())
+ //default:
+ // objBytes = got.Object.Raw
+ //}
+ if err != nil {
+ s.log.Error(fmt.Sprintf("rewrite event '%s'", got.Type), logutil.SlogErr(err))
+ continue
+ }
+
+ // Write event to the client.
+ ev := metav1.WatchEvent{
+ Type: got.Type,
+ Object: runtime.RawExtension{
+ Raw: objBytes,
+ },
+ }
+ evBytes, err := json.Marshal(ev)
+ if err != nil {
+ s.log.Error("encode restored event to bytes", logutil.SlogErr(err))
+ continue
+ }
+ l := len(evBytes)
+ if l > 1300 {
+ l = 1300
+ }
+ s.log.Info(fmt.Sprintf("restored event: %s", string(evBytes)[0:l]))
+
+ _, err = io.Copy(s.w, io.NopCloser(bytes.NewBuffer(evBytes)))
+ if err != nil {
+ s.log.Error("write event", logutil.SlogErr(err))
+ }
+ // Flush to send buffered content to the client.
+ if wr, ok := s.w.(http.Flusher); ok {
+ wr.Flush()
+ }
+
+ // Check if application is stopped.
+ select {
+ case <-s.done:
+ return
+ default:
+ }
+ }
+}
+
+func (s *StreamHandler) Stop() {
+ select {
+ case <-s.done:
+ default:
+ close(s.done)
+ }
+}
+
+func (s *StreamHandler) DoneChan() chan struct{} {
+ return s.done
+}
+
+// createSerializers
+// Source
+// k8s.io/client-go@v0.26.1/rest/request.go:765 newStreamWatcher
+// k8s.io/apimachinery@v0.26.1/pkg/runtime/negotiate.go:70 StreamDecoder
+func (s *StreamHandler) createWatchDecoder(contentType string) (streaming.Decoder, error) {
+ mediaType, _, err := mime.ParseMediaType(contentType)
+ if err != nil {
+ s.log.Info("Unexpected media type from the server: %q: %v", contentType, err)
+ }
+
+ negotiatedSerializer := scheme.Codecs.WithoutConversion()
+ mediaTypes := negotiatedSerializer.SupportedMediaTypes()
+ info, ok := runtime.SerializerInfoForMediaType(mediaTypes, mediaType)
+ if !ok {
+ if len(contentType) != 0 || len(mediaTypes) == 0 {
+ return nil, fmt.Errorf("no matching serializer for media type '%s'", contentType)
+ }
+ info = mediaTypes[0]
+ }
+ if info.StreamSerializer == nil {
+ return nil, fmt.Errorf("no serializer for content type %s", contentType)
+ }
+
+ // A chain of the framer and the serializer will split body stream into JSON objects.
+ frameReader := info.StreamSerializer.Framer.NewFrameReader(s.r)
+ streamingDecoder := streaming.NewDecoder(frameReader, info.StreamSerializer.Serializer)
+ return streamingDecoder, nil
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/admission_configuration.go b/images/kube-api-proxy/pkg/rewriter/admission_configuration.go
new file mode 100644
index 000000000..881ae2837
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/admission_configuration.go
@@ -0,0 +1,46 @@
+package rewriter
+
+const (
+ ValidatingWebhookConfigurationKind = "ValidatingWebhookConfiguration"
+ ValidatingWebhookConfigurationListKind = "ValidatingWebhookConfigurationList"
+ MutatingWebhookConfigurationKind = "MutatingWebhookConfiguration"
+ MutatingWebhookConfigurationListKind = "MutatingWebhookConfigurationList"
+)
+
+func RewriteValidatingOrList(rules *RewriteRules, obj []byte, action Action) ([]byte, error) {
+ if action == Rename {
+ return RewriteResourceOrList(obj, ValidatingWebhookConfigurationListKind, func(singleObj []byte) ([]byte, error) {
+ return RewriteArray(singleObj, "webhooks", func(webhook []byte) ([]byte, error) {
+ return RewriteArray(webhook, "rules", func(item []byte) ([]byte, error) {
+ return renameRoleRule(rules, item)
+ })
+ })
+ })
+ }
+ return RewriteResourceOrList(obj, ValidatingWebhookConfigurationListKind, func(singleObj []byte) ([]byte, error) {
+ return RewriteArray(singleObj, "webhooks", func(webhook []byte) ([]byte, error) {
+ return RewriteArray(webhook, "rules", func(item []byte) ([]byte, error) {
+ return restoreRoleRule(rules, item)
+ })
+ })
+ })
+}
+
+func RewriteMutatingOrList(rules *RewriteRules, obj []byte, action Action) ([]byte, error) {
+ if action == Rename {
+ return RewriteResourceOrList(obj, MutatingWebhookConfigurationListKind, func(singleObj []byte) ([]byte, error) {
+ return RewriteArray(singleObj, "webhooks", func(webhook []byte) ([]byte, error) {
+ return RewriteArray(webhook, "rules", func(item []byte) ([]byte, error) {
+ return renameRoleRule(rules, item)
+ })
+ })
+ })
+ }
+ return RewriteResourceOrList(obj, MutatingWebhookConfigurationListKind, func(singleObj []byte) ([]byte, error) {
+ return RewriteArray(singleObj, "webhooks", func(webhook []byte) ([]byte, error) {
+ return RewriteArray(webhook, "rules", func(item []byte) ([]byte, error) {
+ return restoreRoleRule(rules, item)
+ })
+ })
+ })
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/admission_configuration_test.go b/images/kube-api-proxy/pkg/rewriter/admission_configuration_test.go
new file mode 100644
index 000000000..311627663
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/admission_configuration_test.go
@@ -0,0 +1,69 @@
+package rewriter
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestValidatingRename(t *testing.T) {
+ tests := []struct {
+ name string
+ manifest string
+ expect string
+ }{
+ {
+ "mixed resources",
+ `{"webhooks":[{"rules":[{"apiGroups":[""],"resources":["pods"]},{"apiGroups": ["original.group.io"], "resources": ["someresources"]}]}]}`,
+ `{"webhooks":[{"rules":[{"apiGroups":[""],"resources":["pods"]},{"apiGroups": ["prefixed.resources.group.io"], "resources": ["prefixedsomeresources"]}]}]}`,
+ },
+ {
+ "empty object",
+ `{}`,
+ `{}`,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rwr := createTestRewriter()
+
+ resBytes, err := RewriteValidatingOrList(rwr.Rules, []byte(tt.manifest), Rename)
+ require.NoError(t, err, "should rename validating webhook configuration")
+
+ actual := string(resBytes)
+ require.Equal(t, tt.expect, actual)
+ })
+ }
+}
+
+func TestValidatingRestore(t *testing.T) {
+ tests := []struct {
+ name string
+ manifest string
+ expect string
+ }{
+ {
+ "mixed resources",
+ `{"webhooks":[{"rules":[{"apiGroups":[""],"resources":["pods"]},{"apiGroups": ["prefixed.resources.group.io"], "resources": ["prefixedsomeresources"]}]}]}`,
+ `{"webhooks":[{"rules":[{"apiGroups":[""],"resources":["pods"]},{"apiGroups": ["original.group.io"], "resources": ["someresources"]}]}]}`,
+ },
+ {
+ "empty object",
+ `{}`,
+ `{}`,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rwr := createTestRewriter()
+
+ resBytes, err := RewriteValidatingOrList(rwr.Rules, []byte(tt.manifest), Restore)
+ require.NoError(t, err, "should rename validating webhook configuration")
+
+ actual := string(resBytes)
+ require.Equal(t, tt.expect, actual)
+ })
+ }
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/admission_review.go b/images/kube-api-proxy/pkg/rewriter/admission_review.go
new file mode 100644
index 000000000..4ed448f3a
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/admission_review.go
@@ -0,0 +1,154 @@
+package rewriter
+
+import (
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+// RewriteAdmissionReview rewrites AdmissionReview request and response.
+// NOTE: only one rewrite direction is supported for now:
+// - Restore object in AdmissionReview request.
+// - Do nothing for AdmissionReview response.
+func RewriteAdmissionReview(rules *RewriteRules, obj []byte, origGroup string) ([]byte, error) {
+ response := gjson.GetBytes(obj, "response")
+ if response.Exists() {
+ // TODO rewrite response with the Patch.
+ return obj, nil
+ }
+
+ request := gjson.GetBytes(obj, "request")
+ if request.Exists() {
+ newRequest, err := RestoreAdmissionReviewRequest(rules, []byte(request.Raw), origGroup)
+ if err != nil {
+ return nil, err
+ }
+ if len(newRequest) > 0 {
+ obj, err = sjson.SetRawBytes(obj, "request", newRequest)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return obj, nil
+}
+
+// RestoreAdmissionReviewRequest restores apiVersion, kind and other fields in an AdmissionReview request.
+// Only restoring is required, as AdmissionReview request only comes from API Server.
+func RestoreAdmissionReviewRequest(rules *RewriteRules, obj []byte, origGroup string) ([]byte, error) {
+ var err error
+
+ // Rewrite "resource" field and find rules.
+ {
+ resourceObj := gjson.GetBytes(obj, "resource")
+ group := resourceObj.Get("group")
+ resource := resourceObj.Get("resource")
+ // Ignore reviews for unknown renamed group.
+ if group.String() != rules.RenamedGroup {
+ return nil, nil
+ }
+ newResource := rules.RestoreResource(resource.String())
+ obj, err = sjson.SetBytes(obj, "resource.resource", newResource)
+ if err != nil {
+ return nil, err
+ }
+ obj, err = sjson.SetBytes(obj, "resource.group", origGroup)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Rewrite "requestResource" field.
+ {
+ fieldObj := gjson.GetBytes(obj, "requestResource")
+ group := fieldObj.Get("group")
+ resource := fieldObj.Get("resource")
+ // Ignore reviews for unknown renamed group.
+ if group.String() != rules.RenamedGroup {
+ return nil, nil
+ }
+ newResource := rules.RestoreResource(resource.String())
+ obj, err = sjson.SetBytes(obj, "requestResource.resource", newResource)
+ if err != nil {
+ return nil, err
+ }
+ obj, err = sjson.SetBytes(obj, "requestResource.group", origGroup)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Check "subresource" field. No need to rewrite kind, requestKind, object and oldObject fields if subresource is set.
+ {
+ fieldObj := gjson.GetBytes(obj, "subresource")
+ if fieldObj.Exists() && fieldObj.String() != "" {
+ return obj, err
+ }
+ }
+
+ // Rewrite "kind" field.
+ {
+ fieldObj := gjson.GetBytes(obj, "kind")
+ kind := fieldObj.Get("kind")
+ newKind := rules.RestoreKind(kind.String())
+ obj, err = sjson.SetBytes(obj, "kind.kind", newKind)
+ if err != nil {
+ return nil, err
+ }
+ obj, err = sjson.SetBytes(obj, "kind.group", origGroup)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Rewrite "requestKind" field.
+ {
+ fieldObj := gjson.GetBytes(obj, "requestKind")
+ kind := fieldObj.Get("kind")
+ newKind := rules.RestoreKind(kind.String())
+ obj, err = sjson.SetBytes(obj, "requestKind.kind", newKind)
+ if err != nil {
+ return nil, err
+ }
+ obj, err = sjson.SetBytes(obj, "requestKind.group", origGroup)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Rewrite "object" field.
+ {
+ fieldObj := gjson.GetBytes(obj, "object")
+ if fieldObj.Exists() {
+ newField, err := RestoreResource(rules, []byte(fieldObj.Raw), origGroup)
+ if err != nil {
+ return nil, err
+ }
+ if len(newField) > 0 {
+ obj, err = sjson.SetRawBytes(obj, "object", newField)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ // Rewrite "oldObject" field.
+ {
+ fieldObj := gjson.GetBytes(obj, "oldObject")
+ if fieldObj.Exists() {
+ newField, err := RestoreResource(rules, []byte(fieldObj.Raw), origGroup)
+ if err != nil {
+ return nil, err
+ }
+ if len(newField) > 0 {
+ obj, err = sjson.SetRawBytes(obj, "oldObject", newField)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ return obj, nil
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/api_endpoint.go b/images/kube-api-proxy/pkg/rewriter/api_endpoint.go
new file mode 100644
index 000000000..ea36ddb66
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/api_endpoint.go
@@ -0,0 +1,297 @@
+package rewriter
+
+import (
+ "net/url"
+ "strings"
+)
+
+type APIEndpoint struct {
+ // IsUknown indicates that path is unknown for rewriter and should be passed as is.
+ IsUnknown bool
+ RawPath string
+
+ IsRoot bool
+
+ Prefix string
+ IsCore bool
+
+ Group string
+ Version string
+ Namespace string
+ ResourceType string
+ Name string
+ Subresource string
+ Remainder []string
+
+ IsCRD bool
+ CRDResourceType string
+ CRDGroup string
+
+ IsWatch bool
+ RawQuery string
+}
+
+// Core resources:
+// - /api/VERSION/RESOURCETYPE
+// - /api/VERSION/RESOURCETYPE/NAME
+// - /api/VERSION/RESOURCETYPE/NAME/SUBRESOURCE
+// - /api/VERSION/namespaces/NAMESPACE/RESOURCETYPE
+// - /api/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME
+// - /api/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
+// - /api/VERSION/namespaces/NAME/SUBRESOURCE - RESOURCETYPE=namespaces
+//
+// Cluster scoped custom resource:
+// - /apis/GROUP/VERSION/RESOURCETYPE/NAME/SUBRESOURCE
+// | | | |
+// PrefixIdx | | |
+// GroupIDx -+ | |
+// VersionIDx -----+ |
+// ClusterResourceIdx -----+
+//
+// Namespaced custom resource:
+// - /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
+// - /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
+//
+// CRD (CRD is itself a cluster scoped custom resource):
+// - /apis/apiextensions.k8s.io/v1/customresourcedefinitions
+// - /apis/apiextensions.k8s.io/v1/customresourcedefinitions/RESOURCETYPE.GROUP
+
+const (
+ CorePrefix = "api"
+ APIsPrefix = "apis"
+
+ NamespacesPart = "namespaces"
+
+ CRDGroup = "apiextensions.k8s.io"
+ CRDResourceType = "customresourcedefinitions"
+
+ WatchClause = "watch=true"
+)
+
+// ParseAPIEndpoint breaks url path by parts.
+func ParseAPIEndpoint(apiURL *url.URL) *APIEndpoint {
+ rawPath := apiURL.Path
+ rawQuery := apiURL.RawQuery
+ isWatch := strings.Contains(rawQuery, WatchClause)
+
+ cleanedPath := strings.Trim(apiURL.Path, "/")
+ pathItems := strings.Split(cleanedPath, "/")
+
+ if len(pathItems) == 0 {
+ return &APIEndpoint{
+ IsRoot: true,
+ IsWatch: isWatch,
+ RawPath: rawPath,
+ RawQuery: rawQuery,
+ }
+ }
+
+ var ae *APIEndpoint
+ // PREFIX is the first item in path.
+ prefix := pathItems[0]
+ switch prefix {
+ case CorePrefix:
+ ae = parseCoreEndpoint(pathItems)
+ case APIsPrefix:
+ ae = parseAPIsEndpoint(pathItems)
+ }
+
+ if ae == nil {
+ return &APIEndpoint{
+ IsUnknown: true,
+ RawPath: rawPath,
+ RawQuery: rawQuery,
+ }
+ }
+
+ ae.IsWatch = isWatch
+ ae.RawPath = rawPath
+ ae.RawQuery = rawQuery
+ return ae
+}
+
+func parseCoreEndpoint(pathItems []string) *APIEndpoint {
+ var isLast bool
+ var ae APIEndpoint
+ ae.IsCore = true
+
+ // /api
+ ae.Prefix, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // /api/VERSION
+ ae.Version, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // /api/VERSION/RESOURCETYPE
+ ae.ResourceType, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // /api/VERSION/RESOURCETYPE/NAME
+ ae.Name, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // /api/VERSION/RESOURCETYPE/NAME/SUBRESOURCE
+ // /api/VERSION/namespaces/NAMESPACE/status
+ // /api/VERSION/namespaces/NAMESPACE/RESOURCETYPE
+ ae.Subresource, isLast = Shift(&pathItems)
+ if ae.ResourceType == NamespacesPart && ae.Subresource != "status" {
+ // It is a namespaced resource, we got ns name and resourcetype in name and subresource.
+ ae.Namespace = ae.Name
+ ae.ResourceType = ae.Subresource
+ ae.Name = ""
+ ae.Subresource = ""
+ }
+ // Stop if no items available.
+ if isLast {
+ return &ae
+ }
+
+ // /api/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME
+ ae.Name, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+ // /api/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
+ ae.Subresource, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // Save remaining items if any.
+ ae.Remainder = pathItems
+ return &ae
+}
+
+func parseAPIsEndpoint(pathItems []string) *APIEndpoint {
+ var ae APIEndpoint
+ var isLast bool
+
+ // /apis
+ ae.Prefix, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // /apis/GROUP
+ ae.Group, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // /apis/GROUP/VERSION
+ ae.Version, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // /apis/GROUP/VERSION/RESOURCETYPE
+ ae.ResourceType, isLast = Shift(&pathItems)
+ // /apis/apiextensions.k8s.io/VERSION/customresourcedefinitions
+ if ae.Group == CRDGroup && ae.ResourceType == CRDResourceType {
+ ae.IsCRD = true
+ }
+ if isLast {
+ return &ae
+ }
+
+ // /apis/GROUP/VERSION/RESOURCETYPE/NAME
+ ae.Name, isLast = Shift(&pathItems)
+ if ae.IsCRD {
+ ae.CRDResourceType, ae.CRDGroup, _ = strings.Cut(ae.Name, ".")
+ }
+ if isLast {
+ return &ae
+ }
+
+ // /apis/GROUP/VERSION/RESOURCETYPE/NAME/SUBRESOURCE
+ // /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE
+ ae.Subresource, isLast = Shift(&pathItems)
+ if ae.ResourceType == NamespacesPart {
+ // It is a namespaced resource, we got ns name and resourcetype in name and subresource.
+ ae.Namespace = ae.Name
+ ae.ResourceType = ae.Subresource
+ ae.Name = ""
+ ae.Subresource = ""
+ }
+ // Stop if no items available.
+ if isLast {
+ return &ae
+ }
+
+ // /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME
+ ae.Name, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+ // /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
+ ae.Subresource, isLast = Shift(&pathItems)
+ if isLast {
+ return &ae
+ }
+
+ // Save remaining items if any.
+ ae.Remainder = pathItems
+ return &ae
+}
+
+func (a *APIEndpoint) Clone() *APIEndpoint {
+ clone := *a
+ return &clone
+}
+
+func (a *APIEndpoint) Path() string {
+ if a.IsRoot || a.IsCore || a.IsUnknown {
+ return a.RawPath
+ }
+
+ ns := ""
+ if a.Namespace != "" {
+ ns = NamespacesPart + "/" + a.Namespace
+ }
+ var parts []string
+ parts = []string{
+ a.Prefix,
+ a.Group,
+ a.Version,
+ ns,
+ a.ResourceType,
+ a.Name,
+ a.Subresource,
+ }
+ if len(a.Remainder) > 0 {
+ parts = append(parts, a.Remainder...)
+ }
+
+ nonEmptyParts := make([]string, 0)
+ for _, part := range parts {
+ if part != "" {
+ nonEmptyParts = append(nonEmptyParts, part)
+ }
+ }
+
+ return "/" + strings.Join(nonEmptyParts, "/")
+}
+
+// Shift deletes the first item from the array and returns it.
+func Shift(items *[]string) (string, bool) {
+ if len(*items) == 0 {
+ return "", true
+ }
+
+ first := (*items)[0]
+ if len(*items) == 1 {
+ *items = []string{}
+ } else {
+ *items = (*items)[1:]
+ }
+ return first, len(*items) == 0
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/api_endpoint_test.go b/images/kube-api-proxy/pkg/rewriter/api_endpoint_test.go
new file mode 100644
index 000000000..a91d7901a
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/api_endpoint_test.go
@@ -0,0 +1,276 @@
+package rewriter
+
+import (
+ "net/url"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestParseAPIEndpoint(t *testing.T) {
+
+ tests := []struct {
+ name string
+ path string
+ expect *APIEndpoint
+ }{
+ {
+ "root",
+ "/",
+ &APIEndpoint{
+ IsRoot: true,
+ },
+ },
+
+ // Core resources.
+ {
+ "core apiversions",
+ "/api",
+ &APIEndpoint{
+ IsCore: true,
+ Prefix: CorePrefix,
+ },
+ },
+ {
+ "core apiresourcelist",
+ "/api/v1",
+ &APIEndpoint{
+ IsCore: true,
+ Prefix: CorePrefix,
+ Version: "v1",
+ },
+ },
+ {
+ "core deploymentlist",
+ "/api/v1/deployments",
+ &APIEndpoint{
+ IsCore: true,
+ Prefix: CorePrefix,
+ Version: "v1",
+ ResourceType: "deployments",
+ },
+ },
+ {
+ "core deployment dy name",
+ "/api/v1/deployments/deployname",
+ &APIEndpoint{
+ IsCore: true,
+ Prefix: CorePrefix,
+ Version: "v1",
+ ResourceType: "deployments",
+ Name: "deployname",
+ },
+ },
+ {
+ "core deployment status",
+ "/api/v1/deployments/deployname/status",
+ &APIEndpoint{
+ IsCore: true,
+ Prefix: CorePrefix,
+ Version: "v1",
+ ResourceType: "deployments",
+ Name: "deployname",
+ Subresource: "status",
+ },
+ },
+ {
+ "core deployments in nsname",
+ "/api/v1/namespaces/nsname/deployments",
+ &APIEndpoint{
+ IsCore: true,
+ Prefix: CorePrefix,
+ Version: "v1",
+ ResourceType: "deployments",
+ Namespace: "nsname",
+ },
+ },
+ {
+ "core deployment in nsname by name",
+ "/api/v1/namespaces/nsname/deployments/deployname",
+ &APIEndpoint{
+ IsCore: true,
+ Prefix: CorePrefix,
+ Version: "v1",
+ ResourceType: "deployments",
+ Namespace: "nsname",
+ Name: "deployname",
+ },
+ },
+ {
+ "core deployment status in nsname",
+ "/api/v1/namespaces/nsname/deployments/deployname/status",
+ &APIEndpoint{
+ IsCore: true,
+ Prefix: CorePrefix,
+ Version: "v1",
+ ResourceType: "deployments",
+ Namespace: "nsname",
+ Name: "deployname",
+ Subresource: "status",
+ },
+ },
+
+ // Custom resources.
+ {
+ "apigrouplist",
+ "/apis",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ },
+ },
+ {
+ "apigroup",
+ "/apis/group.io",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ Group: "group.io",
+ },
+ },
+ {
+ "apiresourcelist",
+ "/apis/group.io/v1",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ Group: "group.io",
+ Version: "v1",
+ },
+ },
+ {
+ "someresourceslist",
+ "/apis/group.io/v1/someresources",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ Group: "group.io",
+ Version: "v1",
+ ResourceType: "someresources",
+ },
+ },
+ {
+ "someresource by name",
+ "/apis/group.io/v1/someresources/srname",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ Group: "group.io",
+ Version: "v1",
+ ResourceType: "someresources",
+ Name: "srname",
+ },
+ },
+ {
+ "someresource status",
+ "/apis/group.io/v1/someresources/srname/status",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ Group: "group.io",
+ Version: "v1",
+ ResourceType: "someresources",
+ Name: "srname",
+ Subresource: "status",
+ },
+ },
+ {
+ "someresources in nsname",
+ "/apis/group.io/v1/namespaces/nsname/someresources",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ Group: "group.io",
+ Version: "v1",
+ Namespace: "nsname",
+ ResourceType: "someresources",
+ },
+ },
+ {
+ "someresource in nsname by name",
+ "/apis/group.io/v1/namespaces/nsname/someresources/srname",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ Group: "group.io",
+ Version: "v1",
+ Namespace: "nsname",
+ ResourceType: "someresources",
+ Name: "srname",
+ },
+ },
+ {
+ "someresource status in nsname",
+ "/apis/group.io/v1/namespaces/nsname/someresources/srname/status",
+ &APIEndpoint{
+ Prefix: APIsPrefix,
+ Group: "group.io",
+ Version: "v1",
+ Namespace: "nsname",
+ ResourceType: "someresources",
+ Name: "srname",
+ Subresource: "status",
+ },
+ },
+
+ // CRDs
+ {
+ "crd list",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions",
+ &APIEndpoint{
+ IsCRD: true,
+ Prefix: APIsPrefix,
+ Group: "apiextensions.k8s.io",
+ Version: "v1",
+ ResourceType: "customresourcedefinitions",
+ },
+ },
+ {
+ "crd by name",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/crname",
+ &APIEndpoint{
+ IsCRD: true,
+ Prefix: APIsPrefix,
+ Group: "apiextensions.k8s.io",
+ Version: "v1",
+ ResourceType: "customresourcedefinitions",
+ Name: "crname",
+ },
+ },
+ {
+ "crd status",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/crname/status",
+ &APIEndpoint{
+ IsCRD: true,
+ Prefix: APIsPrefix,
+ Group: "apiextensions.k8s.io",
+ Version: "v1",
+ ResourceType: "customresourcedefinitions",
+ Name: "crname",
+ Subresource: "status",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ u, err := url.Parse(tt.path)
+ require.NoError(t, err, "should parse path '%s'", tt.path)
+
+ actual := ParseAPIEndpoint(u)
+ if tt.expect == nil {
+ require.Nil(t, actual, "expect not parse path '%s', got non-empty %+v", tt.path, actual)
+ }
+
+ if tt.expect != nil {
+ require.NotNil(t, actual, "expect parse path '%s' to %+v, got nil", tt.path, tt.expect)
+
+ // Flags.
+ require.Equal(t, tt.expect.IsRoot, actual.IsRoot, "IsRoot")
+ require.Equal(t, tt.expect.IsCore, actual.IsCore, "IsCore")
+ require.Equal(t, tt.expect.IsCRD, actual.IsCRD, "IsCRD")
+
+ // Parts.
+ require.Equal(t, tt.expect.Prefix, actual.Prefix, "Prefix")
+ require.Equal(t, tt.expect.Group, actual.Group, "Group")
+ require.Equal(t, tt.expect.Version, actual.Version, "Version")
+ require.Equal(t, tt.expect.ResourceType, actual.ResourceType, "ResourceType")
+ require.Equal(t, tt.expect.Name, actual.Name, "Name")
+ require.Equal(t, tt.expect.Subresource, actual.Subresource, "Subresource")
+ require.Equal(t, tt.expect.Namespace, actual.Namespace, "Namespace")
+ }
+ })
+ }
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/crd.go b/images/kube-api-proxy/pkg/rewriter/crd.go
new file mode 100644
index 000000000..2c75bdbd1
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/crd.go
@@ -0,0 +1,242 @@
+package rewriter
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+const (
+ CRDKind = "CustomResourceDefinition"
+ CRDListKind = "CustomResourceDefinitionList"
+)
+
+func RewriteCRDOrList(rules *RewriteRules, obj []byte, action Action) ([]byte, error) {
+ // CREATE, UPDATE, or PATCH requests.
+ if action == Rename {
+ return RewriteResourceOrList(obj, CRDListKind, func(singleObj []byte) ([]byte, error) {
+ return RenameCRD(rules, singleObj)
+ })
+ }
+
+ // Responses of GET, LIST, DELETE requests. Also, rewrite in watch events.
+ return RewriteResourceOrList(obj, CRDListKind, func(singleObj []byte) ([]byte, error) {
+ return RestoreCRD(rules, singleObj)
+ })
+}
+
+// RestoreCRD restores fields in CRD to original.
+//
+// Example:
+// .metadata.name prefixedvirtualmachines.x.virtualization.deckhouse.io -> virtualmachines.kubevirt.io
+// .spec.group x.virtualization.deckhouse.io -> kubevirt.io
+// .spec.names
+//
+// categories kubevirt -> all
+// kind PrefixedVirtualMachines -> VirtualMachine
+// listKind PrefixedVirtualMachineList -> VirtualMachineList
+// plural prefixedvirtualmachines -> virtualmachines
+// singular prefixedvirtualmachine -> virtualmachine
+// shortNames [xvm xvms] -> [vm vms]
+func RestoreCRD(rules *RewriteRules, obj []byte) ([]byte, error) {
+ crdName := gjson.GetBytes(obj, "metadata.name").String()
+ resource, group, found := strings.Cut(crdName, ".")
+ if !found {
+ return nil, fmt.Errorf("malformed CRD name: should be resourcetype.group, got %s", crdName)
+ }
+ // Do not restore CRDs in unknown groups.
+ if group != rules.RenamedGroup {
+ return nil, nil
+ }
+
+ origResource := rules.RestoreResource(resource)
+
+ groupRule, resourceRule := rules.GroupResourceRules(origResource)
+ if resourceRule == nil {
+ return nil, nil
+ }
+
+ newName := resourceRule.Plural + "." + groupRule.Group
+ obj, err := sjson.SetBytes(obj, "metadata.name", newName)
+ if err != nil {
+ return nil, err
+ }
+
+ obj, err = sjson.SetBytes(obj, "spec.group", groupRule.Group)
+ if err != nil {
+ return nil, err
+ }
+
+ names := []byte(gjson.GetBytes(obj, "spec.names").Raw)
+
+ names, err = sjson.SetBytes(names, "categories", rules.RestoreCategories(resourceRule))
+ if err != nil {
+ return nil, err
+ }
+ names, err = sjson.SetBytes(names, "kind", rules.RestoreKind(resourceRule.Kind))
+ if err != nil {
+ return nil, err
+ }
+ names, err = sjson.SetBytes(names, "listKind", rules.RestoreKind(resourceRule.ListKind))
+ if err != nil {
+ return nil, err
+ }
+ names, err = sjson.SetBytes(names, "plural", rules.RestoreResource(resourceRule.Plural))
+ if err != nil {
+ return nil, err
+ }
+ names, err = sjson.SetBytes(names, "singular", rules.RestoreResource(resourceRule.Singular))
+ if err != nil {
+ return nil, err
+ }
+ names, err = sjson.SetBytes(names, "shortNames", rules.RestoreShortNames(resourceRule.ShortNames))
+ if err != nil {
+ return nil, err
+ }
+
+ obj, err = sjson.SetRawBytes(obj, "spec.names", names)
+ if err != nil {
+ return nil, err
+ }
+
+ return obj, nil
+}
+
+// RenameCRD renames fields in CRD.
+//
+// Example:
+// .metadata.name virtualmachines.kubevirt.io -> prefixedvirtualmachines.x.virtualization.deckhouse.io
+// .spec.group kubevirt.io -> x.virtualization.deckhouse.io
+// .spec.names
+//
+// categories all -> kubevirt
+// kind VirtualMachine -> PrefixedVirtualMachines
+// listKind VirtualMachineList -> PrefixedVirtualMachineList
+// plural virtualmachines -> prefixedvirtualmachines
+// singular virtualmachine -> prefixedvirtualmachine
+// shortNames [vm vms] -> [xvm xvms]
+func RenameCRD(rules *RewriteRules, obj []byte) ([]byte, error) {
+ crdName := gjson.GetBytes(obj, "metadata.name").String()
+ resource, group, found := strings.Cut(crdName, ".")
+ if !found {
+ return nil, fmt.Errorf("malformed CRD name: should be resourcetype.group, got %s", crdName)
+ }
+
+ _, resourceRule := rules.ResourceRules(group, resource)
+ if resourceRule == nil {
+ return nil, nil
+ }
+
+ newName := rules.RenameResource(resource) + "." + rules.RenamedGroup
+ obj, err := sjson.SetBytes(obj, "metadata.name", newName)
+ if err != nil {
+ return nil, err
+ }
+
+ spec := gjson.GetBytes(obj, "spec")
+ newSpec, err := renameCRDSpec(rules, resourceRule, []byte(spec.Raw))
+ if err != nil {
+ return nil, err
+ }
+ return sjson.SetRawBytes(obj, "spec", newSpec)
+}
+
+func renameCRDSpec(rules *RewriteRules, resourceRule *ResourceRule, spec []byte) ([]byte, error) {
+ var err error
+
+ spec, err = sjson.SetBytes(spec, "group", rules.RenamedGroup)
+ if err != nil {
+ return nil, err
+ }
+
+ // Rename fields in the 'names' object.
+ names := []byte(gjson.GetBytes(spec, "names").Raw)
+
+ if gjson.GetBytes(names, "categories").Exists() {
+ names, err = sjson.SetBytes(names, "categories", rules.RenameCategories(resourceRule.Categories))
+ if err != nil {
+ return nil, err
+ }
+ }
+ if gjson.GetBytes(names, "kind").Exists() {
+ names, err = sjson.SetBytes(names, "kind", rules.RenameKind(resourceRule.Kind))
+ if err != nil {
+ return nil, err
+ }
+ }
+ if gjson.GetBytes(names, "listKind").Exists() {
+ names, err = sjson.SetBytes(names, "listKind", rules.RenameKind(resourceRule.ListKind))
+ if err != nil {
+ return nil, err
+ }
+ }
+ if gjson.GetBytes(names, "plural").Exists() {
+ names, err = sjson.SetBytes(names, "plural", rules.RenameResource(resourceRule.Plural))
+ if err != nil {
+ return nil, err
+ }
+ }
+ if gjson.GetBytes(names, "singular").Exists() {
+ names, err = sjson.SetBytes(names, "singular", rules.RenameResource(resourceRule.Singular))
+ if err != nil {
+ return nil, err
+ }
+ }
+ if gjson.GetBytes(names, "shortNames").Exists() {
+ names, err = sjson.SetBytes(names, "shortNames", rules.RenameShortNames(resourceRule.ShortNames))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ spec, err = sjson.SetRawBytes(spec, "names", names)
+ if err != nil {
+ return nil, err
+ }
+
+ return spec, nil
+}
+
+func RenameCRDPatch(rules *RewriteRules, resourceRule *ResourceRule, obj []byte) ([]byte, error) {
+ var err error
+
+ patches := gjson.ParseBytes(obj).Array()
+ if len(patches) == 0 {
+ return obj, nil
+ }
+
+ newPatches := []byte(`[]`)
+ isRenamed := false
+ for _, patch := range patches {
+ newPatch := []byte(patch.Raw)
+
+ op := gjson.GetBytes(newPatch, "op").String()
+ path := gjson.GetBytes(newPatch, "path").String()
+
+ if (op == "replace" || op == "add") && path == "/spec" {
+ isRenamed = true
+ value := []byte(gjson.GetBytes(newPatch, "value").Raw)
+ newValue, err := renameCRDSpec(rules, resourceRule, value)
+ if err != nil {
+ return nil, err
+ }
+ newPatch, err = sjson.SetRawBytes(newPatch, "value", newValue)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ newPatches, err = sjson.SetRawBytes(newPatches, "-1", newPatch)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if !isRenamed {
+ return obj, nil
+ }
+
+ return newPatches, nil
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/crd_test.go b/images/kube-api-proxy/pkg/rewriter/crd_test.go
new file mode 100644
index 000000000..e2691d826
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/crd_test.go
@@ -0,0 +1,319 @@
+package rewriter
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "net/http"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "github.com/tidwall/gjson"
+)
+
+func createRewriterForCRDTest() *RuleBasedRewriter {
+ apiGroupRules := map[string]APIGroupRule{
+ "original.group.io": {
+ GroupRule: GroupRule{
+ Group: "original.group.io",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ "someresources": {
+ Kind: "SomeResource",
+ ListKind: "SomeResourceList",
+ Plural: "someresources",
+ Singular: "someresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"sr", "srs"},
+ },
+ "anotherresources": {
+ Kind: "AnotherResource",
+ ListKind: "AnotherResourceList",
+ Plural: "anotherresources",
+ Singular: "anotherresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ ShortNames: []string{"ar"},
+ },
+ },
+ },
+ "other.group.io": {
+ GroupRule: GroupRule{
+ Group: "original.group.io",
+ Versions: []string{"v2alpha3"},
+ PreferredVersion: "v2alpha3",
+ },
+ ResourceRules: map[string]ResourceRule{
+ "otherresources": {
+ Kind: "OtherResource",
+ ListKind: "OtherResourceList",
+ Plural: "otherresources",
+ Singular: "otherresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ ShortNames: []string{"or"},
+ },
+ },
+ },
+ }
+
+ rwRules := &RewriteRules{
+ KindPrefix: "Prefixed", // KV
+ ResourceTypePrefix: "prefixed", // kv
+ ShortNamePrefix: "p",
+ Categories: []string{"prefixed"},
+ RenamedGroup: "prefixed.resources.group.io",
+ Rules: apiGroupRules,
+ }
+
+ return &RuleBasedRewriter{
+ Rules: rwRules,
+ }
+}
+
+// TestCRDRename - rename of a single CRD.
+func TestCRDRename(t *testing.T) {
+ origGroup := "original.group.io"
+ reqBody := `{
+"apiVersion": "apiextensions.k8s.io/v1",
+"kind": "CustomResourceDefinition",
+"metadata": {
+ "name":"someresources.original.group.io"
+}
+"spec": {
+ "group": "original.group.io",
+ "names": {
+ "kind": "SomeResource",
+ "listKind": "SomeResourceList",
+ "plural": "someresources",
+ "singular": "someresource",
+ "shortNames": ["sr"],
+ "categories": ["all"]
+ },
+ "scope":"Namespaced",
+ "versions": {}
+}
+}`
+ rwr := createRewriterForCRDTest()
+ testCRDRules := rwr.Rules
+
+ restored, err := RewriteCRDOrList(testCRDRules, []byte(reqBody), Rename)
+ if err != nil {
+ t.Fatalf("should rename CRD without error: %v", err)
+ }
+ if restored == nil {
+ t.Fatalf("should rename CRD: %v", err)
+ }
+
+ resRule := testCRDRules.Rules[origGroup].ResourceRules["someresources"]
+
+ tests := []struct {
+ path string
+ expected string
+ }{
+ {"metadata.name", testCRDRules.RenameResource(resRule.Plural) + "." + testCRDRules.RenamedGroup},
+ {"spec.group", testCRDRules.RenamedGroup},
+ {"spec.names.kind", testCRDRules.RenameKind(resRule.Kind)},
+ {"spec.names.listKind", testCRDRules.RenameKind(resRule.ListKind)},
+ {"spec.names.plural", testCRDRules.RenameResource(resRule.Plural)},
+ {"spec.names.singular", testCRDRules.RenameResource(resRule.Singular)},
+ {"spec.names.shortNames", `["psr","psrs"]`},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.path, func(t *testing.T) {
+ actual := gjson.GetBytes(restored, tt.path).String()
+ if actual != tt.expected {
+ t.Fatalf("%s value should be %s, got %s", tt.path, tt.expected, actual)
+ }
+ })
+ }
+}
+
+// TestCRDPatch tests renaming /spec in a CRD patch.
+func TestCRDPatch(t *testing.T) {
+ patches := `[{ "op": "add", "path": "/metadata/ownerReferences", "value": null },
+{ "op": "replace", "path": "/spec", "value": {
+"group":"original.group.io",
+"names":{"plural":"someresources","singular":"someresource","shortNames":["sr","srs"],"kind":"SomeResource","categories":["all"]},
+"scope":"Namespaced","versions":[{"name":"v1alpha1","schema":{}}]
+} }
+]`
+ patches = strings.ReplaceAll(patches, "\n", "")
+
+ expect := `[{ "op": "add", "path": "/metadata/ownerReferences", "value": null },
+{ "op": "replace", "path": "/spec", "value": {
+"group":"prefixed.resources.group.io",
+"names":{"plural":"prefixedsomeresources","singular":"prefixedsomeresource","shortNames":["psr","psrs"],"kind":"PrefixedSomeResource","categories":["prefixed"]},
+"scope":"Namespaced","versions":[{"name":"v1alpha1","schema":{}}]
+} }
+]`
+ expect = strings.ReplaceAll(expect, "\n", "")
+
+ rwr := createRewriterForCRDTest()
+ _, resRule := rwr.Rules.ResourceRules("original.group.io", "someresources")
+ require.NotNil(t, resRule, "should get resource rule for hardcoded group and resourceType")
+
+ resBytes, err := RenameCRDPatch(rwr.Rules, resRule, []byte(patches))
+ require.NoError(t, err, "should rename CRD patch")
+
+ actual := string(resBytes)
+ require.Equal(t, expect, actual)
+}
+
+// TestCRDRestore test restoring of a single CRD.
+func TestCRDRestore(t *testing.T) {
+ crdHTTPRequest := `GET /apis/apiextensions.k8s.io/v1/customresourcedefinitions/someresources.original.group.io HTTP/1.1
+Host: 127.0.0.1
+
+`
+ origGroup := "original.group.io"
+ crdPayload := `{
+"apiVersion": "apiextensions.k8s.io/v1",
+"kind": "CustomResourceDefinition",
+"metadata": {
+ "name":"prefixedsomeresources.prefixed.resources.group.io"
+}
+"spec": {
+ "group": "prefixed.resources.group.io",
+ "names": {
+ "kind": "PrefixedSomeResource",
+ "listKind": "PrefixedSomeResourceList",
+ "plural": "prefixedsomeresources",
+ "singular": "prefixedsomeresource",
+ "shortNames": ["psr"],
+ "categories": ["prefixed"]
+ },
+ "scope":"Namespaced",
+ "versions": {}
+}
+}`
+
+ req, err := http.ReadRequest(bufio.NewReader(bytes.NewBufferString(crdHTTPRequest)))
+ require.NoError(t, err, "should parse hardcoded http request")
+ require.NotNil(t, req.URL, "should parse url in hardcoded http request")
+
+ rwr := createRewriterForCRDTest()
+ targetReq := NewTargetRequest(rwr, req)
+ require.NotNil(t, targetReq, "should get TargetRequest")
+ require.Equal(t, origGroup, targetReq.OrigGroup(), "should set proper orig group")
+
+ resultBytes, err := rwr.RewriteJSONPayload(targetReq, []byte(crdPayload), Restore) // RewriteCRDOrList(crdPayload, []byte(reqBody), Restore, origGroup)
+ if err != nil {
+ t.Fatalf("should restore CRD without error: %v", err)
+ }
+ if resultBytes == nil {
+ t.Fatalf("should restore CRD: %v", err)
+ }
+
+ resRule := rwr.Rules.Rules[origGroup].ResourceRules["someresources"]
+
+ tests := []struct {
+ path string
+ expected string
+ }{
+ {"metadata.name", resRule.Plural + "." + origGroup},
+ {"spec.group", origGroup},
+ {"spec.names.kind", resRule.Kind},
+ {"spec.names.listKind", resRule.ListKind},
+ {"spec.names.plural", resRule.Plural},
+ {"spec.names.singular", resRule.Singular},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.path, func(t *testing.T) {
+ actual := gjson.GetBytes(resultBytes, tt.path).String()
+ if actual != tt.expected {
+ t.Fatalf("%s value should be %s, got %s", tt.path, tt.expected, actual)
+ }
+ })
+ }
+}
+
+func TestCRDPathRewrite(t *testing.T) {
+ tests := []struct {
+ name string
+ urlPath string
+ expected string
+ origGroup string
+ origResourceType string
+ }{
+ {
+ "crd with rule",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/someresources.original.group.io",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/prefixedsomeresources.prefixed.resources.group.io",
+ "original.group.io",
+ "someresources",
+ },
+ {
+ "crd watch by name",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions?fieldSelector=metadata.name%3Dsomeresources.original.group.io&resourceVersion=0&watch=true",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions?fieldSelector=metadata.name%3Dprefixedsomeresources.prefixed.resources.group.io&resourceVersion=0&watch=true",
+ "",
+ "",
+ },
+ {
+ "unknown crd watch by name",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions?fieldSelector=metadata.name%3Dresource.unknown.group.io&resourceVersion=0&watch=true",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions?fieldSelector=metadata.name%3Dresource.unknown.group.io&resourceVersion=0&watch=true",
+ "",
+ "",
+ },
+ {
+ "crd without rule",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/unknown.group.io",
+ "",
+ "",
+ "",
+ },
+ {
+ "crd list",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions",
+ "",
+ "",
+ "",
+ },
+ {
+ "non crd apiextension",
+ "/apis/apiextensions.k8s.io/v1/unknown",
+ "",
+ "",
+ "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ httpReqHead := fmt.Sprintf(`GET %s HTTP/1.1`, tt.urlPath)
+ httpReq := httpReqHead + "\n" + "Host: 127.0.0.1\n\n"
+ req, err := http.ReadRequest(bufio.NewReader(bytes.NewBufferString(httpReq)))
+ require.NoError(t, err, "should parse hardcoded http request")
+ require.NotNil(t, req.URL, "should parse url in hardcoded http request")
+
+ rwr := createRewriterForCRDTest()
+ targetReq := NewTargetRequest(rwr, req)
+ require.NotNil(t, targetReq, "should get TargetRequest")
+
+ if tt.expected == "" {
+ require.Equal(t, tt.urlPath, targetReq.Path(), "should not rewrite api endpoint path")
+ return
+ }
+
+ if tt.origGroup != "" {
+ require.Equal(t, tt.origGroup, targetReq.OrigGroup())
+ }
+
+ actual := targetReq.Path()
+ if targetReq.RawQuery() != "" {
+ actual += "?" + targetReq.RawQuery()
+ }
+
+ require.Equal(t, tt.expected, actual, "should rewrite api endpoint path")
+ })
+ }
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/discovery.go b/images/kube-api-proxy/pkg/rewriter/discovery.go
new file mode 100644
index 000000000..f346bcc69
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/discovery.go
@@ -0,0 +1,259 @@
+package rewriter
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+ "k8s.io/apimachinery/pkg/runtime"
+)
+
+// RewriteAPIGroupList restores groups and kinds in /apis/ response.
+//
+// Rewrite each APIGroup in "groups".
+// Response example:
+// {"name":"x.virtualization.deckhouse.io",
+//
+// "versions":[
+// {"groupVersion":"x.virtualization.deckhouse.io/v1","version":"v1"},
+// {"groupVersion":"x.virtualization.deckhouse.io/v1beta1","version":"v1beta1"},
+// {"groupVersion":"x.virtualization.deckhouse.io/v1alpha3","version":"v1alpha3"},
+// {"groupVersion":"x.virtualization.deckhouse.io/v1alpha2","version":"v1alpha2"},
+// {"groupVersion":"x.virtualization.deckhouse.io/v1alpha1","version":"v1alpha1"}
+// ],
+// "preferredVersion":{"groupVersion":"x.virtualization.deckhouse.io/v1","version":"v1"}
+// }
+func RewriteAPIGroupList(rules *RewriteRules, objBytes []byte) ([]byte, error) {
+ groups := gjson.GetBytes(objBytes, "groups").Array()
+ // TODO get rid of RawExtension, use SetRawBytes.
+ rwrGroups := make([]interface{}, 0)
+ for _, group := range groups {
+ groupName := gjson.Get(group.Raw, "name").String()
+ // Replace renamed group with groups from rules.
+ if groupName == rules.RenamedGroup {
+ rwrGroups = append(rwrGroups, rules.GetAPIGroupList()...)
+ continue
+ }
+ rwrGroups = append(rwrGroups, runtime.RawExtension{Raw: []byte(group.Raw)})
+ }
+
+ return sjson.SetBytes(objBytes, "groups", rwrGroups)
+}
+
+// RewriteAPIGroup rewrites responses for
+// /apis/x.virtualization.deckhouse.io
+//
+// This call returns all versions for x.virtualization.deckhouse.io.
+// Rewriter should reduce versions for only available in original group
+// To reduce further requests with specific versions.
+//
+// Example response:
+// { "kind":"APIGroup",
+//
+// "apiVersion":"v1",
+// "name":"x.virtualization.deckhouse.io",
+// "versions":[
+// {"groupVersion":"x.virtualization.deckhouse.io/v1","version":"v1"},
+// {"groupVersion":"x.virtualization.deckhouse.io/v1beta1","version":"v1beta1"},
+// {"groupVersion":"x.virtualization.deckhouse.io/v1alpha3","version":"v1alpha3"},
+// {"groupVersion":"x.virtualization.deckhouse.io/v1alpha2","version":"v1alpha2"},
+// {"groupVersion":"x.virtualization.deckhouse.io/v1alpha1","version":"v1alpha1"}
+// ],
+// "preferredVersion": {
+// "groupVersion":"x.virtualization.deckhouse.io/v1",
+// "version":"v1"}
+// }
+//
+// Rewrite for kubevirt.io group should be:
+// { "kind":"APIGroup",
+//
+// "apiVersion":"v1",
+// "name":"kubevirt.io",
+// "versions":[
+// {"groupVersion":"kubevirt.io/v1","version":"v1"},
+// {"groupVersion":"kubevirt.io/v1alpha3","version":"v1alpha3"}
+// ],
+// "preferredVersion": {
+// "groupVersion":"kubevirt.io/v1",
+// "version":"v1"}
+// }
+//
+// And rewrite for clone.kubevirt.io group should be:
+// { "kind":"APIGroup",
+//
+// "apiVersion":"v1",
+// "name":"clone.kubevirt.io",
+// "versions":[
+// {"groupVersion":"clone.kubevirt.io/v1alpha1","version":"v1alpha1"}
+// ],
+// "preferredVersion": {
+// "groupVersion":"clone.kubevirt.io/v1alpha1",
+// "version":"v1alpha1"}
+// }
+func RewriteAPIGroup(rules *RewriteRules, obj []byte, origGroup string) ([]byte, error) {
+ apiGroupRule, ok := rules.Rules[origGroup]
+ if !ok {
+ return nil, fmt.Errorf("no APIGroup rewrites for group '%s'", origGroup)
+ }
+
+ // Grab all versions from rules.
+ versions := make([]interface{}, 0)
+ for _, ver := range apiGroupRule.GroupRule.Versions {
+ versions = append(versions, map[string]interface{}{
+ "groupVersion": origGroup + "/" + ver,
+ "version": ver,
+ })
+ }
+ preferredVersion := map[string]interface{}{
+ "groupVersion": origGroup + "/" + apiGroupRule.GroupRule.PreferredVersion,
+ "version": apiGroupRule.GroupRule.PreferredVersion,
+ }
+
+ obj, err := sjson.SetBytes(obj, "name", origGroup)
+ if err != nil {
+ return nil, err
+ }
+ obj, err = sjson.SetBytes(obj, "versions", versions)
+ if err != nil {
+ return nil, err
+ }
+ obj, err = sjson.SetBytes(obj, "preferredVersion", preferredVersion)
+ if err != nil {
+ return nil, err
+ }
+ return obj, nil
+}
+
+// RewriteAPIResourceList rewrites server responses on
+// /apis/GROUP/VERSION requests.
+// This method excludes resources not belonging to original group from request.
+//
+// Example:
+//
+// Path rewrite: https://10.222.0.1:443/apis/kubevirt.io/v1 -> https://10.222.0.1:443/apis/x.virtualization.deckhouse.io/v1
+// original Group: kubevirt.io
+// rewrite name,singularName,kind for each resource.
+// /status -> name and kind
+// /scale -> rewrite resource name in the name field
+//
+// Response from /apis/x.virtualization.deckhouse.io/v1:
+//
+// {
+// "kind":"APIResourceList",
+// "apiVersion":"v1",
+//
+// --> "groupVersion":"x.virtualization.deckhouse.io/v1" --> rewrite to origGroup+version: kubevirt.io/v1
+//
+// "resources":[
+// {
+//
+// --> "name":"virtualmachineinstancereplicasets",
+// --> "singularName":"virtualmachineinstancereplicaset",
+//
+// "namespaced":true,
+//
+// --> "kind":"VirtualMachineInstanceReplicaSet",
+//
+// "verbs":["delete","deletecollection","get","list","patch","create","update","watch"],
+// "shortNames":["xvmirs","xvmirss"],
+// "categories":["kubevirt"],
+// "storageVersionHash":"QUMxLW9gfYs="
+// },{
+//
+// --> "name":"virtualmachineinstancereplicasets/status",
+//
+// "singularName":"",
+// "namespaced":true,
+//
+// --> "kind":"VirtualMachineInstanceReplicaSet",
+//
+// "verbs":["get","patch","update"]
+// },{
+//
+// --> "name":"virtualmachineinstancereplicasets/scale",
+//
+// "singularName":"",
+// "namespaced":true,
+// "group":"autoscaling",
+// "version":"v1",
+// "kind":"Scale",
+// "verbs":["get","patch","update"]
+// }]
+// }
+func RewriteAPIResourceList(rules *RewriteRules, obj []byte, origGroup string) ([]byte, error) {
+ // Ignore apiGroups not in rules.
+ apiGroupRule, ok := rules.Rules[origGroup]
+ if !ok {
+ return obj, nil
+ }
+ // MVP: rewrite only group for now. (No prefixes in the cluster yet).
+ obj, err := sjson.SetBytes(obj, "groupVersion", origGroup+"/"+apiGroupRule.GroupRule.PreferredVersion)
+ if err != nil {
+ return nil, err
+ }
+
+ resources := []byte(`[]`)
+
+ for _, resource := range gjson.GetBytes(obj, "resources").Array() {
+ name := resource.Get("name").String()
+ nameParts := strings.Split(name, "/")
+ resourceName := rules.RestoreResource(nameParts[0])
+
+ _, resourceRule := rules.ResourceRules(origGroup, resourceName)
+ if resourceRule == nil {
+ continue
+ }
+
+ // Rewrite name and kind.
+ resBytes, err := sjson.SetBytes([]byte(resource.Raw), "name", rules.RestoreResource(name))
+ if err != nil {
+ return nil, err
+ }
+
+ kind := gjson.GetBytes(resBytes, "kind").String()
+ if kind != "" {
+ resBytes, err = sjson.SetBytes(resBytes, "kind", rules.RestoreKind(kind))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ singular := gjson.GetBytes(resBytes, "singularName").String()
+ if singular != "" {
+ resBytes, err = sjson.SetBytes(resBytes, "singularName", rules.RestoreResource(singular))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ shortNames := gjson.GetBytes(resBytes, "shortNames").Array()
+ if len(shortNames) > 0 {
+ strShortNames := make([]string, 0, len(shortNames))
+ for _, shortName := range shortNames {
+ strShortNames = append(strShortNames, shortName.String())
+ }
+ newShortNames := rules.RestoreShortNames(strShortNames)
+ resBytes, err = sjson.SetBytes(resBytes, "shortNames", newShortNames)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ categories := gjson.GetBytes(resBytes, "categories")
+ if categories.Exists() {
+ restoredCategories := rules.RestoreCategories(resourceRule)
+ resBytes, err = sjson.SetBytes(resBytes, "categories", restoredCategories)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ resources, err = sjson.SetRawBytes(resources, "-1", resBytes)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return sjson.SetRawBytes(obj, "resources", resources)
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/discovery_test.go b/images/kube-api-proxy/pkg/rewriter/discovery_test.go
new file mode 100644
index 000000000..7b87ce72f
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/discovery_test.go
@@ -0,0 +1,285 @@
+package rewriter
+
+import (
+ "bufio"
+ "bytes"
+ "net/http"
+ "strconv"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "github.com/tidwall/gjson"
+)
+
+func createRewriterForDiscoveryTest() *RuleBasedRewriter {
+ apiGroupRules := map[string]APIGroupRule{
+ "original.group.io": {
+ GroupRule: GroupRule{
+ Group: "original.group.io",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ "someresources": {
+ Kind: "SomeResource",
+ ListKind: "SomeResourceList",
+ Plural: "someresources",
+ Singular: "someresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"sr", "srs"},
+ },
+ "anotherresources": {
+ Kind: "AnotherResource",
+ ListKind: "AnotherResourceList",
+ Plural: "anotherresources",
+ Singular: "anotherresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ ShortNames: []string{"ar"},
+ },
+ },
+ },
+ "other.group.io": {
+ GroupRule: GroupRule{
+ Group: "original.group.io",
+ Versions: []string{"v2alpha3"},
+ PreferredVersion: "v2alpha3",
+ },
+ ResourceRules: map[string]ResourceRule{
+ "otherresources": {
+ Kind: "OtherResource",
+ ListKind: "OtherResourceList",
+ Plural: "otherresources",
+ Singular: "otherresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ ShortNames: []string{"or"},
+ },
+ },
+ },
+ }
+
+ webhookRules := map[string]WebhookRule{
+ "/validate-prefixed-resources-group-io-v1-prefixedsomeresource": {
+ Path: "/validate-original-group-io-v1-someresource",
+ Group: "original.group.io",
+ Resource: "someresources",
+ },
+ }
+
+ rwRules := &RewriteRules{
+ KindPrefix: "Prefixed", // KV
+ ResourceTypePrefix: "prefixed", // kv
+ ShortNamePrefix: "p",
+ Categories: []string{"prefixed"},
+ RenamedGroup: "prefixed.resources.group.io",
+ Rules: apiGroupRules,
+ Webhooks: webhookRules,
+ }
+
+ return &RuleBasedRewriter{
+ Rules: rwRules,
+ }
+}
+
+func TestRewriteRequestAPIResourceList(t *testing.T) {
+ // Request APIResourcesList of original, non-renamed resources.
+ request := `GET /apis/original.group.io/v1 HTTP/1.1
+Host: 127.0.0.1
+
+`
+ req, err := http.ReadRequest(bufio.NewReader(bytes.NewBufferString(request)))
+ require.NoError(t, err, "should read hardcoded request")
+
+ expectPath := "/apis/prefixed.resources.group.io/v1"
+
+ // Response body with renamed APIResourcesList
+ resourceListPayload := `{
+ "kind": "APIResourceList",
+ "apiVersion": "v1",
+ "groupVersion": "prefixed.resources.group.io/v1",
+ "resources": [
+ {"name":"prefixedsomeresources",
+ "singularName":"prefixedsomeresource",
+ "namespaced":true,
+ "kind":"PrefixedSomeResource",
+ "verbs":["delete","deletecollection","get","list","patch","create","update","watch"],
+ "shortNames":["psr","psrs"],
+ "categories":["prefixed"],
+ "storageVersionHash":"1qIJ90Mhvd8="},
+
+ {"name":"prefixedsomeresources/status",
+ "singularName":"",
+ "namespaced":true,
+ "kind":"PrefixedSomeResource",
+ "verbs":["get","patch","update"]},
+
+ {"name":"prefixedotherresources",
+ "singularName":"prefixedotherresource",
+ "namespaced":true,
+ "kind":"PrefixedOtherResource",
+ "verbs":["delete","deletecollection","get","list","patch","create","update","watch"],
+ "shortNames":["por"],
+ "categories":["prefixed"],
+ "storageVersionHash":"Nwlto9QquX0="},
+
+ {"name":"prefixedotherresources/status",
+ "singularName":"",
+ "namespaced":true,
+ "kind":"PrefixOtherResource",
+ "verbs":["get","patch","update"]}
+]}`
+
+ // Client proxy mode.
+ rwr := createRewriterForDiscoveryTest()
+
+ var targetReq *TargetRequest
+
+ targetReq = NewTargetRequest(rwr, req)
+ require.NotNil(t, targetReq, "should get TargetRequest")
+ require.Equal(t, expectPath, targetReq.Path(), "should rewrite api endpoint path")
+
+ resultBytes, err := rwr.RewriteJSONPayload(targetReq, []byte(resourceListPayload), Restore)
+ if err != nil {
+ t.Fatalf("should rewrite body with renamed resources: %v", err)
+ }
+
+ actual := string(resultBytes)
+
+ require.Contains(t, actual, `"someresources"`, "should contains original someresources, got: %s", actual)
+ require.Contains(t, actual, `"someresources/status"`, "should contains original someresources/status, got: %s", actual)
+ require.NotContains(t, actual, `"otherresources"`, "should not contains not requested otherresources, got: %s", actual)
+ require.NotContains(t, actual, `"otherresources/status"`, "should not contains not requested otherresources/status, got: %s", actual)
+}
+
+func TestRewriteAdmissionReviewRequestForResource(t *testing.T) {
+ admissionReview := `{
+ "kind":"AdmissionReview",
+ "apiVersion":"admission.k8s.io/v1",
+ "request":{
+ "uid":"389cfe15-34a1-4829-ad4d-de2576385711",
+ "kind":{"group":"prefixed.resources.group.io","version":"v1","kind":"PrefixedSomeResource"},
+ "resource":{"group":"prefixed.resources.group.io","version":"v1","resource":"prefixedsomeresources"},
+ "requestKind":{"group":"prefixed.resources.group.io","version":"v1","kind":"PrefixedSomeResource"},
+ "requestResource":{"group":"prefixed.resources.group.io","version":"v1","resource":"prefixedsomeresources"},
+ "name":"some-resource-name",
+ "namespace":"nsname",
+ "operation":"UPDATE",
+ "userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]},
+ "object":{
+ "apiVersion":"prefixed.resources.group.io/v1",
+ "kind":"PrefixedSomeResource",
+ "metadata":{
+ "annotations":{
+ "anno":"value",
+ },
+ "creationTimestamp":"2024-02-05T12:42:32Z",
+ "finalizers":["group.io/protection","other.group.io/protection"],
+ "name":"some-resource-name",
+ "namespace":"nsname",
+ "ownerReferences":[
+ {"apiVersion":"controller.group.io/v2",
+ "blockOwnerDeletion":true,
+ "controller":true,
+ "kind":"SomeKind","name":"some-controller-name","uid":"904cfea9-c9d6-4d3a-82f7-5790b1a1b3e0"}
+ ],
+ "resourceVersion":"265111919","uid":"4c74c3ff-2199-4f20-a71c-3b0e5fb505ca"
+ },
+ "spec":{"field1":"value1", "field2":"value2"},
+ "status":{
+ "conditions":[
+ {"lastProbeTime":null,"lastTransitionTime":"2024-03-06T14:38:39Z","status":"True","type":"Ready"},
+ {"lastProbeTime":"2024-02-29T14:11:05Z","lastTransitionTime":null,"status":"True","type":"Healthy"}],
+ "printableStatus":"Ready"
+ }
+ },
+
+ "oldObject":{
+ "apiVersion":"prefixed.resources.group.io/v1",
+ "kind":"PrefixedSomeResource",
+ "metadata":{
+ "annotations":{
+ "anno":"value",
+ },
+ "creationTimestamp":"2024-02-05T12:42:32Z",
+ "finalizers":["group.io/protection","other.group.io/protection"],
+ "name":"some-resource-name",
+ "namespace":"nsname",
+ "ownerReferences":[
+ {"apiVersion":"controller.group.io/v2",
+ "blockOwnerDeletion":true,
+ "controller":true,
+ "kind":"SomeKind","name":"some-controller-name","uid":"904cfea9-c9d6-4d3a-82f7-5790b1a1b3e0"}
+ ],
+ "resourceVersion":"265111919","uid":"4c74c3ff-2199-4f20-a71c-3b0e5fb505ca"
+ },
+ "spec":{"field1":"value1", "field2":"value2"},
+ "status":{
+ "conditions":[
+ {"lastProbeTime":null,"lastTransitionTime":"2024-03-06T14:38:39Z","status":"True","type":"Ready"},
+ {"lastProbeTime":"2024-02-29T14:11:05Z","lastTransitionTime":null,"status":"True","type":"Healthy"}],
+ "printableStatus":"Ready"
+ }
+ }
+ }
+}
+`
+ admissionReviewRequest := `POST /validate-prefixed-resources-group-io-v1-prefixedsomeresource HTTP/1.1
+Host: 127.0.0.1
+Content-Type: application/json
+Content-Length: ` + strconv.Itoa(len(admissionReview)) + `
+
+` + admissionReview
+
+ req, err := http.ReadRequest(bufio.NewReader(bytes.NewBufferString(admissionReviewRequest)))
+ require.NoError(t, err, "should read hardcoded AdmissionReview request")
+
+ rwr := createRewriterForDiscoveryTest()
+
+ // Check getting TargetRequest from the webhook request.
+ var targetReq *TargetRequest
+ targetReq = NewTargetRequest(rwr, req)
+ require.NotNil(t, targetReq, "should get TargetRequest")
+ require.True(t, targetReq.ShouldRewriteRequest(), "should rewrite request in TargetRequest")
+
+ // Check payload rewriting.
+ resultBytes, err := rwr.RewriteJSONPayload(targetReq, []byte(admissionReview), Restore)
+ require.NoError(t, err, "should rewrite request")
+ if err != nil {
+ t.Fatalf("should rewrite request: %v", err)
+ }
+
+ require.Greater(t, len(resultBytes), 0, "result bytes from RewriteJSONPayload should not be empty")
+
+ groupRule, resRule := rwr.Rules.ResourceRules("original.group.io", "someresources")
+ require.NotNil(t, resRule, "should get resourceRule for hardcoded group and resourceType")
+
+ tests := []struct {
+ path string
+ expected string
+ }{
+ {"request.kind.group", groupRule.Group},
+ {"request.kind.kind", resRule.Kind},
+ {"request.requestKind.group", groupRule.Group},
+ {"request.requestKind.kind", resRule.Kind},
+ {"request.resource.group", groupRule.Group},
+ {"request.resource.resource", resRule.Plural},
+ {"request.requestResource.group", groupRule.Group},
+ {"request.requestResource.resource", resRule.Plural},
+ {"request.object.apiVersion", groupRule.Group + "/v1"},
+ {"request.object.kind", resRule.Kind},
+ {"request.oldObject.apiVersion", groupRule.Group + "/v1"},
+ {"request.oldObject.kind", resRule.Kind},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.path, func(t *testing.T) {
+ actual := gjson.GetBytes(resultBytes, tt.path).String()
+ if actual != tt.expected {
+ t.Fatalf("%s value should be %s, got %s", tt.path, tt.expected, actual)
+ }
+ })
+ }
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/list.go b/images/kube-api-proxy/pkg/rewriter/list.go
new file mode 100644
index 000000000..12823695f
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/list.go
@@ -0,0 +1,43 @@
+package rewriter
+
+import (
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+// RewriteResourceOrList is a helper to transform a single resource or a list of resources.
+func RewriteResourceOrList(payload []byte, listKind string, transformFn func(singleObj []byte) ([]byte, error)) ([]byte, error) {
+ kind := gjson.GetBytes(payload, "kind").String()
+
+ // Not a list, transform a single resource.
+ if kind != listKind {
+ return transformFn(payload)
+ }
+
+ return RewriteArray(payload, "items", transformFn)
+}
+
+func RewriteArray(obj []byte, arrayPath string, transformFn func(item []byte) ([]byte, error)) ([]byte, error) {
+ // Transform each item in list. Put back original items if transformFn returns nil bytes.
+ items := gjson.GetBytes(obj, arrayPath).Array()
+ if len(items) == 0 {
+ return obj, nil
+ }
+ rwrItems := []byte(`[]`)
+ for _, item := range items {
+ rwrItem, err := transformFn([]byte(item.Raw))
+ if err != nil {
+ return nil, err
+ }
+ // Put original item back.
+ if rwrItem == nil {
+ rwrItem = []byte(item.Raw)
+ }
+ rwrItems, err = sjson.SetRawBytes(rwrItems, "-1", rwrItem)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return sjson.SetRawBytes(obj, arrayPath, rwrItems)
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/load.go b/images/kube-api-proxy/pkg/rewriter/load.go
new file mode 100644
index 000000000..4621678a7
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/load.go
@@ -0,0 +1,22 @@
+package rewriter
+
+import (
+ "os"
+
+ "sigs.k8s.io/yaml"
+)
+
+func LoadRules(filename string) (*RewriteRules, error) {
+ data, err := os.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ var rules = new(RewriteRules)
+ err = yaml.Unmarshal(data, rules)
+ if err != nil {
+ return nil, err
+ }
+
+ return rules, nil
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/path.go b/images/kube-api-proxy/pkg/rewriter/path.go
new file mode 100644
index 000000000..364e793e6
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/path.go
@@ -0,0 +1,175 @@
+package rewriter
+
+// RewritePath return rewritten TargetPath along with original group and resource type.
+// TODO: this rewriter is not conform to S in SOLID. Should split to ParseAPIEndpoint and RewriteAPIEndpoint.
+//func (rw *RuleBasedRewriter) RewritePath(urlPath string) (*TargetRequest, error) {
+// // Is it a webhook?
+// if webhookRule, ok := rw.Rules.Webhooks[urlPath]; ok {
+// return &TargetRequest{
+// Webhook: &webhookRule,
+// }, nil
+// }
+//
+// // Is it an API request?
+// if strings.HasPrefix(urlPath, "/apis/") || urlPath == "/apis" {
+// // TODO refactor RewriteAPIPath to produce a TargetPath, not an array in PathItems.
+// cleanedPath := strings.Trim(urlPath, "/")
+// pathItems := strings.Split(cleanedPath, "/")
+//
+// // First, try to rewrite CRD request.
+// res := RewriteCRDPath(pathItems, rw.Rules)
+// if res != nil {
+// return res, nil
+// }
+// // Next, rewrite usual request.
+// res, err := RewriteAPIsPath(pathItems, rw.Rules)
+// if err != nil {
+// return nil, err
+// }
+// if res == nil {
+// // e.g. no rewrite rule find.
+// return nil, nil
+// }
+// if len(res.PathItems) > 0 {
+// res.TargetPath = "/" + path.Join(res.PathItems...)
+// }
+// return res, nil
+// }
+//
+// if strings.HasPrefix(urlPath, "/api/") || urlPath == "/api" {
+// return &TargetRequest{
+// IsCoreAPI: true,
+// }, nil
+// }
+//
+// return nil, nil
+//}
+
+// Constants with indices of API endpoints portions.
+// Request cluster scoped resource:
+// - /apis/GROUP/VERSION/RESOURCETYPE/NAME/SUBRESOURCE
+// | | | |
+// APISIdx | | |
+// GroupIDx | |
+// VersionIDx ---+ |
+// ClusterResourceIdx ---+
+
+//
+// Request namespaced resource:
+// - /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
+// | | |
+// NamespacesIdx --------+ | |
+// NamespaceIdx --------------------+ |
+// NamespacedResourceIdx----------------------+
+//
+// Request CRD:
+// - /apis/apiextensions.k8s.io/v1/customresourcedefinitions/RESOURCETYPE.GROUP
+// | | |
+// GroupIdx | |
+// ClusterResourceIdx -------------+ |
+// CRDNameIdx -----------------------------------------------+
+
+//const (
+// APISIdx = 0
+// GroupIdx = 1
+// VersionIdx = 2
+// NamespacesIdx = 3
+// NamespaceIdx = 4
+// ClusterResourceIdx = 3
+// NamespacedResourceIdx = 5
+//)
+
+// RewriteAPIsPath rewrites GROUP and RESOURCETYPE in these API calls:
+// - /apis/GROUP
+// - /apis/GROUP/VERSION
+// - /apis/GROUP/VERSION/RESOURCETYPE
+// - /apis/GROUP/VERSION/RESOURCETYPE/NAME
+// - /apis/GROUP/VERSION/RESOURCETYPE/NAME/SUBRESOURCE
+//
+// - /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE
+// - /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME
+// - /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
+//func RewriteAPIsPath(pathItems []string, rules *RewriteRules) (*TargetRequest, error) {
+// if len(pathItems) == 0 {
+// return nil, nil
+// }
+//
+// res := &TargetRequest{
+// PathItems: make([]string, 0, len(pathItems)),
+// }
+//
+// if len(pathItems) == 1 {
+// if pathItems[APISIdx] == "apis" {
+// // Do not rewrite URL, but rewrite response later.
+// res.PathItems = append(res.PathItems, pathItems[APISIdx])
+// return res, nil
+// }
+// // The single path item should be "apis".
+// return nil, nil
+// }
+//
+// res.PathItems = append(res.PathItems, pathItems[APISIdx])
+//
+// // Check if the GROUP portion match Rules.
+// apiGroupName := ""
+// apiGroupMatch := false
+// group := pathItems[GroupIdx]
+// for groupName, apiGroupRule := range rules.Rules {
+// if apiGroupRule.GroupRule.Group == group {
+// res.OrigGroup = group
+// res.PathItems = append(res.PathItems, rules.RenamedGroup)
+// apiGroupName = groupName
+// apiGroupMatch = true
+// break
+// }
+// }
+//
+// if !apiGroupMatch {
+// return nil, nil
+// }
+// // Stop if GROUP is the last item in path.
+// if len(pathItems) <= GroupIdx+1 {
+// return res, nil
+// }
+//
+// // Add VERSION portion.
+// res.PathItems = append(res.PathItems, pathItems[VersionIdx])
+// // Stop if VERSION is the last item in path.
+// if len(pathItems) <= VersionIdx+1 {
+// return res, nil
+// }
+//
+// // Check is namespaced resource is requested.
+// resourceTypeIdx := ClusterResourceIdx
+// if pathItems[NamespacesIdx] == "namespaces" {
+// res.PathItems = append(res.PathItems, pathItems[NamespacesIdx])
+// res.PathItems = append(res.PathItems, pathItems[NamespaceIdx])
+// resourceTypeIdx = NamespacedResourceIdx
+// }
+//
+// // Check if the RESOURCETYPE portion match Rules.
+// resourceType := pathItems[resourceTypeIdx]
+// resourceTypeMatched := true
+// for _, rule := range rules.Rules[apiGroupName].ResourceRules {
+// if rule.Plural == resourceType {
+// res.OrigResourceType = resourceType
+// res.PathItems = append(res.PathItems, rules.RenameResource(rule.Plural))
+// resourceTypeMatched = true
+// break
+// }
+// }
+// if !resourceTypeMatched {
+// return nil, nil
+// }
+// // Return if RESOURCETYPE is the last item in path.
+// if len(pathItems) == resourceTypeIdx+1 {
+// return res, nil
+// }
+//
+// // Copy remaining items: NAME and SUBRESOURCE.
+// for i := resourceTypeIdx + 1; i < len(pathItems); i++ {
+// res.PathItems = append(res.PathItems, pathItems[i])
+// }
+//
+// return res, nil
+//}
diff --git a/images/kube-api-proxy/pkg/rewriter/rbac.go b/images/kube-api-proxy/pkg/rewriter/rbac.go
new file mode 100644
index 000000000..eacbdc42c
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/rbac.go
@@ -0,0 +1,193 @@
+package rewriter
+
+import (
+ "strings"
+
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+const (
+ ClusterRoleKind = "ClusterRole"
+ ClusterRoleListKind = "ClusterRoleList"
+ RoleKind = "Role"
+ RoleListKind = "RoleList"
+)
+
+func RewriteClusterRoleOrList(rules *RewriteRules, obj []byte, action Action) ([]byte, error) {
+ if action == Rename {
+ return RewriteResourceOrList(obj, ClusterRoleListKind, func(singleObj []byte) ([]byte, error) {
+ return RewriteArray(singleObj, "rules", func(item []byte) ([]byte, error) {
+ return renameRoleRule(rules, item)
+ })
+ })
+ }
+ return RewriteResourceOrList(obj, ClusterRoleListKind, func(singleObj []byte) ([]byte, error) {
+ return RewriteArray(singleObj, "rules", func(item []byte) ([]byte, error) {
+ return restoreRoleRule(rules, item)
+ })
+ })
+}
+
+func RewriteRoleOrList(rules *RewriteRules, obj []byte, action Action) ([]byte, error) {
+ if action == Rename {
+ return RewriteResourceOrList(obj, RoleListKind, func(singleObj []byte) ([]byte, error) {
+ return RewriteArray(singleObj, "rules", func(item []byte) ([]byte, error) {
+ return renameRoleRule(rules, item)
+ })
+ })
+ }
+ return RewriteResourceOrList(obj, RoleListKind, func(singleObj []byte) ([]byte, error) {
+ return RewriteArray(singleObj, "rules", func(item []byte) ([]byte, error) {
+ return restoreRoleRule(rules, item)
+ })
+ })
+}
+
+// renameRoleRule renames apiGroups and resources in a single rule.
+// Rule examples:
+// - apiGroups:
+// - original.group.io
+// resources:
+// - '*'
+// verbs:
+// - '*'
+// - apiGroups:
+// - original.group.io
+// resources:
+// - someresources
+// - someresources/finalizers
+// - someresources/status
+// - someresources/scale
+// verbs:
+// - watch
+// - list
+// - create
+func renameRoleRule(rules *RewriteRules, obj []byte) ([]byte, error) {
+ var err error
+
+ apiGroups := gjson.GetBytes(obj, "apiGroups").Array()
+ newGroups := []byte(`[]`)
+ shouldRename := false
+ for _, apiGroup := range apiGroups {
+ group := apiGroup.String()
+ if group == "*" {
+ shouldRename = true
+ } else if rules.HasGroup(group) {
+ group = rules.RenamedGroup
+ shouldRename = true
+ }
+
+ newGroups, err = sjson.SetBytes(newGroups, "-1", group)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if !shouldRename {
+ return obj, nil
+ }
+
+ resources := gjson.GetBytes(obj, "resources").Array()
+ newResources := []byte(`[]`)
+ for _, resource := range resources {
+ resourceType := resource.String()
+ if strings.Contains(resourceType, "/") {
+ resourceType, _, _ = strings.Cut(resource.String(), "/")
+ }
+ if resourceType != "*" {
+ _, resRule := rules.GroupResourceRules(resourceType)
+ if resRule != nil {
+ // TODO(future) make it work with suffix and subresource.
+ resourceType = rules.RenameResource(resource.String())
+ }
+ }
+
+ newResources, err = sjson.SetBytes(newResources, "-1", resourceType)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ obj, err = sjson.SetRawBytes(obj, "apiGroups", newGroups)
+ if err != nil {
+ return nil, err
+ }
+ return sjson.SetRawBytes(obj, "resources", newResources)
+}
+
+// restoreRoleRule restores apiGroups and resources in a single rule.
+func restoreRoleRule(rules *RewriteRules, obj []byte) ([]byte, error) {
+ var err error
+
+ apiGroups := gjson.GetBytes(obj, "apiGroups").Array()
+ newGroups := []byte(`[]`)
+ shouldRestore := false
+ shouldAddGroup := false
+ for _, apiGroup := range apiGroups {
+ group := apiGroup.String()
+ if group == "*" {
+ shouldRestore = true
+ }
+ if group == rules.RenamedGroup {
+ shouldRestore = true
+ shouldAddGroup = true
+ // Group will be restored later, do not add now.
+ continue
+ }
+ newGroups, err = sjson.SetBytes(newGroups, "-1", group)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if !shouldRestore {
+ return obj, nil
+ }
+
+ // Loop over resources and detect group from rules.
+
+ resources := gjson.GetBytes(obj, "resources").Array()
+ newResources := []byte(`[]`)
+ shouldRestore = false
+ groupToAdd := ""
+ for _, resource := range resources {
+ newResource := resource.String()
+ resourceType := resource.String()
+ //subresource := ""
+ if strings.Contains(resourceType, "/") {
+ resourceType, _, _ = strings.Cut(resourceType, "/")
+ }
+ if resourceType != "*" {
+ // Restore resourceType to get rules.
+ originalResourceType := rules.RestoreResource(resourceType)
+ groupRule, resRule := rules.GroupResourceRules(originalResourceType)
+ if groupRule != nil && resRule != nil {
+ shouldRestore = true
+ groupToAdd = groupRule.Group
+ // NOTE: Restore resource with subresource.
+ // TODO(future) make it work with suffixes.
+ newResource = rules.RestoreResource(resource.String())
+ }
+ }
+
+ newResources, err = sjson.SetBytes(newResources, "-1", newResource)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Add restored group to apiGroups.
+ if shouldAddGroup && groupToAdd != "" {
+ newGroups, err = sjson.SetBytes(newGroups, "-1", groupToAdd)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ obj, err = sjson.SetRawBytes(obj, "apiGroups", newGroups)
+ if err != nil {
+ return nil, err
+ }
+ return sjson.SetRawBytes(obj, "resources", newResources)
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/rbac_test.go b/images/kube-api-proxy/pkg/rewriter/rbac_test.go
new file mode 100644
index 000000000..4acbdbb41
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/rbac_test.go
@@ -0,0 +1,147 @@
+package rewriter
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestRenameRoleRule(t *testing.T) {
+
+ tests := []struct {
+ name string
+ rule string
+ expect string
+ }{
+ {
+ "group and resources",
+ `{"apiGroups":["original.group.io"],
+"resources": ["someresources","someresources/finalizers","someresources/status"],
+"verbs": ["watch", "list", "create"]
+}`,
+ `{"apiGroups":["prefixed.resources.group.io"],
+"resources": ["prefixedsomeresources","prefixedsomeresources/finalizers","prefixedsomeresources/status"],
+"verbs": ["watch", "list", "create"]
+}`,
+ },
+ {
+ "only resources",
+ `{"apiGroups":["*"],
+"resources": ["someresources","someresources/finalizers","someresources/status"],
+"verbs": ["watch", "list", "create"]
+}`,
+ `{"apiGroups":["*"],
+"resources": ["prefixedsomeresources","prefixedsomeresources/finalizers","prefixedsomeresources/status"],
+"verbs": ["watch", "list", "create"]
+}`,
+ },
+ {
+ "only group",
+ `{"apiGroups":["original.group.io"],
+"resources": ["*"],
+"verbs": ["watch", "list", "create"]
+}`,
+ `{"apiGroups":["prefixed.resources.group.io"],
+"resources": ["*"],
+"verbs": ["watch", "list", "create"]
+}`,
+ },
+ {
+ "allow all",
+ `{"apiGroups":["*"], "resources":["*"], "verbs":["*"]}`,
+ `{"apiGroups":["*"], "resources":["*"], "verbs":["*"]}`,
+ },
+ {
+ "unknown group",
+ `{"apiGroups":["unknown.group.io"], "resources":["someresources"], "verbs":["*"]}`,
+ `{"apiGroups":["unknown.group.io"], "resources":["someresources"], "verbs":["*"]}`,
+ },
+ {
+ "core resource",
+ `{"apiGroups":[""], "resources":["pods"], "verbs":["create"]}`,
+ `{"apiGroups":[""], "resources":["pods"], "verbs":["create"]}`,
+ },
+ }
+
+ rwr := createTestRewriter()
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resBytes, err := renameRoleRule(rwr.Rules, []byte(tt.rule))
+ require.NoError(t, err, "should rename rule")
+
+ actual := string(resBytes)
+ require.Equal(t, tt.expect, actual)
+ })
+ }
+}
+
+func TestRestoreRoleRule(t *testing.T) {
+ tests := []struct {
+ name string
+ rule string
+ expect string
+ }{
+ {
+ "group and resources",
+ `{"apiGroups":["prefixed.resources.group.io"],
+"resources": ["prefixedsomeresources","prefixedsomeresources/finalizers","prefixedsomeresources/status"],
+"verbs": ["watch", "list", "create"]
+}`,
+ `{"apiGroups":["original.group.io"],
+"resources": ["someresources","someresources/finalizers","someresources/status"],
+"verbs": ["watch", "list", "create"]
+}`,
+ },
+ {
+ "only resources",
+ `{"apiGroups":["*"],
+"resources": ["prefixedsomeresources","prefixedsomeresources/finalizers","prefixedsomeresources/status"],
+"verbs": ["watch", "list", "create"]
+}`,
+ `{"apiGroups":["*"],
+"resources": ["someresources","someresources/finalizers","someresources/status"],
+"verbs": ["watch", "list", "create"]
+}`,
+ },
+ // Impossible to restore with current rules.
+ // {
+ // "only group",
+ // `{"apiGroups":["prefixed.resources.group.io"],
+ //"resources": ["*"],
+ //"verbs": ["watch", "list", "create"]
+ //}`,
+ // `{"apiGroups":["original.group.io"],
+ //"resources": ["*"],
+ //"verbs": ["watch", "list", "create"]
+ //}`,
+ // },
+ {
+ "allow all",
+ `{"apiGroups":["*"], "resources":["*"], "verbs":["*"]}`,
+ `{"apiGroups":["*"], "resources":["*"], "verbs":["*"]}`,
+ },
+ {
+ "unknown group",
+ `{"apiGroups":["unknown.group.io"], "resources":["someresources"], "verbs":["*"]}`,
+ `{"apiGroups":["unknown.group.io"], "resources":["someresources"], "verbs":["*"]}`,
+ },
+ {
+ "core resource",
+ `{"apiGroups":[""], "resources":["pods","configmaps"], "verbs":["create"]}`,
+ `{"apiGroups":[""], "resources":["pods","configmaps"], "verbs":["create"]}`,
+ },
+ }
+
+ rwr := createTestRewriter()
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resBytes, err := restoreRoleRule(rwr.Rules, []byte(tt.rule))
+ require.NoError(t, err, "should rename rule")
+
+ actual := string(resBytes)
+ require.Equal(t, tt.expect, actual)
+ })
+ }
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/resource.go b/images/kube-api-proxy/pkg/rewriter/resource.go
new file mode 100644
index 000000000..e89b8b1b8
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/resource.go
@@ -0,0 +1,343 @@
+package rewriter
+
+import (
+ "strings"
+
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+func RewriteCustomResourceOrList(rules *RewriteRules, obj []byte, action Action) ([]byte, error) {
+ kind := gjson.GetBytes(obj, "kind").String()
+ if action == Restore {
+ kind = rules.RestoreKind(kind)
+ }
+ origGroupName, origResName, isList := rules.ResourceByKind(kind)
+ if origGroupName == "" && origResName == "" {
+ // Return as-is if kind is not in rules.
+ return obj, nil
+ }
+ if isList {
+ if action == Restore {
+ return RestoreResourcesList(rules, obj, origGroupName)
+ }
+
+ return RenameResourcesList(rules, obj)
+ }
+
+ // Responses of GET, LIST, DELETE requests.
+ // AdmissionReview requests from API Server.
+ if action == Restore {
+ return RestoreResource(rules, obj, origGroupName)
+ }
+ // CREATE, UPDATE, PATCH requests.
+ // TODO need to implement for
+ return RenameResource(rules, obj)
+}
+
+func RenameResourcesList(rules *RewriteRules, obj []byte) ([]byte, error) {
+ obj, err := RenameAPIVersionAndKind(rules, obj)
+ if err != nil {
+ return nil, err
+ }
+
+ // Rewrite apiVersion and kind in each item.
+ items := gjson.GetBytes(obj, "items").Array()
+ rwrItems := []byte(`[]`)
+ for _, item := range items {
+ rwrItem, err := RenameResource(rules, []byte(item.Raw))
+ if err != nil {
+ return nil, err
+ }
+ rwrItems, err = sjson.SetRawBytes(rwrItems, "-1", rwrItem)
+ }
+
+ obj, err = sjson.SetRawBytes(obj, "items", rwrItems)
+ if err != nil {
+ return nil, err
+ }
+
+ return obj, nil
+}
+
+func RestoreResourcesList(rules *RewriteRules, obj []byte, origGroupName string) ([]byte, error) {
+ obj, err := RestoreAPIVersionAndKind(rules, obj, origGroupName)
+ if err != nil {
+ return nil, err
+ }
+
+ // Rewrite apiVersion and kind in each item.
+ items := gjson.GetBytes(obj, "items").Array()
+ rwrItems := []byte(`[]`)
+ for _, item := range items {
+ rwrItem, err := RestoreResource(rules, []byte(item.Raw), origGroupName)
+ if err != nil {
+ return nil, err
+ }
+ rwrItems, err = sjson.SetRawBytes(rwrItems, "-1", rwrItem)
+ }
+
+ obj, err = sjson.SetRawBytes(obj, "items", rwrItems)
+ if err != nil {
+ return nil, err
+ }
+
+ return obj, nil
+}
+
+func RenameResource(rules *RewriteRules, obj []byte) ([]byte, error) {
+ obj, err := RenameAPIVersionAndKind(rules, obj)
+ if err != nil {
+ return nil, err
+ }
+
+ // Rewrite apiVersion in each managedFields.
+ obj, err = RenameManagedFields(rules, obj)
+ if err != nil {
+ return nil, err
+ }
+
+ return RenameOwnerReferences(rules, obj)
+}
+
+func RestoreResource(rules *RewriteRules, obj []byte, origGroupName string) ([]byte, error) {
+ obj, err := RestoreAPIVersionAndKind(rules, obj, origGroupName)
+ if err != nil {
+ return nil, err
+ }
+
+ // Rewrite apiVersion in each managedFields.
+ obj, err = RestoreManagedFields(rules, obj, origGroupName)
+ if err != nil {
+ return nil, err
+ }
+
+ return RestoreOwnerReferences(rules, obj, origGroupName)
+}
+
+func RenameAPIVersionAndKind(rules *RewriteRules, obj []byte) ([]byte, error) {
+ apiVersion := gjson.GetBytes(obj, "apiVersion").String()
+ obj, err := sjson.SetBytes(obj, "apiVersion", rules.RenameApiVersion(apiVersion))
+ if err != nil {
+ return nil, err
+ }
+
+ kind := gjson.GetBytes(obj, "kind").String()
+ return sjson.SetBytes(obj, "kind", rules.RenameKind(kind))
+}
+
+func RestoreAPIVersionAndKind(rules *RewriteRules, obj []byte, origGroupName string) ([]byte, error) {
+ apiVersion := gjson.GetBytes(obj, "apiVersion").String()
+ apiVersion = rules.RestoreApiVersion(apiVersion, origGroupName)
+ obj, err := sjson.SetBytes(obj, "apiVersion", apiVersion)
+ if err != nil {
+ return nil, err
+ }
+
+ kind := gjson.GetBytes(obj, "kind").String()
+ return sjson.SetBytes(obj, "kind", rules.RestoreKind(kind))
+}
+
+func RewriteOwnerReferences(rules *RewriteRules, obj []byte, action Action) ([]byte, error) {
+ ownerRefs := gjson.GetBytes(obj, "metadata.ownerReferences").Array()
+ if len(ownerRefs) == 0 {
+ return obj, nil
+ }
+
+ rwrOwnerRefs := []byte(`[]`)
+ rewritten := false
+ for _, ownerRef := range ownerRefs {
+ kind := ownerRef.Get("kind").String()
+ if action == Restore {
+ kind = rules.RestoreKind(kind)
+ }
+ // Find if kind should be rewritten.
+ origGroup, origResource, _ := rules.ResourceByKind(kind)
+ if origGroup == "" && origResource == "" {
+ // There is no rewrite rule for kind, append ownerRef as is.
+ var err error
+ rwrOwnerRefs, err = sjson.SetRawBytes(rwrOwnerRefs, "-1", []byte(ownerRef.Raw))
+ if err != nil {
+ return nil, err
+ }
+ continue
+ }
+ if action == Rename {
+ kind = rules.RenameKind(kind)
+ }
+
+ rwrOwnerRef := []byte(ownerRef.Raw)
+
+ rwrOwnerRef, err := sjson.SetBytes(rwrOwnerRef, "kind", kind)
+ if err != nil {
+ return nil, err
+ }
+
+ apiVersion := ownerRef.Get("apiVersion").String()
+ if action == Restore {
+ apiVersion = rules.RestoreApiVersion(apiVersion, origGroup)
+ }
+ if action == Rename {
+ apiVersion = rules.RenameApiVersion(apiVersion)
+ }
+ rwrOwnerRef, err = sjson.SetBytes(rwrOwnerRef, "apiVersion", apiVersion)
+ if err != nil {
+ return nil, err
+ }
+
+ rwrOwnerRefs, err = sjson.SetRawBytes(rwrOwnerRefs, "-1", rwrOwnerRef)
+ rewritten = true
+ }
+ if rewritten {
+ return sjson.SetRawBytes(obj, "metadata.ownerReferences", rwrOwnerRefs)
+ }
+
+ return obj, nil
+}
+
+// RenameOwnerReferences renames kind and apiVersion to send request to server.
+func RenameOwnerReferences(rules *RewriteRules, obj []byte) ([]byte, error) {
+ ownerRefs := gjson.GetBytes(obj, "metadata.ownerReferences").Array()
+ if len(ownerRefs) == 0 {
+ return obj, nil
+ }
+
+ rwrOwnerRefs := []byte(`[]`)
+ var err error
+ for _, ownerRef := range ownerRefs {
+ apiVersion := ownerRef.Get("apiVersion").String()
+ kind := ownerRef.Get("kind").String()
+
+ rwrOwnerRef := []byte(ownerRef.Raw)
+
+ _, resRule := rules.KindRules(apiVersion, kind)
+ if resRule != nil {
+ // Rename apiVersion and kind if resource has renaming rules.
+ rwrOwnerRef, err = sjson.SetBytes(rwrOwnerRef, "kind", rules.RenameKind(kind))
+ if err != nil {
+ return nil, err
+ }
+
+ rwrOwnerRef, err = sjson.SetBytes(rwrOwnerRef, "apiVersion", rules.RenameApiVersion(apiVersion))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ rwrOwnerRefs, err = sjson.SetRawBytes(rwrOwnerRefs, "-1", rwrOwnerRef)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return sjson.SetRawBytes(obj, "metadata.ownerReferences", rwrOwnerRefs)
+}
+
+// RestoreOwnerReferences restores kind and apiVersion to consume by the client.
+// There are no checks if resource should be restored. This should be determined by the caller.
+//
+// Example response from the server:
+// apiVersion: x.virtualization.deckhouse.io/v1
+// kind: VirtualMachineInstance
+// metadata:
+//
+// name: ...
+// namespace: ..
+// ownerReferences:
+// - apiVersion: x.virtualization.deckhouse.io/v1 <--- restore apiVersion
+// blockOwnerDeletion: true
+// controller: true
+// kind: VirtualMachine <--- restore kind
+// name: cloud-alpine
+// uid: 4c74c3ff-2199-4f20-a71c-3b0e5fb505ca
+func RestoreOwnerReferences(rules *RewriteRules, obj []byte, groupName string) ([]byte, error) {
+ ownerRefs := gjson.GetBytes(obj, "metadata.ownerReferences").Array()
+ if len(ownerRefs) == 0 {
+ return obj, nil
+ }
+ rOwnerRefs := []byte(`[]`)
+ restored := false
+ for _, ownerRef := range ownerRefs {
+ apiVersion := ownerRef.Get("apiVersion").String()
+ rOwnerRef := []byte(ownerRef.Raw)
+ var err error
+ if strings.HasPrefix(apiVersion, rules.RenamedGroup) {
+ rOwnerRef, err = sjson.SetBytes([]byte(ownerRef.Raw), "apiVersion", rules.RestoreApiVersion(apiVersion, groupName))
+ if err != nil {
+ return nil, err
+ }
+ kind := gjson.GetBytes(rOwnerRef, "kind").String()
+ rOwnerRef, err = sjson.SetBytes(rOwnerRef, "kind", rules.RestoreKind(kind))
+ if err != nil {
+ return nil, err
+ }
+ restored = true
+ }
+ rOwnerRefs, err = sjson.SetRawBytes(rOwnerRefs, "-1", rOwnerRef)
+ }
+ if restored {
+ return sjson.SetRawBytes(obj, "metadata.ownerReferences", rOwnerRefs)
+ }
+ return obj, nil
+}
+
+// RestoreManagedFields restores apiVersion in managedFields items.
+//
+// Example response from the server:
+//
+// "metadata": {
+// "managedFields":[
+// { "apiVersion":"x.virtualization.deckhouse.io/v1", "fieldsType":"FieldsV1", "fieldsV1":{ ... }}, "manager": "Go-http-client", ...},
+// { "apiVersion":"x.virtualization.deckhouse.io/v1", "fieldsType":"FieldsV1", "fieldsV1":{ ... }}, "manager": "kubectl-edit", ...}
+// ],
+func RestoreManagedFields(rules *RewriteRules, obj []byte, origGroupName string) ([]byte, error) {
+ mgFields := gjson.GetBytes(obj, "metadata.managedFields")
+ if !mgFields.Exists() || len(mgFields.Array()) == 0 {
+ return obj, nil
+ }
+
+ newFields := []byte(`[]`)
+ for _, mgField := range mgFields.Array() {
+ apiVersion := mgField.Get("apiVersion").String()
+ restoredAPIVersion := rules.RestoreApiVersion(apiVersion, origGroupName)
+ newField, err := sjson.SetBytes([]byte(mgField.Raw), "apiVersion", restoredAPIVersion)
+ if err != nil {
+ return nil, err
+ }
+ newFields, err = sjson.SetRawBytes(newFields, "-1", newField)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return sjson.SetRawBytes(obj, "metadata.managedFields", newFields)
+}
+
+// RenameManagedFields renames apiVersion in managedFields items.
+//
+// Example request from the client:
+//
+// "metadata": {
+// "managedFields":[
+// { "apiVersion":"kubevirt.io/v1", "fieldsType":"FieldsV1", "fieldsV1":{ ... }}, "manager": "Go-http-client", ...},
+// { "apiVersion":"kubevirt.io/v1", "fieldsType":"FieldsV1", "fieldsV1":{ ... }}, "manager": "kubectl-edit", ...}
+// ],
+func RenameManagedFields(rules *RewriteRules, obj []byte) ([]byte, error) {
+ mgFields := gjson.GetBytes(obj, "metadata.managedFields")
+ if !mgFields.Exists() || len(mgFields.Array()) == 0 {
+ return obj, nil
+ }
+
+ newFields := []byte(`[]`)
+ for _, mgField := range mgFields.Array() {
+ apiVersion := mgField.Get("apiVersion").String()
+ renamedAPIVersion := rules.RenameApiVersion(apiVersion)
+ newField, err := sjson.SetBytes([]byte(mgField.Raw), "apiVersion", renamedAPIVersion)
+ if err != nil {
+ return nil, err
+ }
+ newFields, err = sjson.SetRawBytes(newFields, "-1", newField)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return sjson.SetRawBytes(obj, "metadata.managedFields", newFields)
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/rule_rewriter.go b/images/kube-api-proxy/pkg/rewriter/rule_rewriter.go
new file mode 100644
index 000000000..06d9e9414
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/rule_rewriter.go
@@ -0,0 +1,208 @@
+package rewriter
+
+import (
+ "regexp"
+ "strings"
+
+ "github.com/tidwall/gjson"
+)
+
+type RuleBasedRewriter struct {
+ Rules *RewriteRules
+}
+
+type Action string
+
+const (
+ // Restore is an action to restore resources to original.
+ Restore Action = "restore"
+ // Rename is an action to rename original resources.
+ Rename Action = "rename"
+)
+
+// RewriteAPIEndpoint renames group and resource in /apis/* endpoints.
+// It assumes that ep contains original group and resourceType.
+// Restoring of path is not implemented.
+func (rw *RuleBasedRewriter) RewriteAPIEndpoint(ep *APIEndpoint) *APIEndpoint {
+ // Leave paths /, /api, /api/*, and unknown paths as is.
+ if ep.IsRoot || ep.IsCore || ep.IsUnknown {
+ return nil
+ }
+
+ // Rename CRD name resourcetype.group for resources with rules.
+ if ep.IsCRD {
+ // No endpoint rewrite for CRD list.
+ if ep.CRDGroup == "" && ep.CRDResourceType == "" {
+ if strings.Contains(ep.RawQuery, "metadata.name") {
+ // Rewrite name in field selector if any.
+ newQuery := rw.rewriteFieldSelector(ep.RawQuery)
+ if newQuery != "" {
+ res := ep.Clone()
+ res.RawQuery = newQuery
+ return res
+ }
+ }
+ return nil
+ }
+
+ // Check if resource has rules
+ _, resourceRule := rw.Rules.ResourceRules(ep.CRDGroup, ep.CRDResourceType)
+ if resourceRule == nil {
+ // No rewrite for CRD without rules.
+ return nil
+ }
+ // Rewrite CRD name.
+ res := ep.Clone()
+ res.CRDGroup = rw.Rules.RenamedGroup
+ res.CRDResourceType = rw.Rules.RenameResource(res.CRDResourceType)
+ res.Name = res.CRDResourceType + "." + res.CRDGroup
+ return res
+ }
+
+ // Rename group and resource for CR requests.
+ newGroup := ""
+ if ep.Group != "" {
+ groupRule := rw.Rules.GroupRule(ep.Group)
+ if groupRule == nil {
+ // No rewrite for group without rules.
+ return nil
+ }
+ newGroup = rw.Rules.RenamedGroup
+ }
+
+ newResource := ""
+ if ep.ResourceType != "" {
+ _, resRule := rw.Rules.ResourceRules(ep.Group, ep.ResourceType)
+ if resRule != nil {
+ newResource = rw.Rules.RenameResource(ep.ResourceType)
+ }
+ }
+
+ // Return rewritten endpoint if group or resource are changed.
+ if newGroup != "" || newResource != "" {
+ res := ep.Clone()
+ if newGroup != "" {
+ res.Group = newGroup
+ }
+ if newResource != "" {
+ res.ResourceType = newResource
+ }
+
+ return res
+ }
+
+ return nil
+}
+
+var metadataNameRe = regexp.MustCompile(`metadata.name\%3D([a-z0-9-]+)((\.[a-z0-9-]+)*)`)
+
+// rewriteFieldSelector rewrites value for metadata.name in fieldSelector of CRDs listing.
+// Example request:
+// https://APISERVER/apis/apiextensions.k8s.io/v1/customresourcedefinitions?fieldSelector=metadata.name%3Dresources.original.group.io&...
+func (rw *RuleBasedRewriter) rewriteFieldSelector(rawQuery string) string {
+ matches := metadataNameRe.FindStringSubmatch(rawQuery)
+ if matches == nil {
+ return ""
+ }
+
+ resourceType := matches[1]
+ group := matches[2]
+ group = strings.TrimPrefix(group, ".")
+
+ _, resRule := rw.Rules.ResourceRules(group, resourceType)
+ if resRule == nil {
+ return ""
+ }
+
+ group = rw.Rules.RenamedGroup
+ resourceType = rw.Rules.RenameResource(resourceType)
+
+ newSelector := `metadata.name%3D` + resourceType + "." + group
+
+ return metadataNameRe.ReplaceAllString(rawQuery, newSelector)
+}
+
+// RewriteJSONPayload does rewrite based on kind.
+func (rw *RuleBasedRewriter) RewriteJSONPayload(targetReq *TargetRequest, obj []byte, action Action) ([]byte, error) {
+ // Detect Kind
+ kind := gjson.GetBytes(obj, "kind").String()
+
+ var rwrBytes []byte
+ var err error
+
+ //// Handle core resources: rewrite only for specific kind.
+ //if targetReq.IsCore() {
+ // pass := true
+ // switch kind {
+ // case "APIGroupList":
+ // case "APIGroup":
+ // case "APIResourceList":
+ // default:
+ // pass = shouldPassCoreResource(kind)
+ // }
+ // if pass {
+ // return obj, nil
+ // }
+ //}
+
+ switch kind {
+ case "APIGroupList":
+ rwrBytes, err = RewriteAPIGroupList(rw.Rules, obj)
+
+ case "APIGroup":
+ rwrBytes, err = RewriteAPIGroup(rw.Rules, obj, targetReq.OrigGroup())
+
+ case "APIResourceList":
+ rwrBytes, err = RewriteAPIResourceList(rw.Rules, obj, targetReq.OrigGroup())
+
+ case "AdmissionReview":
+ rwrBytes, err = RewriteAdmissionReview(rw.Rules, obj, targetReq.OrigGroup())
+
+ case CRDKind, CRDListKind:
+ rwrBytes, err = RewriteCRDOrList(rw.Rules, obj, action)
+
+ case MutatingWebhookConfigurationKind,
+ MutatingWebhookConfigurationListKind:
+ rwrBytes, err = RewriteMutatingOrList(rw.Rules, obj, action)
+
+ case ValidatingWebhookConfigurationKind,
+ ValidatingWebhookConfigurationListKind:
+ rwrBytes, err = RewriteValidatingOrList(rw.Rules, obj, action)
+
+ case ClusterRoleKind, ClusterRoleListKind:
+ rwrBytes, err = RewriteClusterRoleOrList(rw.Rules, obj, action)
+
+ case RoleKind, RoleListKind:
+ rwrBytes, err = RewriteRoleOrList(rw.Rules, obj, action)
+
+ default:
+ if targetReq.IsCore() {
+ rwrBytes, err = RewriteOwnerReferences(rw.Rules, obj, action)
+ } else {
+ rwrBytes, err = RewriteCustomResourceOrList(rw.Rules, obj, action)
+ }
+ }
+
+ // Return obj bytes as-is in case of the error.
+ if err != nil {
+ return obj, err
+ }
+
+ return rwrBytes, nil
+}
+
+// RewritePatch rewrites patches for some known objects.
+// Only rename action is required for patches.
+func (rw *RuleBasedRewriter) RewritePatch(targetReq *TargetRequest, obj []byte) ([]byte, error) {
+ if targetReq.IsCRD() {
+ // Check if CRD is known.
+ _, resRule := rw.Rules.ResourceRules(targetReq.OrigGroup(), targetReq.OrigResourceType())
+ if resRule == nil {
+ return obj, nil
+ }
+
+ return RenameCRDPatch(rw.Rules, resRule, obj)
+ }
+
+ return obj, nil
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/rule_rewriter_test.go b/images/kube-api-proxy/pkg/rewriter/rule_rewriter_test.go
new file mode 100644
index 000000000..846706791
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/rule_rewriter_test.go
@@ -0,0 +1,133 @@
+package rewriter
+
+import (
+ "net/url"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func createTestRewriter() *RuleBasedRewriter {
+ apiGroupRules := map[string]APIGroupRule{
+ "original.group.io": {
+ GroupRule: GroupRule{
+ Group: "original.group.io",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ },
+ ResourceRules: map[string]ResourceRule{
+ "someresources": {
+ Kind: "SomeResource",
+ ListKind: "SomeResourceList",
+ Plural: "someresources",
+ Singular: "someresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ Categories: []string{"all"},
+ ShortNames: []string{"sr", "srs"},
+ },
+ "anotherresources": {
+ Kind: "AnotherResource",
+ ListKind: "AnotherResourceList",
+ Plural: "anotherresources",
+ Singular: "anotherresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ ShortNames: []string{"ar"},
+ },
+ },
+ },
+ "other.group.io": {
+ GroupRule: GroupRule{
+ Group: "original.group.io",
+ Versions: []string{"v2alpha3"},
+ PreferredVersion: "v2alpha3",
+ },
+ ResourceRules: map[string]ResourceRule{
+ "otherresources": {
+ Kind: "OtherResource",
+ ListKind: "OtherResourceList",
+ Plural: "otherresources",
+ Singular: "otherresource",
+ Versions: []string{"v1", "v1alpha1"},
+ PreferredVersion: "v1",
+ ShortNames: []string{"or"},
+ },
+ },
+ },
+ }
+
+ rules := &RewriteRules{
+ KindPrefix: "Prefixed", // KV
+ ResourceTypePrefix: "prefixed", // kv
+ ShortNamePrefix: "p",
+ Categories: []string{"prefixed"},
+ RenamedGroup: "prefixed.resources.group.io",
+ Rules: apiGroupRules,
+ }
+
+ return &RuleBasedRewriter{
+ Rules: rules,
+ }
+}
+
+func TestRewriteAPIEndpoint(t *testing.T) {
+ tests := []struct {
+ name string
+ path string
+ expect string
+ }{
+ {
+ "rewritable group",
+ "/apis/original.group.io",
+ "/apis/prefixed.resources.group.io",
+ },
+ {
+ "rewritable group and version",
+ "/apis/original.group.io/v1",
+ "/apis/prefixed.resources.group.io/v1",
+ },
+ {
+ "rewritable resource list",
+ "/apis/original.group.io/v1/someresources",
+ "/apis/prefixed.resources.group.io/v1/prefixedsomeresources",
+ },
+ {
+ "rewritable resource by name",
+ "/apis/original.group.io/v1/someresources/srname",
+ "/apis/prefixed.resources.group.io/v1/prefixedsomeresources/srname",
+ },
+ {
+ "rewritable resource status",
+ "/apis/original.group.io/v1/someresources/srname/status",
+ "/apis/prefixed.resources.group.io/v1/prefixedsomeresources/srname/status",
+ },
+ {
+ "rewritable CRD",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/someresources.original.group.io",
+ "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/prefixedsomeresources.prefixed.resources.group.io",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ u, err := url.Parse(tt.path)
+ require.NoError(t, err, "should parse path '%s'", tt.path)
+
+ ep := ParseAPIEndpoint(u)
+
+ rwr := createTestRewriter()
+
+ newEp := rwr.RewriteAPIEndpoint(ep)
+
+ if tt.expect == "" {
+ require.Nil(t, newEp, "should not rewrite path '%s', got %+v", tt.path, newEp)
+ }
+
+ require.NotNil(t, newEp, "should rewrite path '%s', got nil originEndpoint")
+
+ require.Equal(t, tt.expect, newEp.Path(), "expect rewrite for path '%s' to be '%s', got '%s'", tt.path, tt.expect, ep.Path())
+ })
+ }
+
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/rules.go b/images/kube-api-proxy/pkg/rewriter/rules.go
new file mode 100644
index 000000000..a8fc1ec60
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/rules.go
@@ -0,0 +1,210 @@
+package rewriter
+
+import "strings"
+
+type RewriteRules struct {
+ KindPrefix string `json:"kindPrefix"`
+ ResourceTypePrefix string `json:"resourceTypePrefix"`
+ ShortNamePrefix string `json:"shortNamePrefix"`
+ Categories []string `json:"categories"`
+ RenamedGroup string `json:"renamedGroup"`
+ Rules map[string]APIGroupRule `json:"rules"`
+ Webhooks map[string]WebhookRule `json:"webhooks"`
+}
+
+type APIGroupRule struct {
+ GroupRule GroupRule `json:"groupRule"`
+ ResourceRules map[string]ResourceRule `json:"resourceRules"`
+}
+
+type GroupRule struct {
+ Group string `json:"group"`
+ Versions []string `json:"versions"`
+ PreferredVersion string `json:"preferredVersion"`
+}
+
+type ResourceRule struct {
+ Kind string `json:"kind"`
+ ListKind string `json:"listKind"`
+ Plural string `json:"plural"`
+ Singular string `json:"singular"`
+ ShortNames []string `json:"shortNames"`
+ Categories []string `json:"categories"`
+ Versions []string `json:"versions"`
+ PreferredVersion string `json:"preferredVersion"`
+}
+
+type WebhookRule struct {
+ Path string `json:"path"`
+ Group string `json:"group"`
+ Resource string `json:"resource"`
+}
+
+// GetAPIGroupList returns an array of groups in format applicable to use in APIGroupList:
+//
+// {
+// name
+// versions: [ { groupVersion, version } ... ]
+// preferredVersion: { groupVersion, version }
+// }
+func (rr *RewriteRules) GetAPIGroupList() []interface{} {
+ groups := make([]interface{}, 0)
+
+ for _, rGroup := range rr.Rules {
+ group := map[string]interface{}{
+ "name": rGroup.GroupRule.Group,
+ "preferredVersion": map[string]interface{}{
+ "groupVersion": rGroup.GroupRule.Group + "/" + rGroup.GroupRule.PreferredVersion,
+ "version": rGroup.GroupRule.PreferredVersion,
+ },
+ }
+ versions := make([]interface{}, 0)
+ for _, ver := range rGroup.GroupRule.Versions {
+ versions = append(versions, map[string]interface{}{
+ "groupVersion": rGroup.GroupRule.Group + "/" + ver,
+ "version": ver,
+ })
+ }
+ group["versions"] = versions
+ groups = append(groups, group)
+ }
+
+ return groups
+}
+
+func (rr *RewriteRules) ResourceByKind(kind string) (string, string, bool) {
+ for groupName, group := range rr.Rules {
+ for resName, res := range group.ResourceRules {
+ if res.Kind == kind {
+ return groupName, resName, false
+ }
+ if res.ListKind == kind {
+ return groupName, resName, true
+ }
+ }
+ }
+ return "", "", false
+}
+
+func (rr *RewriteRules) WebhookRule(path string) *WebhookRule {
+ if webhookRule, ok := rr.Webhooks[path]; ok {
+ return &webhookRule
+ }
+ return nil
+}
+
+func (rr *RewriteRules) HasGroup(group string) bool {
+ _, ok := rr.Rules[group]
+ return ok
+}
+
+func (rr *RewriteRules) GroupRule(group string) *GroupRule {
+ if groupRule, ok := rr.Rules[group]; ok {
+ return &groupRule.GroupRule
+ }
+ return nil
+}
+
+// KindRules returns rule for group and resource by apiGroup and kind.
+// apiGroup may be a group or a group with version.
+func (rr *RewriteRules) KindRules(apiGroup, kind string) (*GroupRule, *ResourceRule) {
+ group, _, _ := strings.Cut(apiGroup, "/")
+ groupRule, ok := rr.Rules[group]
+ if !ok {
+ return nil, nil
+ }
+
+ for _, resRule := range groupRule.ResourceRules {
+ if resRule.Kind == kind {
+ return &groupRule.GroupRule, &resRule
+ }
+ if resRule.ListKind == kind {
+ return &groupRule.GroupRule, &resRule
+ }
+ }
+ return nil, nil
+}
+
+func (rr *RewriteRules) ResourceRules(group, resource string) (*GroupRule, *ResourceRule) {
+ groupRule, ok := rr.Rules[group]
+ if !ok {
+ return nil, nil
+ }
+ resourceRule, ok := rr.Rules[group].ResourceRules[resource]
+ if !ok {
+ return nil, nil
+ }
+ return &groupRule.GroupRule, &resourceRule
+}
+
+func (rr *RewriteRules) GroupResourceRules(resourceType string) (*GroupRule, *ResourceRule) {
+ for _, group := range rr.Rules {
+ for _, res := range group.ResourceRules {
+ if res.Plural == resourceType {
+ return &group.GroupRule, &res
+ }
+ }
+ }
+ return nil, nil
+}
+
+func (rr *RewriteRules) RenameResource(resource string) string {
+ return rr.ResourceTypePrefix + resource
+}
+
+func (rr *RewriteRules) RenameKind(kind string) string {
+ return rr.KindPrefix + kind
+}
+
+func (rr *RewriteRules) RestoreResource(resource string) string {
+ return strings.TrimPrefix(resource, rr.ResourceTypePrefix)
+}
+
+func (rr *RewriteRules) RestoreKind(kind string) string {
+ return strings.TrimPrefix(kind, rr.KindPrefix)
+}
+
+func (rr *RewriteRules) RestoreApiVersion(apiVersion string, group string) string {
+ // Replace group, keep version.
+ slashVersion := strings.TrimPrefix(apiVersion, rr.RenamedGroup)
+ return group + slashVersion
+}
+
+func (rr *RewriteRules) RenameApiVersion(apiVersion string) string {
+ // Replace group, keep version.
+ apiVerParts := strings.Split(apiVersion, "/")
+ if len(apiVerParts) != 2 {
+ return apiVersion
+ }
+ return rr.RenamedGroup + "/" + apiVerParts[1]
+}
+
+func (rr *RewriteRules) RenameCategories(categories []string) []string {
+ if len(categories) == 0 {
+ return []string{}
+ }
+ return rr.Categories
+}
+
+func (rr *RewriteRules) RestoreCategories(resourceRule *ResourceRule) []string {
+ if resourceRule == nil {
+ return []string{}
+ }
+ return resourceRule.Categories
+}
+
+func (rr *RewriteRules) RenameShortNames(shortNames []string) []string {
+ newNames := make([]string, 0, len(shortNames))
+ for _, shortName := range shortNames {
+ newNames = append(newNames, rr.ShortNamePrefix+shortName)
+ }
+ return newNames
+}
+
+func (rr *RewriteRules) RestoreShortNames(shortNames []string) []string {
+ newNames := make([]string, 0, len(shortNames))
+ for _, shortName := range shortNames {
+ newNames = append(newNames, strings.TrimPrefix(shortName, rr.ShortNamePrefix))
+ }
+ return newNames
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/target_request.go b/images/kube-api-proxy/pkg/rewriter/target_request.go
new file mode 100644
index 000000000..4a7606f5f
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/target_request.go
@@ -0,0 +1,278 @@
+package rewriter
+
+import (
+ "net/http"
+)
+
+type TargetRequest struct {
+ originEndpoint *APIEndpoint
+ targetEndpoint *APIEndpoint
+
+ webhookRule *WebhookRule
+}
+
+func NewTargetRequest(rwr *RuleBasedRewriter, req *http.Request) *TargetRequest {
+ if req == nil || req.URL == nil {
+ return nil
+ }
+
+ // Is it a request to the webhook?
+ webhookRule := rwr.Rules.WebhookRule(req.URL.Path)
+ if webhookRule != nil {
+ return &TargetRequest{
+ webhookRule: webhookRule,
+ }
+ }
+
+ apiEndpoint := ParseAPIEndpoint(req.URL)
+ if apiEndpoint == nil {
+ return nil
+ }
+
+ // rewrite path if needed
+ targetEndpoint := rwr.RewriteAPIEndpoint(apiEndpoint)
+
+ return &TargetRequest{
+ originEndpoint: apiEndpoint,
+ targetEndpoint: targetEndpoint,
+ }
+}
+
+// Path return possibly rewritten path for target endpoint.
+func (tr *TargetRequest) Path() string {
+ if tr.targetEndpoint != nil {
+ return tr.targetEndpoint.Path()
+ }
+ if tr.originEndpoint != nil {
+ return tr.originEndpoint.Path()
+ }
+ if tr.webhookRule != nil {
+ return tr.webhookRule.Path
+ }
+
+ return ""
+}
+
+func (tr *TargetRequest) IsCore() bool {
+ if tr.originEndpoint != nil {
+ return tr.originEndpoint.IsCore
+ }
+ return false
+}
+
+func (tr *TargetRequest) IsCRD() bool {
+ if tr.originEndpoint != nil {
+ return tr.originEndpoint.IsCRD
+ }
+ return false
+}
+
+func (tr *TargetRequest) IsWatch() bool {
+ if tr.originEndpoint != nil {
+ return tr.originEndpoint.IsWatch
+ }
+ return false
+}
+
+func (tr *TargetRequest) OrigGroup() string {
+ if tr.IsCRD() {
+ return tr.originEndpoint.CRDGroup
+ }
+ if tr.originEndpoint != nil {
+ return tr.originEndpoint.Group
+ }
+ if tr.webhookRule != nil {
+ return tr.webhookRule.Group
+ }
+ return ""
+}
+
+func (tr *TargetRequest) OrigResourceType() string {
+ if tr.IsCRD() {
+ return tr.originEndpoint.CRDResourceType
+ }
+ if tr.originEndpoint != nil {
+ return tr.originEndpoint.ResourceType
+ }
+ if tr.webhookRule != nil {
+ return tr.webhookRule.Resource
+ }
+ return ""
+}
+
+func (tr *TargetRequest) RawQuery() string {
+ if tr.targetEndpoint != nil {
+ return tr.targetEndpoint.RawQuery
+ }
+ if tr.originEndpoint != nil {
+ return tr.originEndpoint.RawQuery
+ }
+ return ""
+}
+
+// ShouldRewriteRequest returns true if incoming payload should
+// be rewritten.
+func (tr *TargetRequest) ShouldRewriteRequest() bool {
+ // Consider known webhook should be rewritten. Unknown paths will be passed as-is.
+ if tr.webhookRule != nil {
+ return true
+ }
+
+ if tr.originEndpoint != nil {
+ if tr.originEndpoint.IsRoot || tr.originEndpoint.IsUnknown {
+ return false
+ }
+
+ if tr.targetEndpoint == nil {
+ // Pass resources without rules as is, except some special types.
+
+ if tr.originEndpoint.IsCore {
+ switch tr.originEndpoint.ResourceType {
+ case "pods":
+ return true
+ }
+ }
+
+ switch tr.originEndpoint.ResourceType {
+ case "mutatingwebhookconfigurations",
+ "validatingwebhookconfigurations",
+ "clusterroles",
+ "roles":
+ return true
+ }
+
+ // Rewrite request body when creating CRD.
+ if tr.originEndpoint.ResourceType == "customresourcedefinitions" && tr.originEndpoint.Name == "" {
+ return true
+ }
+
+ // Should not rewrite request if path is not rewritten.
+ return false
+ }
+ }
+
+ // Payload should be inspected to decide if rewrite is required.
+ return true
+}
+
+// ShouldRewriteResponse return true if response rewrite is needed.
+// Response may be passed as is if false.
+func (tr *TargetRequest) ShouldRewriteResponse() bool {
+ // If there is webhook rule, response should be rewritten.
+ if tr.webhookRule != nil {
+ return true
+ }
+
+ if tr.originEndpoint == nil {
+ return false
+ }
+
+ if tr.originEndpoint.IsRoot || tr.originEndpoint.IsUnknown {
+ return false
+ }
+
+ // Some core resources should be rewritten.
+ if tr.originEndpoint.IsCore {
+ switch tr.originEndpoint.ResourceType {
+ case "pods":
+ return true
+ // pods should be rewritten
+ }
+ return false
+ }
+
+ if tr.originEndpoint.IsCRD {
+ // Rewrite CRD List.
+ if tr.originEndpoint.Name == "" {
+ return true
+ }
+ // Rewrite CRD if group and resource was rewritten.
+ if tr.originEndpoint.Name != "" && tr.targetEndpoint != nil {
+ return true
+ }
+ return false
+ }
+
+ // Rewrite if path was rewritten for known resource.
+ if tr.targetEndpoint != nil {
+ return true
+ }
+
+ // Rewrite response from /apis discovery.
+ if tr.originEndpoint.Group == "" {
+ return true
+ }
+
+ // Rewrite special resources.
+ switch tr.originEndpoint.ResourceType {
+ // Webhook configurations should be rewritten.
+ case "mutatingwebhookconfigurations",
+ "validatingwebhookconfigurations",
+ "clusterroles":
+ return true
+ }
+
+ return false
+}
+
+func (tr *TargetRequest) ResourceForLog() string {
+ if tr.webhookRule != nil {
+ return tr.webhookRule.Resource
+ }
+ if tr.originEndpoint != nil {
+ ep := tr.originEndpoint
+ if ep.IsRoot {
+ return "ROOT"
+ }
+ if ep.IsUnknown {
+ return "UKNOWN"
+ }
+ if ep.IsCore {
+ // /api
+ if ep.Version == "" {
+ return "APIVersions/core"
+ }
+ // /api/v1
+ if ep.ResourceType == "" {
+ return "APIResourceList/core"
+ }
+ // /api/v1/RESOURCE/NAME/SUBRESOURCE
+ // /api/v1/namespaces/NS/status
+ // /api/v1/namespaces/NS/RESOURCE/NAME/SUBRESOURCE
+ if ep.Subresource != "" {
+ return ep.ResourceType + "/" + ep.Subresource
+ }
+ // /api/v1/RESOURCETYPE
+ // /api/v1/RESOURCETYPE/NAME
+ // /api/v1/namespaces
+ // /api/v1/namespaces/NAMESPACE
+ // /api/v1/namespaces/NAMESPACE/RESOURCETYPE
+ // /api/v1/namespaces/NAMESPACE/RESOURCETYPE/NAME
+ return ep.ResourceType
+ }
+ // /apis
+ if ep.Group == "" {
+ return "APIGroupList"
+ }
+ // /apis/GROUP
+ if ep.Version == "" {
+ return "APIGroup/" + ep.Group
+ }
+ // /apis/GROUP/VERSION
+ if ep.ResourceType == "" {
+ return "APIResourceList/" + ep.Group
+ }
+ // /apis/GROUP/VERSION/RESOURCETYPE/NAME/SUBRESOURCE
+ // /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
+ if ep.Subresource != "" {
+ return ep.ResourceType + "/" + ep.Subresource
+ }
+ // /apis/GROUP/VERSION/RESOURCETYPE
+ // /apis/GROUP/VERSION/RESOURCETYPE/NAME
+ // /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE
+ // /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME
+ return ep.ResourceType
+ }
+
+ return "UNKNOWN"
+}
diff --git a/images/kube-api-proxy/pkg/rewriter/webhook.go b/images/kube-api-proxy/pkg/rewriter/webhook.go
new file mode 100644
index 000000000..4a51c74cb
--- /dev/null
+++ b/images/kube-api-proxy/pkg/rewriter/webhook.go
@@ -0,0 +1 @@
+package rewriter
diff --git a/images/kube-api-proxy/pkg/server/http_server.go b/images/kube-api-proxy/pkg/server/http_server.go
new file mode 100644
index 000000000..4858064b1
--- /dev/null
+++ b/images/kube-api-proxy/pkg/server/http_server.go
@@ -0,0 +1,124 @@
+package server
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ log "log/slog"
+ "net"
+ "net/http"
+ "sync"
+
+ logutil "kube-api-proxy/pkg/log"
+)
+
+// HTTPServer starts HTTP server with root handler using listen address.
+// Implements Runnable interface to be able to stop server.
+type HTTPServer struct {
+ InstanceDesc string
+ ListenAddr string
+ RootHandler http.Handler
+ CertFile string
+ KeyFile string
+ Err error
+
+ initLock sync.Mutex
+ stopped bool
+
+ listener net.Listener
+ instance *http.Server
+}
+
+// init checks if listen is possible and creates new HTTP server instance.
+// initLock is used to avoid data races with the Stop method.
+func (s *HTTPServer) init() bool {
+ s.initLock.Lock()
+ defer s.initLock.Unlock()
+ if s.stopped {
+ // Stop was called earlier.
+ return false
+ }
+
+ l, err := net.Listen("tcp", s.ListenAddr)
+ if err != nil {
+ s.Err = err
+ log.Error(fmt.Sprintf("%s: listen on %s err: %s", s.InstanceDesc, s.ListenAddr, err))
+ return false
+ }
+ s.listener = l
+ log.Info(fmt.Sprintf("%s: listen for incoming requests on %s", s.InstanceDesc, s.ListenAddr))
+
+ mux := http.NewServeMux()
+ mux.Handle("/", s.RootHandler)
+
+ s.instance = &http.Server{
+ Handler: mux,
+ }
+ return true
+}
+
+func (s *HTTPServer) Start() {
+ if !s.init() {
+ return
+ }
+
+ // Start serving HTTP requests, block until server instance stops or returns an error.
+ var err error
+ if s.CertFile != "" && s.KeyFile != "" {
+ err = s.instance.ServeTLS(s.listener, s.CertFile, s.KeyFile)
+ } else {
+ err = s.instance.Serve(s.listener)
+ }
+ // Ignore closed error: it's a consequence of stop.
+ if err != nil {
+ switch {
+ case errors.Is(err, http.ErrServerClosed):
+ case errors.Is(err, net.ErrClosed):
+ default:
+ s.Err = err
+ }
+ }
+ return
+}
+
+// Stop shutdowns HTTP server instance and close a done channel.
+// Stop and init may be run in parallel, so initLock is used to wait until
+// variables are initialized.
+func (s *HTTPServer) Stop() {
+ s.initLock.Lock()
+ defer s.initLock.Unlock()
+
+ if s.stopped {
+ return
+ }
+ s.stopped = true
+
+ // Shutdown instance if it was initialized.
+ if s.instance != nil {
+ log.Info(fmt.Sprintf("%s: stop", s.InstanceDesc))
+ err := s.instance.Shutdown(context.Background())
+ // Ignore ErrClosed.
+ if err != nil {
+ switch {
+ case errors.Is(err, http.ErrServerClosed):
+ case errors.Is(err, net.ErrClosed):
+ case s.Err != nil:
+ // log error to not reset runtime error.
+ log.Error(fmt.Sprintf("%s: stop instance", s.InstanceDesc), logutil.SlogErr(err))
+ default:
+ s.Err = err
+ }
+ }
+ }
+}
+
+// ConstructListenAddr return ip:port with defaults.
+func ConstructListenAddr(addr, port, defaultAddr, defaultPort string) string {
+ if addr == "" {
+ addr = defaultAddr
+ }
+ if port == "" {
+ port = defaultPort
+ }
+ return addr + ":" + port
+}
diff --git a/images/kube-api-proxy/pkg/server/runnable_group.go b/images/kube-api-proxy/pkg/server/runnable_group.go
new file mode 100644
index 000000000..e60120423
--- /dev/null
+++ b/images/kube-api-proxy/pkg/server/runnable_group.go
@@ -0,0 +1,74 @@
+package server
+
+import (
+ "sync"
+)
+
+type Runnable interface {
+ Start()
+ Stop()
+}
+
+// RunnableGroup is a group of Runnables that should run until one of them stops.
+type RunnableGroup struct {
+ runnables []Runnable
+}
+
+func NewRunnableGroup() *RunnableGroup {
+ return &RunnableGroup{
+ runnables: make([]Runnable, 0),
+ }
+}
+
+// Add register Runnable in a group.
+// Note: not designed for parallel registering.
+func (rg *RunnableGroup) Add(r Runnable) {
+ rg.runnables = append(rg.runnables, r)
+}
+
+// Start starts all Runnables and stops all of them when at least one Runnable stops.
+func (rg *RunnableGroup) Start() {
+ // Start all runnables.
+ oneStoppedCh := rg.startAll()
+
+ // Block until one runnable is stopped.
+ <-oneStoppedCh
+
+ // Wait until all Runnables stop.
+ rg.stopAll()
+}
+
+// startAll calls Start for each Runnable in separate go routines.
+// It waits until all go routines starts.
+// It returns a channel, so caller can receive event when one of the Runnables stops.
+func (rg *RunnableGroup) startAll() chan struct{} {
+ oneStopped := make(chan struct{})
+ var closeOnce sync.Once
+
+ for i := range rg.runnables {
+ r := rg.runnables[i]
+ go func() {
+ r.Start()
+ closeOnce.Do(func() {
+ close(oneStopped)
+ })
+ }()
+ }
+
+ return oneStopped
+}
+
+// stopAll calls Stop for each Runnable in a separate go routine.
+// It waits until all go routines starts.
+func (rg *RunnableGroup) stopAll() {
+ var wg sync.WaitGroup
+ wg.Add(len(rg.runnables))
+ for i := range rg.runnables {
+ r := rg.runnables[i]
+ go func() {
+ r.Stop()
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+}
diff --git a/images/kube-api-proxy/pkg/target/kubernetes.go b/images/kube-api-proxy/pkg/target/kubernetes.go
new file mode 100644
index 000000000..ad2757adc
--- /dev/null
+++ b/images/kube-api-proxy/pkg/target/kubernetes.go
@@ -0,0 +1,39 @@
+package target
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "k8s.io/client-go/rest"
+ "sigs.k8s.io/controller-runtime/pkg/client/config"
+)
+
+type Kubernetes struct {
+ Config *rest.Config
+ Client *http.Client
+ APIServerURL *url.URL
+}
+
+func NewKubernetesTarget() (*Kubernetes, error) {
+ var err error
+ k := &Kubernetes{}
+
+ k.Config, err = config.GetConfig()
+ if err != nil {
+ return nil, fmt.Errorf("load Kubernetes client config: %w", err)
+ }
+
+ // Configure HTTP client to Kubernetes API server.
+ k.Client, err = rest.HTTPClientFor(k.Config)
+ if err != nil {
+ return nil, fmt.Errorf("setup Kubernetes API http client: %w", err)
+ }
+
+ k.APIServerURL, err = url.Parse(k.Config.Host)
+ if err != nil {
+ return nil, fmt.Errorf("parse API server URL: %w", err)
+ }
+
+ return k, nil
+}
diff --git a/images/kube-api-proxy/pkg/target/webhook.go b/images/kube-api-proxy/pkg/target/webhook.go
new file mode 100644
index 000000000..5f8c23788
--- /dev/null
+++ b/images/kube-api-proxy/pkg/target/webhook.go
@@ -0,0 +1,87 @@
+package target
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "time"
+)
+
+type Webhook struct {
+ Client *http.Client
+ URL *url.URL
+ CertFile string
+ KeyFile string
+}
+
+const (
+ WebhookAddressVar = "WEBHOOK_ADDRESS"
+ WebhookServerNameVar = "WEBHOOK_SERVER_NAME"
+ WebhookCertFileVar = "WEBHOOK_CERT_FILE"
+ WebhookKeyFileVar = "WEBHOOK_KEY_FILE"
+)
+
+var (
+ defaultWebhookTimeout = 30 * time.Second
+ defaultWebhookAddress = "https://127.0.0.1:9443"
+)
+
+func NewWebhookTarget() (*Webhook, error) {
+ var err error
+ webhook := &Webhook{}
+
+ // Target address and serverName.
+ address := os.Getenv(WebhookAddressVar)
+ if address == "" {
+ address = defaultWebhookAddress
+ }
+
+ serverName := os.Getenv(WebhookServerNameVar)
+ if serverName == "" {
+ serverName = address
+ }
+
+ webhook.URL, err = url.Parse(address)
+ if err != nil {
+ return nil, err
+ }
+
+ // Certificate settings.
+ certFile := os.Getenv(WebhookCertFileVar)
+ keyFile := os.Getenv(WebhookKeyFileVar)
+ if certFile == "" && keyFile != "" {
+ return nil, fmt.Errorf("should specify cert file in %s if %s is not empty", WebhookCertFileVar, WebhookKeyFileVar)
+ }
+ if certFile != "" && keyFile == "" {
+ return nil, fmt.Errorf("should specify key file in %s if %s is not empty", WebhookKeyFileVar, WebhookCertFileVar)
+ }
+ webhook.CertFile = certFile
+ webhook.KeyFile = keyFile
+
+ // Construct TLS client without validation to connect to the local webhook server.
+ dialer := &net.Dialer{
+ Timeout: defaultWebhookTimeout,
+ }
+
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: true,
+ ServerName: serverName,
+ },
+ DisableKeepAlives: true,
+ IdleConnTimeout: 5 * time.Minute,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ DialContext: dialer.DialContext,
+ }
+
+ webhook.Client = &http.Client{
+ Transport: tr,
+ Timeout: defaultWebhookTimeout,
+ }
+
+ return webhook, nil
+}
diff --git a/images/kube-api-proxy/werf.inc.yaml b/images/kube-api-proxy/werf.inc.yaml
new file mode 100644
index 000000000..459209398
--- /dev/null
+++ b/images/kube-api-proxy/werf.inc.yaml
@@ -0,0 +1,39 @@
+---
+image: {{ $.ImageName }}-builder
+fromImage: base-golang-21-bookworm
+git:
+ - add: /images/{{ $.ImageName }}
+ to: /src/kube-api-proxy
+ stageDependencies:
+ install:
+ - go.mod
+ - go.sum
+ setup:
+ - "**/*.go"
+mount:
+ - fromPath: ~/go-pkg-cache
+ to: /go/pkg
+shell:
+ install:
+ - cd /src/kube-api-proxy
+ - go mod download
+ setup:
+ - cd /src/kube-api-proxy
+ - export GO111MODULE=on
+ - export GOOS=linux
+ - export CGO_ENABLED=0
+ - export GOARCH=amd64
+ - go build -v -a -o kube-api-proxy ./cmd/kube-api-proxy
+
+---
+image: {{ $.ImageName }}
+fromImage: base-ubuntu-jammy
+import:
+ - image: {{ $.ImageName }}-builder
+ add: /src/kube-api-proxy/kube-api-proxy
+ to: /app/kube-api-proxy
+ after: install
+docker:
+ USER: "65532:65532"
+ WORKDIR: "/app"
+ ENTRYPOINT: ["/app/kube-api-proxy"]
diff --git a/images/libguestfs/werf.inc.yaml b/images/libguestfs/werf.inc.yaml
index ca0100d12..760b53540 100644
--- a/images/libguestfs/werf.inc.yaml
+++ b/images/libguestfs/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: virt-artifact
+- image: virt-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/pre-delete-hook/entrypoint.sh b/images/pre-delete-hook/entrypoint.sh
index de2c576a6..b0a404abc 100644
--- a/images/pre-delete-hook/entrypoint.sh
+++ b/images/pre-delete-hook/entrypoint.sh
@@ -1,13 +1,13 @@
#!/bin/bash
set -eu -o pipefail
-KUBEVIRT_RESOURCE="kubevirts.x.virtualization.deckhouse.io"
+KUBEVIRT_RESOURCE="dvpinternalkubevirts.internal.virtualization.deckhouse.io"
echo "Delete Kubevirt configuration ..."
kubectl delete -n d8-virtualization ${KUBEVIRT_RESOURCE} kubevirt || true
echo "Wait for Kubevirt deletion ..."
kubectl wait --for=delete -n d8-virtualization ${KUBEVIRT_RESOURCE} kubevirt --timeout=180s || true
-CDI_RESOURCE="cdis.x.virtualization.deckhouse.io"
+CDI_RESOURCE="dvpinternalcdis.internal.virtualization.deckhouse.io"
echo "Delete CDI configuration ..."
kubectl delete ${CDI_RESOURCE} cdi || true
echo "Wait for CDI deletion ..."
diff --git a/images/virt-api/werf.inc.yaml b/images/virt-api/werf.inc.yaml
index 0f698f4df..d7a6d107b 100644
--- a/images/virt-api/werf.inc.yaml
+++ b/images/virt-api/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: virt-artifact
+- image: virt-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/virt-artifact/patches/010-override-crds.patch b/images/virt-artifact/patches/010-override-crds.patch
deleted file mode 100644
index 11b2a5446..000000000
--- a/images/virt-artifact/patches/010-override-crds.patch
+++ /dev/null
@@ -1,992 +0,0 @@
-diff --git a/pkg/storage/export/export/export.go b/pkg/storage/export/export/export.go
-index 51eb69df6..7a8a29381 100644
---- a/pkg/storage/export/export/export.go
-+++ b/pkg/storage/export/export/export.go
-@@ -140,7 +140,7 @@ var exportGVK = schema.GroupVersionKind{
- }
-
- var datavolumeGVK = schema.GroupVersionKind{
-- Group: cdiv1.SchemeGroupVersion.Group,
-+ Group: "x.virtualization.deckhouse.io",
- Version: cdiv1.SchemeGroupVersion.Version,
- Kind: "DataVolume",
- }
-diff --git a/pkg/storage/export/export/links.go b/pkg/storage/export/export/links.go
-index 38bdb9410..5dfd44224 100644
---- a/pkg/storage/export/export/links.go
-+++ b/pkg/storage/export/export/links.go
-@@ -32,6 +32,7 @@ import (
- corev1 "k8s.io/api/core/v1"
- networkingv1 "k8s.io/api/networking/v1"
-
-+ "kubevirt.io/api/export"
- exportv1 "kubevirt.io/api/export/v1alpha1"
-
- "kubevirt.io/kubevirt/pkg/certificates/triple/cert"
-@@ -45,7 +46,7 @@ const (
- routeCaKey = "ca.crt"
- subjectAltNameId = "2.5.29.17"
-
-- apiGroup = "export.kubevirt.io"
-+ apiGroup = export.GroupName
- apiVersion = "v1alpha1"
- exportResourceName = "virtualmachineexports"
- gv = apiGroup + "/" + apiVersion
-diff --git a/pkg/storage/export/virt-exportserver/exportserver.go b/pkg/storage/export/virt-exportserver/exportserver.go
-index 388f9c752..8e20fe5c4 100644
---- a/pkg/storage/export/virt-exportserver/exportserver.go
-+++ b/pkg/storage/export/virt-exportserver/exportserver.go
-@@ -579,7 +579,7 @@ func vmHandler(filePath string, vi []VolumeInfo, getBasePath func() (string, err
- for _, dv := range datavolumes {
- dv.TypeMeta = metav1.TypeMeta{
- Kind: "DataVolume",
-- APIVersion: "cdi.kubevirt.io/v1beta1",
-+ APIVersion: "x.virtualization.deckhouse.io/v1beta1",
- }
- for _, info := range vi {
- if strings.Contains(info.RawGzURI, dv.Name) {
-diff --git a/pkg/virt-api/definitions/definitions.go b/pkg/virt-api/definitions/definitions.go
-index 94443cbe3..b71cca384 100644
---- a/pkg/virt-api/definitions/definitions.go
-+++ b/pkg/virt-api/definitions/definitions.go
-@@ -55,8 +55,33 @@ const (
- )
-
- func ComposeAPIDefinitions() []*restful.WebService {
-- var result []*restful.WebService
-- for _, f := range []func() []*restful.WebService{
-+ return xGVApiServiceDefinitions(v1.GroupVersion)
-+}
-+
-+func xGVApiServiceDefinitions(xGV schema.GroupVersion) []*restful.WebService {
-+ ws := new(restful.WebService)
-+ ws.Doc("The KubeVirt and CDI API, a virtual machine management.")
-+
-+ ws.Route(
-+ ws.GET("/").Produces(mime.MIME_JSON).Writes(metav1.APIResourceList{}).
-+ To(noop).
-+ Operation(fmt.Sprintf("getAPIResources-%s", xGV.Group)).
-+ Doc("Get KubeVirt and CDI API Resources").
-+ Returns(http.StatusOK, "OK", metav1.APIResourceList{}).
-+ Returns(http.StatusNotFound, "Not Found", ""),
-+ )
-+
-+ ws2 := new(restful.WebService)
-+ ws2.Path(GroupBasePath(xGV))
-+ ws2.Route(ws2.GET("/").
-+ Produces(mime.MIME_JSON).Writes(metav1.APIGroup{}).
-+ To(noop).
-+ Doc("Get a KubeVirt and CDI API group").
-+ Operation("getAPIGroup-"+xGV.Group).
-+ Returns(http.StatusOK, "OK", metav1.APIGroup{}).
-+ Returns(http.StatusNotFound, "Not Found", ""))
-+
-+ for _, f := range []func(ws *restful.WebService) *restful.WebService{
- kubevirtApiServiceDefinitions,
- snapshotApiServiceDefinitions,
- exportApiServiceDefinitions,
-@@ -65,13 +90,13 @@ func ComposeAPIDefinitions() []*restful.WebService {
- poolApiServiceDefinitions,
- vmCloneDefinitions,
- } {
-- result = append(result, f()...)
-+ ws = f(ws)
- }
-
-- return result
-+ return []*restful.WebService{ws, ws2}
- }
-
--func kubevirtApiServiceDefinitions() []*restful.WebService {
-+func kubevirtApiServiceDefinitions(ws *restful.WebService) *restful.WebService {
- vmiGVR := schema.GroupVersionResource{Group: v1.GroupVersion.Group, Version: v1.GroupVersion.Version, Resource: "virtualmachineinstances"}
- vmirsGVR := schema.GroupVersionResource{Group: v1.GroupVersion.Group, Version: v1.GroupVersion.Version, Resource: "virtualmachineinstancereplicasets"}
- vmipGVR := schema.GroupVersionResource{Group: v1.GroupVersion.Group, Version: v1.GroupVersion.Version, Resource: "virtualmachineinstancepresets"}
-@@ -79,11 +104,7 @@ func kubevirtApiServiceDefinitions() []*restful.WebService {
- migrationGVR := schema.GroupVersionResource{Group: v1.GroupVersion.Group, Version: v1.GroupVersion.Version, Resource: "virtualmachineinstancemigrations"}
- kubeVirtGVR := schema.GroupVersionResource{Group: v1.GroupVersion.Group, Version: v1.GroupVersion.Version, Resource: "kubevirt"}
-
-- ws, err := groupVersionProxyBase(v1.GroupVersion)
-- if err != nil {
-- panic(err)
-- }
--
-+ var err error
- ws, err = genericNamespacedResourceProxy(ws, kubeVirtGVR, &v1.KubeVirt{}, v1.KubeVirtGroupVersionKind.Kind, &v1.KubeVirtList{})
- if err != nil {
- panic(err)
-@@ -113,24 +134,15 @@ func kubevirtApiServiceDefinitions() []*restful.WebService {
- panic(err)
- }
-
-- ws2, err := resourceProxyAutodiscovery(vmiGVR)
-- if err != nil {
-- panic(err)
-- }
--
-- return []*restful.WebService{ws, ws2}
-+ return ws
- }
-
--func snapshotApiServiceDefinitions() []*restful.WebService {
-+func snapshotApiServiceDefinitions(ws *restful.WebService) *restful.WebService {
- vmsGVR := snapshotv1.SchemeGroupVersion.WithResource("virtualmachinesnapshots")
- vmscGVR := snapshotv1.SchemeGroupVersion.WithResource("virtualmachinesnapshotcontents")
- vmrGVR := snapshotv1.SchemeGroupVersion.WithResource("virtualmachinerestores")
-
-- ws, err := groupVersionProxyBase(schema.GroupVersion{Group: snapshotv1.SchemeGroupVersion.Group, Version: snapshotv1.SchemeGroupVersion.Version})
-- if err != nil {
-- panic(err)
-- }
--
-+ var err error
- ws, err = genericNamespacedResourceProxy(ws, vmsGVR, &snapshotv1.VirtualMachineSnapshot{}, "VirtualMachineSnapshot", &snapshotv1.VirtualMachineSnapshotList{})
- if err != nil {
- panic(err)
-@@ -146,64 +158,40 @@ func snapshotApiServiceDefinitions() []*restful.WebService {
- panic(err)
- }
-
-- ws2, err := resourceProxyAutodiscovery(vmsGVR)
-- if err != nil {
-- panic(err)
-- }
-- return []*restful.WebService{ws, ws2}
-+ return ws
- }
-
--func exportApiServiceDefinitions() []*restful.WebService {
-+func exportApiServiceDefinitions(ws *restful.WebService) *restful.WebService {
- exportsGVR := exportv1.SchemeGroupVersion.WithResource("virtualmachineexports")
-
-- ws, err := groupVersionProxyBase(schema.GroupVersion{Group: exportv1.SchemeGroupVersion.Group, Version: exportv1.SchemeGroupVersion.Version})
-- if err != nil {
-- panic(err)
-- }
--
-+ var err error
- ws, err = genericNamespacedResourceProxy(ws, exportsGVR, &exportv1.VirtualMachineExport{}, "VirtualMachineExport", &exportv1.VirtualMachineExportList{})
- if err != nil {
- panic(err)
- }
-
-- ws2, err := resourceProxyAutodiscovery(exportsGVR)
-- if err != nil {
-- panic(err)
-- }
-- return []*restful.WebService{ws, ws2}
-+ return ws
- }
-
--func migrationPoliciesApiServiceDefinitions() []*restful.WebService {
-+func migrationPoliciesApiServiceDefinitions(ws *restful.WebService) *restful.WebService {
- mpGVR := migrationsv1.SchemeGroupVersion.WithResource(migrations.ResourceMigrationPolicies)
-
-- ws, err := groupVersionProxyBase(schema.GroupVersion{Group: migrationsv1.SchemeGroupVersion.Group, Version: migrationsv1.SchemeGroupVersion.Version})
-- if err != nil {
-- panic(err)
-- }
--
-+ var err error
- ws, err = genericClusterResourceProxy(ws, mpGVR, &migrationsv1.MigrationPolicy{}, migrationsv1.MigrationPolicyKind.Kind, &migrationsv1.MigrationPolicyList{})
- if err != nil {
- panic(err)
- }
-
-- ws2, err := resourceProxyAutodiscovery(mpGVR)
-- if err != nil {
-- panic(err)
-- }
-- return []*restful.WebService{ws, ws2}
-+ return ws
- }
-
--func instancetypeApiServiceDefinitions() []*restful.WebService {
-+func instancetypeApiServiceDefinitions(ws *restful.WebService) *restful.WebService {
- instancetypeGVR := instancetypev1beta1.SchemeGroupVersion.WithResource(instancetype.PluralResourceName)
- clusterInstancetypeGVR := instancetypev1beta1.SchemeGroupVersion.WithResource(instancetype.ClusterPluralResourceName)
- preferenceGVR := instancetypev1beta1.SchemeGroupVersion.WithResource(instancetype.PluralPreferenceResourceName)
- clusterPreferenceGVR := instancetypev1beta1.SchemeGroupVersion.WithResource(instancetype.ClusterPluralPreferenceResourceName)
-
-- ws, err := groupVersionProxyBase(instancetypev1beta1.SchemeGroupVersion)
-- if err != nil {
-- panic(err)
-- }
--
-+ var err error
- ws, err = genericNamespacedResourceProxy(ws, instancetypeGVR, &instancetypev1beta1.VirtualMachineInstancetype{}, "VirtualMachineInstancetype", &instancetypev1beta1.VirtualMachineInstancetypeList{})
- if err != nil {
- panic(err)
-@@ -224,53 +212,31 @@ func instancetypeApiServiceDefinitions() []*restful.WebService {
- panic(err)
- }
-
-- ws2, err := resourceProxyAutodiscovery(instancetypeGVR)
-- if err != nil {
-- panic(err)
-- }
--
-- return []*restful.WebService{ws, ws2}
-+ return ws
- }
-
--func poolApiServiceDefinitions() []*restful.WebService {
-+func poolApiServiceDefinitions(ws *restful.WebService) *restful.WebService {
- poolGVR := poolv1alpha1.SchemeGroupVersion.WithResource("virtualmachinepools")
-
-- ws, err := groupVersionProxyBase(poolv1alpha1.SchemeGroupVersion)
-- if err != nil {
-- panic(err)
-- }
--
-+ var err error
- ws, err = genericNamespacedResourceProxy(ws, poolGVR, &poolv1alpha1.VirtualMachinePool{}, "VirtualMachinePool", &poolv1alpha1.VirtualMachinePoolList{})
- if err != nil {
- panic(err)
- }
-
-- ws2, err := resourceProxyAutodiscovery(poolGVR)
-- if err != nil {
-- panic(err)
-- }
--
-- return []*restful.WebService{ws, ws2}
-+ return ws
- }
-
--func vmCloneDefinitions() []*restful.WebService {
-+func vmCloneDefinitions(ws *restful.WebService) *restful.WebService {
- mpGVR := clonev1lpha1.SchemeGroupVersion.WithResource(clone.ResourceVMClonePlural)
-
-- ws, err := groupVersionProxyBase(schema.GroupVersion{Group: clonev1lpha1.SchemeGroupVersion.Group, Version: clonev1lpha1.SchemeGroupVersion.Version})
-- if err != nil {
-- panic(err)
-- }
--
-+ var err error
- ws, err = genericClusterResourceProxy(ws, mpGVR, &clonev1lpha1.VirtualMachineClone{}, clonev1lpha1.VirtualMachineCloneKind.Kind, &clonev1lpha1.VirtualMachineCloneList{})
- if err != nil {
- panic(err)
- }
-
-- ws2, err := resourceProxyAutodiscovery(mpGVR)
-- if err != nil {
-- panic(err)
-- }
-- return []*restful.WebService{ws, ws2}
-+ return ws
- }
-
- func groupVersionProxyBase(gv schema.GroupVersion) (*restful.WebService, error) {
-@@ -295,25 +261,25 @@ func genericNamespacedResourceProxy(ws *restful.WebService, gvr schema.GroupVers
- listExample := reflect.ValueOf(objListPointer).Elem().Interface()
-
- ws.Route(addNamespaceParam(ws,
-- createOperation(ws, NamespacedResourceBasePath(gvr), objExample).
-+ createOperation(ws, BaseNamespacedResourceBasePath(gvr), objExample).
- Operation("createNamespaced"+objKind).
- Doc("Create a "+objKind+obj),
- ))
-
- ws.Route(addNamespaceParam(ws,
-- replaceOperation(ws, NamespacedResourcePath(gvr), objExample).
-+ replaceOperation(ws, BaseNamespacedResourcePath(gvr), objExample).
- Operation("replaceNamespaced"+objKind).
- Doc("Update a "+objKind+obj),
- ))
-
- ws.Route(addNamespaceParam(ws,
-- deleteOperation(ws, NamespacedResourcePath(gvr)).
-+ deleteOperation(ws, BaseNamespacedResourcePath(gvr)).
- Operation("deleteNamespaced"+objKind).
- Doc("Delete a "+objKind+obj),
- ))
-
- ws.Route(addNamespaceParam(ws,
-- readOperation(ws, NamespacedResourcePath(gvr), objExample).
-+ readOperation(ws, BaseNamespacedResourcePath(gvr), objExample).
- Operation("readNamespaced"+objKind).
- Doc("Get a "+objKind+obj),
- ))
-@@ -325,7 +291,7 @@ func genericNamespacedResourceProxy(ws *restful.WebService, gvr schema.GroupVers
- )
-
- ws.Route(addNamespaceParam(ws,
-- patchOperation(ws, NamespacedResourcePath(gvr), objExample).
-+ patchOperation(ws, BaseNamespacedResourcePath(gvr), objExample).
- Operation("patchNamespaced"+objKind).
- Doc("Patch a "+objKind+obj),
- ))
-@@ -339,19 +305,19 @@ func genericNamespacedResourceProxy(ws *restful.WebService, gvr schema.GroupVers
-
- // TODO, implement watch. For now it is here to provide swagger doc only
- ws.Route(addNamespaceParam(ws,
-- watchOperation(ws, "/watch"+NamespacedResourceBasePath(gvr)).
-+ watchOperation(ws, "/watch"+BaseNamespacedResourceBasePath(gvr)).
- Operation("watchNamespaced"+objKind).
- Doc(watch+objKind+obj),
- ))
-
- ws.Route(addNamespaceParam(ws,
-- listOperation(ws, NamespacedResourceBasePath(gvr), listExample).
-+ listOperation(ws, BaseNamespacedResourceBasePath(gvr), listExample).
- Operation("listNamespaced"+objKind).
- Doc("Get a list of "+objKind+objs),
- ))
-
- ws.Route(
-- deleteCollectionOperation(ws, NamespacedResourceBasePath(gvr)).
-+ deleteCollectionOperation(ws, BaseNamespacedResourceBasePath(gvr)).
- Operation("deleteCollectionNamespaced" + objKind).
- Doc("Delete a collection of " + objKind + objs),
- )
-@@ -642,10 +608,22 @@ func GroupVersionBasePath(gvr schema.GroupVersion) string {
- return fmt.Sprintf("/apis/%s/%s", gvr.Group, gvr.Version)
- }
-
-+// BaseNamespacedResourceBasePath concatenates NamespacedResourceBasePath result with group version base prefix.
-+// WebService no longer uses the root path with group version, so it's necessary to set group version path here.
-+func BaseNamespacedResourceBasePath(gvr schema.GroupVersionResource) string {
-+ return GroupVersionBasePath(gvr.GroupVersion()) + NamespacedResourceBasePath(gvr)
-+}
-+
- func NamespacedResourceBasePath(gvr schema.GroupVersionResource) string {
- return fmt.Sprintf("/namespaces/{namespace:[a-z0-9][a-z0-9\\-]*}/%s", gvr.Resource)
- }
-
-+// BaseNamespacedResourcePath concatenates NamespacedResourcePath result with group version base prefix.
-+// WebService no longer uses the root path with group version, so it's necessary to set group version path here.
-+func BaseNamespacedResourcePath(gvr schema.GroupVersionResource) string {
-+ return GroupVersionBasePath(gvr.GroupVersion()) + NamespacedResourcePath(gvr)
-+}
-+
- func NamespacedResourcePath(gvr schema.GroupVersionResource) string {
- return fmt.Sprintf("/namespaces/{namespace:[a-z0-9][a-z0-9\\-]*}/%s/{name:[a-z0-9][a-z0-9\\-]*}", gvr.Resource)
- }
-diff --git a/pkg/virt-controller/watch/clone/util.go b/pkg/virt-controller/watch/clone/util.go
-index cb66290f7..9cce76b10 100644
---- a/pkg/virt-controller/watch/clone/util.go
-+++ b/pkg/virt-controller/watch/clone/util.go
-@@ -16,12 +16,13 @@ import (
- "k8s.io/apimachinery/pkg/util/rand"
-
- clonev1alpha1 "kubevirt.io/api/clone/v1alpha1"
-+ coreapi "kubevirt.io/api/core"
- v1 "kubevirt.io/api/core/v1"
- )
-
- const (
- vmKind = "VirtualMachine"
-- kubevirtApiGroup = "kubevirt.io"
-+ kubevirtApiGroup = coreapi.GroupName
- )
-
- // variable so can be overridden in tests
-diff --git a/pkg/virt-operator/resource/generate/components/crds.go b/pkg/virt-operator/resource/generate/components/crds.go
-index 822f3d82b..23f3a96a6 100644
---- a/pkg/virt-operator/resource/generate/components/crds.go
-+++ b/pkg/virt-operator/resource/generate/components/crds.go
-@@ -156,9 +156,9 @@ func NewVirtualMachineInstanceCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "virtualmachineinstances",
- Singular: "virtualmachineinstance",
- Kind: virtv1.VirtualMachineInstanceGroupVersionKind.Kind,
-- ShortNames: []string{"vmi", "vmis"},
-+ ShortNames: []string{"xvmi", "xvmis"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -194,9 +194,9 @@ func NewVirtualMachineCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "virtualmachines",
- Singular: "virtualmachine",
- Kind: virtv1.VirtualMachineGroupVersionKind.Kind,
-- ShortNames: []string{"vm", "vms"},
-+ ShortNames: []string{"xvm", "xvms"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -244,9 +244,9 @@ func NewPresetCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "virtualmachineinstancepresets",
- Singular: "virtualmachineinstancepreset",
- Kind: virtv1.VirtualMachineInstancePresetGroupVersionKind.Kind,
-- ShortNames: []string{"vmipreset", "vmipresets"},
-+ ShortNames: []string{"xvmipreset", "xvmipresets"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -271,9 +271,9 @@ func NewReplicaSetCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "virtualmachineinstancereplicasets",
- Singular: "virtualmachineinstancereplicaset",
- Kind: virtv1.VirtualMachineInstanceReplicaSetGroupVersionKind.Kind,
-- ShortNames: []string{"vmirs", "vmirss"},
-+ ShortNames: []string{"xvmirs", "xvmirss"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -316,9 +316,9 @@ func NewVirtualMachineInstanceMigrationCrd() (*extv1.CustomResourceDefinition, e
- Plural: "virtualmachineinstancemigrations",
- Singular: "virtualmachineinstancemigration",
- Kind: virtv1.VirtualMachineInstanceMigrationGroupVersionKind.Kind,
-- ShortNames: []string{"vmim", "vmims"},
-+ ShortNames: []string{"xvmim", "xvmims"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -370,9 +370,9 @@ func NewKubeVirtCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "kubevirts",
- Singular: "kubevirt",
- Kind: virtv1.KubeVirtGroupVersionKind.Kind,
-- ShortNames: []string{"kv", "kvs"},
-+ ShortNames: []string{"xkv", "xkvs"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -411,9 +411,9 @@ func NewVirtualMachinePoolCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "virtualmachinepools",
- Singular: "virtualmachinepool",
- Kind: "VirtualMachinePool",
-- ShortNames: []string{"vmpool", "vmpools"},
-+ ShortNames: []string{"xvmpool", "xvmpools"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -463,9 +463,9 @@ func NewVirtualMachineSnapshotCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "virtualmachinesnapshots",
- Singular: "virtualmachinesnapshot",
- Kind: "VirtualMachineSnapshot",
-- ShortNames: []string{"vmsnapshot", "vmsnapshots"},
-+ ShortNames: []string{"xvmsnapshot", "xvmsnapshots"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -505,9 +505,9 @@ func NewVirtualMachineSnapshotContentCrd() (*extv1.CustomResourceDefinition, err
- Plural: "virtualmachinesnapshotcontents",
- Singular: "virtualmachinesnapshotcontent",
- Kind: "VirtualMachineSnapshotContent",
-- ShortNames: []string{"vmsnapshotcontent", "vmsnapshotcontents"},
-+ ShortNames: []string{"xvmsnapshotcontent", "xvmsnapshotcontents"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -544,9 +544,9 @@ func NewVirtualMachineRestoreCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "virtualmachinerestores",
- Singular: "virtualmachinerestore",
- Kind: "VirtualMachineRestore",
-- ShortNames: []string{"vmrestore", "vmrestores"},
-+ ShortNames: []string{"xvmrestore", "xvmrestores"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -585,9 +585,9 @@ func NewVirtualMachineExportCrd() (*extv1.CustomResourceDefinition, error) {
- Plural: "virtualmachineexports",
- Singular: "virtualmachineexport",
- Kind: "VirtualMachineExport",
-- ShortNames: []string{"vmexport", "vmexports"},
-+ ShortNames: []string{"xvmexport", "xvmexports"},
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-@@ -615,9 +615,9 @@ func NewVirtualMachineInstancetypeCrd() (*extv1.CustomResourceDefinition, error)
- Names: extv1.CustomResourceDefinitionNames{
- Plural: instancetype.PluralResourceName,
- Singular: instancetype.SingularResourceName,
-- ShortNames: []string{"vminstancetype", "vminstancetypes", "vmf", "vmfs"},
-+ ShortNames: []string{"xvminstancetype", "xvminstancetypes", "xvmf", "xvmfs"},
- Kind: "VirtualMachineInstancetype",
-- Categories: []string{"all"},
-+ Categories: []string{"kubevirt"},
- },
- Scope: extv1.NamespaceScoped,
- Conversion: &extv1.CustomResourceConversion{
-@@ -657,7 +657,7 @@ func NewVirtualMachineClusterInstancetypeCrd() (*extv1.CustomResourceDefinition,
- Names: extv1.CustomResourceDefinitionNames{
- Plural: instancetype.ClusterPluralResourceName,
- Singular: instancetype.ClusterSingularResourceName,
-- ShortNames: []string{"vmclusterinstancetype", "vmclusterinstancetypes", "vmcf", "vmcfs"},
-+ ShortNames: []string{"xvmclusterinstancetype", "xvmclusterinstancetypes", "xvmcf", "xvmcfs"},
- Kind: "VirtualMachineClusterInstancetype",
- },
- Scope: extv1.ClusterScoped,
-@@ -698,9 +698,9 @@ func NewVirtualMachinePreferenceCrd() (*extv1.CustomResourceDefinition, error) {
- Names: extv1.CustomResourceDefinitionNames{
- Plural: instancetype.PluralPreferenceResourceName,
- Singular: instancetype.SingularPreferenceResourceName,
-- ShortNames: []string{"vmpref", "vmprefs", "vmp", "vmps"},
-+ ShortNames: []string{"xvmpref", "xvmprefs", "xvmp", "xvmps"},
- Kind: "VirtualMachinePreference",
-- Categories: []string{"all"},
-+ Categories: []string{"kubevirt"},
- },
- Scope: extv1.NamespaceScoped,
- Conversion: &extv1.CustomResourceConversion{
-@@ -740,7 +740,7 @@ func NewVirtualMachineClusterPreferenceCrd() (*extv1.CustomResourceDefinition, e
- Names: extv1.CustomResourceDefinitionNames{
- Plural: instancetype.ClusterPluralPreferenceResourceName,
- Singular: instancetype.ClusterSingularPreferenceResourceName,
-- ShortNames: []string{"vmcp", "vmcps"},
-+ ShortNames: []string{"xvmcp", "xvmcps"},
- Kind: "VirtualMachineClusterPreference",
- },
- Scope: extv1.ClusterScoped,
-@@ -827,10 +827,10 @@ func NewVirtualMachineCloneCrd() (*extv1.CustomResourceDefinition, error) {
- Names: extv1.CustomResourceDefinitionNames{
- Plural: clone.ResourceVMClonePlural,
- Singular: clone.ResourceVMCloneSingular,
-- ShortNames: []string{"vmclone", "vmclones"},
-+ ShortNames: []string{"xvmclone", "xvmclones"},
- Kind: clonev1alpha1.VirtualMachineCloneKind.Kind,
- Categories: []string{
-- "all",
-+ "kubevirt",
- },
- },
- }
-diff --git a/pkg/virt-operator/resource/generate/rbac/apiserver.go b/pkg/virt-operator/resource/generate/rbac/apiserver.go
-index 99e8fe12d..1f409c28c 100644
---- a/pkg/virt-operator/resource/generate/rbac/apiserver.go
-+++ b/pkg/virt-operator/resource/generate/rbac/apiserver.go
-@@ -28,14 +28,16 @@ import (
-
- "kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/components"
-
-+ coreapi "kubevirt.io/api/core"
- virtv1 "kubevirt.io/api/core/v1"
- "kubevirt.io/api/migrations"
-+ snapshotapi "kubevirt.io/api/snapshot"
- )
-
- const (
- VersionName = "rbac.authorization.k8s.io"
- VersionNamev1 = "rbac.authorization.k8s.io/v1"
-- GroupName = "kubevirt.io"
-+ GroupName = coreapi.GroupName
- )
-
- func GetAllApiServer(namespace string) []runtime.Object {
-@@ -195,7 +197,7 @@ func newApiServerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "snapshot.kubevirt.io",
-+ snapshotapi.GroupName,
- },
- Resources: []string{
- "virtualmachinesnapshots",
-@@ -208,7 +210,7 @@ func newApiServerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "datasources",
-@@ -233,7 +235,7 @@ func newApiServerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "instancetype.kubevirt.io",
-+ instancetype.GroupName,
- },
- Resources: []string{
- instancetype.PluralResourceName,
-diff --git a/pkg/virt-operator/resource/generate/rbac/cluster.go b/pkg/virt-operator/resource/generate/rbac/cluster.go
-index 1af14cb60..82659d6f8 100644
---- a/pkg/virt-operator/resource/generate/rbac/cluster.go
-+++ b/pkg/virt-operator/resource/generate/rbac/cluster.go
-@@ -28,15 +28,20 @@ import (
- virtv1 "kubevirt.io/api/core/v1"
-
- "kubevirt.io/api/migrations"
-+
-+ cloneapi "kubevirt.io/api/clone"
-+ exportapi "kubevirt.io/api/export"
-+ poolapi "kubevirt.io/api/pool"
-+ snapshotapi "kubevirt.io/api/snapshot"
- )
-
- const (
- GroupNameSubresources = "subresources.kubevirt.io"
-- GroupNameSnapshot = "snapshot.kubevirt.io"
-- GroupNameExport = "export.kubevirt.io"
-- GroupNameClone = "clone.kubevirt.io"
-- GroupNameInstancetype = "instancetype.kubevirt.io"
-- GroupNamePool = "pool.kubevirt.io"
-+ GroupNameSnapshot = snapshotapi.GroupName
-+ GroupNameExport = exportapi.GroupName
-+ GroupNameClone = cloneapi.GroupName
-+ GroupNameInstancetype = instancetype.GroupName
-+ GroupNamePool = poolapi.GroupName
- NameDefault = "kubevirt.io:default"
- VMInstancesGuestOSInfo = "virtualmachineinstances/guestosinfo"
- VMInstancesFileSysList = "virtualmachineinstances/filesystemlist"
-diff --git a/pkg/virt-operator/resource/generate/rbac/controller.go b/pkg/virt-operator/resource/generate/rbac/controller.go
-index 474ab93b0..b7a1e2a55 100644
---- a/pkg/virt-operator/resource/generate/rbac/controller.go
-+++ b/pkg/virt-operator/resource/generate/rbac/controller.go
-@@ -28,7 +28,11 @@ import (
-
- "kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/components"
-
-+ coreapi "kubevirt.io/api/core"
-+ "kubevirt.io/api/export"
- "kubevirt.io/api/instancetype"
-+ "kubevirt.io/api/pool"
-+ "kubevirt.io/api/snapshot"
-
- virtv1 "kubevirt.io/api/core/v1"
- "kubevirt.io/api/migrations"
-@@ -311,7 +315,7 @@ func newControllerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "snapshot.kubevirt.io",
-+ snapshot.GroupName,
- },
- Resources: []string{
- "*",
-@@ -322,7 +326,7 @@ func newControllerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "export.kubevirt.io",
-+ export.GroupName,
- },
- Resources: []string{
- "*",
-@@ -333,7 +337,7 @@ func newControllerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "pool.kubevirt.io",
-+ pool.GroupName,
- },
- Resources: []string{
- "virtualmachinepools",
-@@ -354,7 +358,7 @@ func newControllerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "kubevirt.io",
-+ coreapi.GroupName,
- },
- Resources: []string{
- "*",
-@@ -380,7 +384,7 @@ func newControllerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "cdi.kubevirt.io",
-+ "x.virtualization.deckhouse.io",
- },
- Resources: []string{
- "*",
-@@ -468,7 +472,7 @@ func newControllerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "instancetype.kubevirt.io",
-+ instancetype.GroupName,
- },
- Resources: []string{
- instancetype.PluralResourceName,
-diff --git a/pkg/virt-operator/resource/generate/rbac/exportproxy.go b/pkg/virt-operator/resource/generate/rbac/exportproxy.go
-index 071ed91f9..bcfe70f36 100644
---- a/pkg/virt-operator/resource/generate/rbac/exportproxy.go
-+++ b/pkg/virt-operator/resource/generate/rbac/exportproxy.go
-@@ -24,7 +24,9 @@ import (
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
-
-+ coreapi "kubevirt.io/api/core"
- virtv1 "kubevirt.io/api/core/v1"
-+ "kubevirt.io/api/export"
- )
-
- const ExportProxyServiceAccountName = "kubevirt-exportproxy"
-@@ -70,7 +72,7 @@ func newExportProxyClusterRole() *rbacv1.ClusterRole {
- Rules: []rbacv1.PolicyRule{
- {
- APIGroups: []string{
-- "export.kubevirt.io",
-+ export.GroupName,
- },
- Resources: []string{
- "virtualmachineexports",
-@@ -81,7 +83,7 @@ func newExportProxyClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "kubevirt.io",
-+ coreapi.GroupName,
- },
- Resources: []string{
- "kubevirts",
-diff --git a/pkg/virt-operator/resource/generate/rbac/handler.go b/pkg/virt-operator/resource/generate/rbac/handler.go
-index e55a4044e..a953a5929 100644
---- a/pkg/virt-operator/resource/generate/rbac/handler.go
-+++ b/pkg/virt-operator/resource/generate/rbac/handler.go
-@@ -25,6 +25,7 @@ import (
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
-
-+ coreapi "kubevirt.io/api/core"
- virtv1 "kubevirt.io/api/core/v1"
- "kubevirt.io/api/migrations"
-
-@@ -72,7 +73,7 @@ func newHandlerClusterRole() *rbacv1.ClusterRole {
- Rules: []rbacv1.PolicyRule{
- {
- APIGroups: []string{
-- "kubevirt.io",
-+ coreapi.GroupName,
- },
- Resources: []string{
- "virtualmachineinstances",
-@@ -134,7 +135,7 @@ func newHandlerClusterRole() *rbacv1.ClusterRole {
- },
- {
- APIGroups: []string{
-- "kubevirt.io",
-+ coreapi.GroupName,
- },
- Resources: []string{
- "kubevirts",
-diff --git a/pkg/virt-operator/resource/generate/rbac/operator.go b/pkg/virt-operator/resource/generate/rbac/operator.go
-index 98939edde..fd6e72c5c 100644
---- a/pkg/virt-operator/resource/generate/rbac/operator.go
-+++ b/pkg/virt-operator/resource/generate/rbac/operator.go
-@@ -25,6 +25,7 @@ import (
- rbacv1 "k8s.io/api/rbac/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-+ coreapi "kubevirt.io/api/core"
- virtv1 "kubevirt.io/api/core/v1"
-
- "kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/components"
-@@ -83,7 +84,7 @@ func NewOperatorClusterRole() *rbacv1.ClusterRole {
- Rules: []rbacv1.PolicyRule{
- {
- APIGroups: []string{
-- "kubevirt.io",
-+ coreapi.GroupName,
- },
- Resources: []string{
- "kubevirts",
-diff --git a/pkg/virtctl/create/clone/clone.go b/pkg/virtctl/create/clone/clone.go
-index d82de56b9..fae2a0aad 100644
---- a/pkg/virtctl/create/clone/clone.go
-+++ b/pkg/virtctl/create/clone/clone.go
-@@ -28,6 +28,8 @@ import (
- v1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/util/rand"
- clonev1alpha1 "kubevirt.io/api/clone/v1alpha1"
-+ coreapi "kubevirt.io/api/core"
-+ "kubevirt.io/api/snapshot"
- "kubevirt.io/client-go/kubecli"
-
- "kubevirt.io/kubevirt/pkg/pointer"
-@@ -248,14 +250,14 @@ func (c *createClone) typeToTypedLocalObjectReference(sourceOrTargetType, source
- switch sourceOrTargetType {
- case "vm", "VM", "VirtualMachine", "virtualmachine":
- kind = "VirtualMachine"
-- apiGroup = "kubevirt.io"
-+ apiGroup = coreapi.GroupName
- case "snapshot", "VirtualMachineSnapshot", "vmsnapshot", "VMSnapshot":
- if !isSource {
- return nil, generateErr()
- }
-
- kind = "VirtualMachineSnapshot"
-- apiGroup = "snapshot.kubevirt.io"
-+ apiGroup = snapshot.GroupName
- default:
- return nil, generateErr()
- }
-diff --git a/staging/src/kubevirt.io/api/clone/register.go b/staging/src/kubevirt.io/api/clone/register.go
-index 85a24c26e..faa19eed5 100644
---- a/staging/src/kubevirt.io/api/clone/register.go
-+++ b/staging/src/kubevirt.io/api/clone/register.go
-@@ -21,7 +21,7 @@ package clone
-
- // GroupName is the group name used in this package
- const (
-- GroupName = "clone.kubevirt.io"
-+ GroupName = "x.virtualization.deckhouse.io"
- LatestVersion = "v1alpha1"
- Kind = "VirtualMachineClone"
- ListKind = "VirtualMachineCloneList"
-diff --git a/staging/src/kubevirt.io/api/core/register.go b/staging/src/kubevirt.io/api/core/register.go
-index 22080c717..56b23f7d7 100644
---- a/staging/src/kubevirt.io/api/core/register.go
-+++ b/staging/src/kubevirt.io/api/core/register.go
-@@ -1,4 +1,4 @@
- package core
-
- // GroupName is the group name use in this package
--const GroupName = "kubevirt.io"
-+const GroupName = "x.virtualization.deckhouse.io"
-diff --git a/staging/src/kubevirt.io/api/export/register.go b/staging/src/kubevirt.io/api/export/register.go
-index 844dbf5e0..82f95d730 100644
---- a/staging/src/kubevirt.io/api/export/register.go
-+++ b/staging/src/kubevirt.io/api/export/register.go
-@@ -21,5 +21,5 @@ package export
-
- // GroupName is the group name used in this package
- const (
-- GroupName = "export.kubevirt.io"
-+ GroupName = "x.virtualization.deckhouse.io"
- )
-diff --git a/staging/src/kubevirt.io/api/instancetype/register.go b/staging/src/kubevirt.io/api/instancetype/register.go
-index d4eafd31f..cffbb27f5 100644
---- a/staging/src/kubevirt.io/api/instancetype/register.go
-+++ b/staging/src/kubevirt.io/api/instancetype/register.go
-@@ -21,7 +21,7 @@ package instancetype
-
- // GroupName is the group name used in this package
- const (
-- GroupName = "instancetype.kubevirt.io"
-+ GroupName = "x.virtualization.deckhouse.io"
-
- SingularResourceName = "virtualmachineinstancetype"
- PluralResourceName = SingularResourceName + "s"
-diff --git a/staging/src/kubevirt.io/api/migrations/register.go b/staging/src/kubevirt.io/api/migrations/register.go
-index dbb6d3c41..03d40a9c2 100644
---- a/staging/src/kubevirt.io/api/migrations/register.go
-+++ b/staging/src/kubevirt.io/api/migrations/register.go
-@@ -21,7 +21,7 @@ package migrations
-
- // GroupName is the group name used in this package
- const (
-- GroupName = "migrations.kubevirt.io"
-+ GroupName = "x.virtualization.deckhouse.io"
- Version = "v1alpha1"
-
- ResourceMigrationPolicies = "migrationpolicies"
-diff --git a/staging/src/kubevirt.io/api/pool/register.go b/staging/src/kubevirt.io/api/pool/register.go
-index 08b9d8c62..9f45555b2 100644
---- a/staging/src/kubevirt.io/api/pool/register.go
-+++ b/staging/src/kubevirt.io/api/pool/register.go
-@@ -21,5 +21,5 @@ package pool
-
- // GroupName is the group name used in this package
- const (
-- GroupName = "pool.kubevirt.io"
-+ GroupName = "x.virtualization.deckhouse.io"
- )
-diff --git a/staging/src/kubevirt.io/api/snapshot/register.go b/staging/src/kubevirt.io/api/snapshot/register.go
-index 880a59292..4a1216cb8 100644
---- a/staging/src/kubevirt.io/api/snapshot/register.go
-+++ b/staging/src/kubevirt.io/api/snapshot/register.go
-@@ -21,5 +21,5 @@ package snapshot
-
- // GroupName is the group name used in this package
- const (
-- GroupName = "snapshot.kubevirt.io"
-+ GroupName = "x.virtualization.deckhouse.io"
- )
-diff --git a/staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/scheme/register.go b/staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/scheme/register.go
-index 3ac7803dd..66a3c3a43 100644
---- a/staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/scheme/register.go
-+++ b/staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/scheme/register.go
-@@ -31,10 +31,6 @@ import (
- var Scheme = runtime.NewScheme()
- var Codecs = serializer.NewCodecFactory(Scheme)
- var ParameterCodec = runtime.NewParameterCodec(Scheme)
--var localSchemeBuilder = runtime.SchemeBuilder{
-- cdiv1beta1.AddToScheme,
-- uploadv1beta1.AddToScheme,
--}
-
- // AddToScheme adds all types of this clientset into the given scheme. This allows composition
- // of clientsets, like in:
-@@ -50,9 +46,44 @@ var localSchemeBuilder = runtime.SchemeBuilder{
- //
- // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
- // correctly.
--var AddToScheme = localSchemeBuilder.AddToScheme
-+var AddToScheme func(scheme *runtime.Scheme) error
-
- func init() {
-+ cdiv1beta1.SchemeGroupVersion.Group = "x.virtualization.deckhouse.io"
-+ cdiv1beta1.CDIGroupVersionKind.Group = "x.virtualization.deckhouse.io"
-+ cdiv1beta1.SchemeBuilder = runtime.NewSchemeBuilder(func(scheme *runtime.Scheme) error {
-+ scheme.AddKnownTypes(cdiv1beta1.SchemeGroupVersion,
-+ &cdiv1beta1.DataVolume{},
-+ &cdiv1beta1.DataVolumeList{},
-+ &cdiv1beta1.CDIConfig{},
-+ &cdiv1beta1.CDIConfigList{},
-+ &cdiv1beta1.CDI{},
-+ &cdiv1beta1.CDIList{},
-+ &cdiv1beta1.StorageProfile{},
-+ &cdiv1beta1.StorageProfileList{},
-+ &cdiv1beta1.DataSource{},
-+ &cdiv1beta1.DataSourceList{},
-+ &cdiv1beta1.DataImportCron{},
-+ &cdiv1beta1.DataImportCronList{},
-+ &cdiv1beta1.ObjectTransfer{},
-+ &cdiv1beta1.ObjectTransferList{},
-+ &cdiv1beta1.VolumeImportSource{},
-+ &cdiv1beta1.VolumeImportSourceList{},
-+ &cdiv1beta1.VolumeUploadSource{},
-+ &cdiv1beta1.VolumeUploadSourceList{},
-+ &cdiv1beta1.VolumeCloneSource{},
-+ &cdiv1beta1.VolumeCloneSourceList{},
-+ )
-+ v1.AddToGroupVersion(scheme, cdiv1beta1.SchemeGroupVersion)
-+ return nil
-+ })
-+ cdiv1beta1.AddToScheme = cdiv1beta1.SchemeBuilder.AddToScheme
-+
-+ AddToScheme = (&runtime.SchemeBuilder{
-+ cdiv1beta1.AddToScheme,
-+ uploadv1beta1.AddToScheme,
-+ }).AddToScheme
-+
- v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
- utilruntime.Must(AddToScheme(Scheme))
- }
-diff --git a/staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/typed/core/v1beta1/core_client.go b/staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/typed/core/v1beta1/core_client.go
-index 2fd57771a..03660be5c 100644
---- a/staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/typed/core/v1beta1/core_client.go
-+++ b/staging/src/kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/typed/core/v1beta1/core_client.go
-@@ -113,6 +113,7 @@ func New(c rest.Interface) *CdiV1beta1Client {
-
- func setConfigDefaults(config *rest.Config) error {
- gv := v1beta1.SchemeGroupVersion
-+ gv.Group = "x.virtualization.deckhouse.io"
- config.GroupVersion = &gv
- config.APIPath = "/apis"
- config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
diff --git a/images/virt-artifact/patches/012-support-kubeconfig-env.patch b/images/virt-artifact/patches/012-support-kubeconfig-env.patch
new file mode 100644
index 000000000..529bd113b
--- /dev/null
+++ b/images/virt-artifact/patches/012-support-kubeconfig-env.patch
@@ -0,0 +1,13 @@
+diff --git a/staging/src/kubevirt.io/client-go/kubecli/kubecli.go b/staging/src/kubevirt.io/client-go/kubecli/kubecli.go
+index f5d3982a8..719717247 100644
+--- a/staging/src/kubevirt.io/client-go/kubecli/kubecli.go
++++ b/staging/src/kubevirt.io/client-go/kubecli/kubecli.go
+@@ -99,7 +99,7 @@ var once sync.Once
+ // the different controller generators which normally add these flags too.
+ func Init() {
+ if flag.CommandLine.Lookup("kubeconfig") == nil {
+- flag.StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
++ flag.StringVar(&kubeconfig, "kubeconfig", os.Getenv("KUBECONFIG"), "absolute path to the kubeconfig file")
+ }
+ if flag.CommandLine.Lookup("master") == nil {
+ flag.StringVar(&master, "master", "", "master url")
diff --git a/images/virt-artifact/patches/README.md b/images/virt-artifact/patches/README.md
index 30df45cbc..ead6ec3a4 100644
--- a/images/virt-artifact/patches/README.md
+++ b/images/virt-artifact/patches/README.md
@@ -26,11 +26,8 @@ The provided fix will always run the job in same place where virt-operator runs
- https://github.com/kubevirt/kubevirt/pull/9360
-#### `010-override-crds.patch`
-
-Rename group name for all kubevirt CRDs to override them with deckhouse virtualization CRDs.
-
-Also, remove short names and change categories. Just in case.
-
#### `011-virt-api-authentication.patch`
-Added the ability for virt-api to authenticate clients with certificates signed by our rootCA located in the config-map virtualization-ca.
\ No newline at end of file
+Added the ability for virt-api to authenticate clients with certificates signed by our rootCA located in the config-map virtualization-ca.
+
+#### `012-support-kubeconfig-env.patch`
+Support `KUBECONFIG` environment variable.
diff --git a/images/virt-artifact/werf.inc.yaml b/images/virt-artifact/werf.inc.yaml
index 6ad72e2da..fe6d1440c 100644
--- a/images/virt-artifact/werf.inc.yaml
+++ b/images/virt-artifact/werf.inc.yaml
@@ -3,7 +3,7 @@
{{- $builderImage := "quay.io/kubevirt/builder:2306070730-a3b45da40" }}
{{- $version := "1.0.0" }}
-artifact: {{ $.ImageName }}
+image: {{ $.ImageName }}
from: {{ $builderImage }}
git:
- add: /images/{{ $.ImageName }}
@@ -19,7 +19,11 @@ shell:
setup:
- git clone --depth 1 --branch v{{ $version }} https://github.com/kubevirt/kubevirt.git /kubevirt
- cd /kubevirt
- - git apply /patches/*.patch
+ - |
+ for p in /patches/*.patch ; do
+ echo -n "Apply ${p} ... "
+ git apply ${p} || echo FAIL && echo OK
+ done
- mkdir -p _out
- echo "========== Build kubevirt images ============"
- make bazel-build-image-bundle KUBEVIRT_RUN_UNNESTED=true
diff --git a/images/virt-controller/werf.inc.yaml b/images/virt-controller/werf.inc.yaml
index dc6f53664..5506075b1 100644
--- a/images/virt-controller/werf.inc.yaml
+++ b/images/virt-controller/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: virt-artifact
+- image: virt-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/virt-exportproxy/werf.inc.yaml b/images/virt-exportproxy/werf.inc.yaml
index c543cfcf3..6026a127b 100644
--- a/images/virt-exportproxy/werf.inc.yaml
+++ b/images/virt-exportproxy/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: virt-artifact
+- image: virt-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/virt-exportserver/werf.inc.yaml b/images/virt-exportserver/werf.inc.yaml
index a26b639b2..96af74ff6 100644
--- a/images/virt-exportserver/werf.inc.yaml
+++ b/images/virt-exportserver/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: virt-artifact
+- image: virt-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/virt-handler/werf.inc.yaml b/images/virt-handler/werf.inc.yaml
index ac79ddde1..bd2ef733f 100644
--- a/images/virt-handler/werf.inc.yaml
+++ b/images/virt-handler/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: virt-artifact
+- image: virt-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/virt-launcher/werf.inc.yaml b/images/virt-launcher/werf.inc.yaml
index c809a3778..723029cb1 100644
--- a/images/virt-launcher/werf.inc.yaml
+++ b/images/virt-launcher/werf.inc.yaml
@@ -2,7 +2,7 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: virt-artifact
+- image: virt-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
diff --git a/images/virt-operator/werf.inc.yaml b/images/virt-operator/werf.inc.yaml
index c738559f0..e4ec6dbe6 100644
--- a/images/virt-operator/werf.inc.yaml
+++ b/images/virt-operator/werf.inc.yaml
@@ -2,13 +2,13 @@
image: {{ $.ImageName }}
fromImage: base-scratch
import:
-- artifact: virt-artifact
+- image: virt-artifact
add: /images/kubevirt/{{ $.ImageName }}:latest
excludePaths:
- 'sys'
to: /
before: setup
-- artifact: virt-artifact
+- image: virt-artifact
add: /
to: /
includePaths:
diff --git a/images/virtualization-artifact/cmd/virtualization-controller/main.go b/images/virtualization-artifact/cmd/virtualization-controller/main.go
index 83e8750e9..a15447df5 100644
--- a/images/virtualization-artifact/cmd/virtualization-controller/main.go
+++ b/images/virtualization-artifact/cmd/virtualization-controller/main.go
@@ -11,9 +11,7 @@ import (
"go.uber.org/zap/zapcore"
extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiruntime "k8s.io/apimachinery/pkg/runtime"
- apiruntimeschema "k8s.io/apimachinery/pkg/runtime/schema"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
virtv1 "kubevirt.io/api/core/v1"
cdiv1beta1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
@@ -47,18 +45,13 @@ var (
)
const (
- defaultVerbosity = "1"
- kubevirtCoreGroupName = "x.virtualization.deckhouse.io"
- cdiCoreGroupName = "x.virtualization.deckhouse.io"
+ defaultVerbosity = "1"
)
func init() {
importerImage = getRequiredEnvVar(common.ImporterPodImageNameVar)
uploaderImage = getRequiredEnvVar(common.UploaderPodImageNameVar)
controllerNamespace = getRequiredEnvVar(common.PodNamespaceVar)
-
- overrideKubevirtCoreGroupName(kubevirtCoreGroupName)
- overrideCDICoreGroupName(cdiCoreGroupName)
}
func setupLogger() {
@@ -99,61 +92,6 @@ func getRequiredEnvVar(name string) string {
return val
}
-func overrideKubevirtCoreGroupName(groupName string) {
- virtv1.GroupVersion.Group = groupName
- virtv1.SchemeGroupVersion.Group = groupName
- virtv1.StorageGroupVersion.Group = groupName
- for i := range virtv1.GroupVersions {
- virtv1.GroupVersions[i].Group = groupName
- }
-
- virtv1.VirtualMachineInstanceGroupVersionKind.Group = groupName
- virtv1.VirtualMachineInstanceReplicaSetGroupVersionKind.Group = groupName
- virtv1.VirtualMachineInstancePresetGroupVersionKind.Group = groupName
- virtv1.VirtualMachineGroupVersionKind.Group = groupName
- virtv1.VirtualMachineInstanceMigrationGroupVersionKind.Group = groupName
- virtv1.KubeVirtGroupVersionKind.Group = groupName
-
- virtv1.SchemeBuilder = apiruntime.NewSchemeBuilder(virtv1.AddKnownTypesGenerator([]apiruntimeschema.GroupVersion{virtv1.GroupVersion}))
- virtv1.AddToScheme = virtv1.SchemeBuilder.AddToScheme
-}
-
-func overrideCDICoreGroupName(groupName string) {
- cdiv1beta1.SchemeGroupVersion.Group = groupName
- cdiv1beta1.CDIGroupVersionKind.Group = groupName
-
- cdiv1beta1.SchemeBuilder = apiruntime.NewSchemeBuilder(addKnownTypes)
- cdiv1beta1.AddToScheme = cdiv1beta1.SchemeBuilder.AddToScheme
-}
-
-// Adds the list of known types to Scheme.
-func addKnownTypes(scheme *apiruntime.Scheme) error {
- scheme.AddKnownTypes(cdiv1beta1.SchemeGroupVersion,
- &cdiv1beta1.DataVolume{},
- &cdiv1beta1.DataVolumeList{},
- &cdiv1beta1.CDIConfig{},
- &cdiv1beta1.CDIConfigList{},
- &cdiv1beta1.CDI{},
- &cdiv1beta1.CDIList{},
- &cdiv1beta1.StorageProfile{},
- &cdiv1beta1.StorageProfileList{},
- &cdiv1beta1.DataSource{},
- &cdiv1beta1.DataSourceList{},
- &cdiv1beta1.DataImportCron{},
- &cdiv1beta1.DataImportCronList{},
- &cdiv1beta1.ObjectTransfer{},
- &cdiv1beta1.ObjectTransferList{},
- &cdiv1beta1.VolumeImportSource{},
- &cdiv1beta1.VolumeImportSourceList{},
- &cdiv1beta1.VolumeUploadSource{},
- &cdiv1beta1.VolumeUploadSourceList{},
- &cdiv1beta1.VolumeCloneSource{},
- &cdiv1beta1.VolumeCloneSourceList{},
- )
- metav1.AddToGroupVersion(scheme, cdiv1beta1.SchemeGroupVersion)
- return nil
-}
-
func main() {
flag.Parse()
@@ -172,6 +110,8 @@ func main() {
log.Error(err, "")
os.Exit(1)
}
+ // Override content type to JSON so proxy can rewrite payloads.
+ cfg.ContentType = apiruntime.ContentTypeJSON
leaderElectionNS := os.Getenv(common.PodNamespaceVar)
if leaderElectionNS == "" {
diff --git a/images/virtualization-artifact/pkg/controller/vm_reconciler.go b/images/virtualization-artifact/pkg/controller/vm_reconciler.go
index 5fdddf519..95bee730b 100644
--- a/images/virtualization-artifact/pkg/controller/vm_reconciler.go
+++ b/images/virtualization-artifact/pkg/controller/vm_reconciler.go
@@ -77,6 +77,32 @@ func (r *VMReconciler) SetupController(_ context.Context, mgr manager.Manager, c
return fmt.Errorf("error setting watch on VirtualMachine: %w", err)
}
+ // Subscribe on Kubevirt VirtualMachineInstances to update our VM status.
+ if err := ctr.Watch(
+ source.Kind(mgr.GetCache(), &virtv1.VirtualMachineInstance{}),
+ handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, vmi client.Object) []reconcile.Request {
+ return []reconcile.Request{
+ {
+ NamespacedName: types.NamespacedName{
+ Name: vmi.GetName(),
+ Namespace: vmi.GetNamespace(),
+ },
+ },
+ }
+ }),
+ predicate.Funcs{
+ CreateFunc: func(e event.CreateEvent) bool { return true },
+ DeleteFunc: func(e event.DeleteEvent) bool { return true },
+ UpdateFunc: func(e event.UpdateEvent) bool {
+ oldVM := e.ObjectOld.(*virtv1.VirtualMachineInstance)
+ newVM := e.ObjectNew.(*virtv1.VirtualMachineInstance)
+ return oldVM.Status.Phase != newVM.Status.Phase
+ },
+ },
+ ); err != nil {
+ return fmt.Errorf("error setting watch on VirtualMachine: %w", err)
+ }
+
// Watch for Pods created on behalf of VMs. Handle only changes in status.phase.
// Pod tracking is required to detect when Pod becomes Completed after guest initiated reset or shutdown.
if err := ctr.Watch(
diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl
new file mode 100644
index 000000000..c73b50b85
--- /dev/null
+++ b/templates/_helpers.tpl
@@ -0,0 +1,74 @@
+{{- define "strategic_affinity_patch" -}}
+ {{- $key := index . 0 -}}
+ {{- $labelValue := index . 1 -}}
+ '{{ include "tmplAntiAffinity" (list $key $labelValue) | fromYaml | toJson }}'
+{{- end }}
+
+{{- define "tmplAntiAffinity" -}}
+ {{- $key := index . 0 -}}
+ {{- $labelValue := index . 1 -}}
+spec:
+ template:
+ spec:
+ affinity:
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: {{ $key }}
+ operator: In
+ values:
+ - {{ $labelValue }}
+ topologyKey: kubernetes.io/hostname
+{{- end -}}
+
+{{- define "kubeproxy_resources" -}}
+cpu: 100m
+memory: 150Mi
+{{- end -}}
+
+{{- define "nowebhook_kubeproxy_patch" -}}
+ '{{ include "nowebhook_kubeproxy_patch_tmpl" . | fromYaml | toJson }}'
+{{- end }}
+
+{{- define "nowebhook_kubeproxy_patch_tmpl" -}}
+ {{- $ctx := index . 0 -}}
+ {{- $containerName := index . 1 -}}
+ {{- $proxyImage := include "helm_lib_module_image" (list $ctx "kubeApiProxy") }}
+metadata:
+ annotations:
+ kubectl.kubernetes.io/default-container: {{ $containerName }}
+spec:
+ template:
+ spec:
+ volumes:
+ - name: kube-api-proxy-kubeconfig
+ configMap:
+ name: kube-api-proxy-kubeconfig
+ containers:
+ - name: proxy
+ image: {{ $proxyImage }}
+ imagePullPolicy: IfNotPresent
+ resources:
+ requests:
+ {{- include "helm_lib_module_ephemeral_storage_only_logs" . | nindent 12 }}
+ {{- if not ( $ctx.Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
+ {{- include "kubeproxy_resources" . | nindent 12 }}
+ {{- end }}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ seccompProfile:
+ type: RuntimeDefault
+ terminationMessagePath: /dev/termination-log
+ terminationMessagePolicy: File
+ - name: {{ $containerName }}
+ env:
+ - name: KUBECONFIG
+ value: /kubeconfig.local/proxy.kubeconfig
+ volumeMounts:
+ - name: kube-api-proxy-kubeconfig
+ mountPath: /kubeconfig.local
+{{- end -}}
diff --git a/templates/cdi/_helpers.tpl b/templates/cdi/_helpers.tpl
new file mode 100644
index 000000000..221db64ba
--- /dev/null
+++ b/templates/cdi/_helpers.tpl
@@ -0,0 +1,58 @@
+{{- define "cdi.apiserver_kubeproxy_patch" -}}
+ '{{ include "cdi.apiserver_kubeproxy_patch_tmpl" . | fromYaml | toJson }}'
+{{- end }}
+
+{{- define "cdi.apiserver_kubeproxy_patch_tmpl" -}}
+ {{- $proxyImage := include "helm_lib_module_image" (list . "kubeApiProxy") }}
+metadata:
+ annotations:
+ kubectl.kubernetes.io/default-container: cdi-apiserver
+spec:
+ template:
+ spec:
+ volumes:
+ - name: kube-api-proxy-kubeconfig
+ configMap:
+ name: kube-api-proxy-kubeconfig
+ containers:
+ - name: proxy
+ image: {{ $proxyImage }}
+ imagePullPolicy: Always
+ resources:
+ requests:
+ {{- include "helm_lib_module_ephemeral_storage_only_logs" . | nindent 12 }}
+ {{- if not ( .Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
+ {{- include "kubeproxy_resources" . | nindent 12 }}
+ {{- end }}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ seccompProfile:
+ type: RuntimeDefault
+ terminationMessagePath: /dev/termination-log
+ terminationMessagePolicy: File
+ ports:
+ - containerPort: 24192
+ name: webhook-proxy
+ protocol: TCP
+ env:
+ - name: WEBHOOK_ADDRESS
+ value: "https://127.0.0.1:8443"
+ - name: WEBHOOK_CERT_FILE
+ value: "/var/run/certs/cdi-apiserver-server-cert/tls.crt"
+ - name: WEBHOOK_KEY_FILE
+ value: "/var/run/certs/cdi-apiserver-server-cert/tls.key"
+ volumeMounts:
+ - mountPath: /var/run/certs/cdi-apiserver-server-cert
+ name: server-cert
+ readOnly: true
+ - name: cdi-apiserver
+ env:
+ - name: KUBECONFIG
+ value: /kubeconfig.local/proxy.kubeconfig
+ volumeMounts:
+ - name: kube-api-proxy-kubeconfig
+ mountPath: /kubeconfig.local
+{{- end -}}
diff --git a/templates/cdi/cdi-operator/deployment.yaml b/templates/cdi/cdi-operator/deployment.yaml
index 3d16ca868..791f06a5e 100644
--- a/templates/cdi/cdi-operator/deployment.yaml
+++ b/templates/cdi/cdi-operator/deployment.yaml
@@ -59,6 +59,8 @@ metadata:
{{- include "helm_lib_module_labels" (list .) | nindent 2 }}
name: cdi-operator
namespace: d8-{{ .Chart.Name }}
+ annotations:
+ kubectl.kubernetes.io/default-container: cdi-operator
spec:
{{- include "helm_lib_deployment_strategy_and_replicas_for_ha" . | nindent 2 }}
revisionHistoryLimit: 2
@@ -72,10 +74,30 @@ spec:
spec:
{{- include "helm_lib_pod_anti_affinity_for_ha" (list . (dict "app" "cdi-operator")) | nindent 6 }}
containers:
+ - name: proxy
+ image: {{ include "helm_lib_module_image" (list . "kubeApiProxy") }}
+ imagePullPolicy: IfNotPresent
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ seccompProfile:
+ type: RuntimeDefault
+ terminationMessagePath: /dev/termination-log
+ terminationMessagePolicy: File
+ resources:
+ requests:
+ {{- include "helm_lib_module_ephemeral_storage_only_logs" . | nindent 12 }}
+ {{- if not ( .Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
+ {{- include "cdi_operator_resources" . | nindent 12 }}
+ {{- end }}
- name: cdi-operator
{{- include "helm_lib_module_container_security_context_read_only_root_filesystem_capabilities_drop_all" . | nindent 8 }}
env:
{{- include "cdi_images" . | nindent 8 }}
+ - name: KUBECONFIG
+ value: "/kubeconfig.local/proxy.kubeconfig"
- name: DEPLOY_CLUSTER_RESOURCES
value: "true"
- name: OPERATOR_VERSION
@@ -97,8 +119,15 @@ spec:
{{- if not ( .Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
{{- include "cdi_operator_resources" . | nindent 12 }}
{{- end }}
+ volumeMounts:
+ - name: kube-api-proxy-kubeconfig
+ mountPath: /kubeconfig.local
{{- include "helm_lib_priority_class" (tuple . "cluster-low") | nindent 6 }}
{{- include "helm_lib_node_selector" (tuple . "system") | nindent 6 }}
{{- include "helm_lib_tolerations" (tuple . "system") | nindent 6 }}
{{- include "helm_lib_module_pod_security_context_run_as_user_nobody" . | nindent 6 }}
serviceAccountName: cdi-operator
+ volumes:
+ - name: kube-api-proxy-kubeconfig
+ configMap:
+ name: kube-api-proxy-kubeconfig
diff --git a/templates/cdi/cdi-operator/rbac-for-us.yaml b/templates/cdi/cdi-operator/rbac-for-us.yaml
index 9bcb328d0..810371ea6 100644
--- a/templates/cdi/cdi-operator/rbac-for-us.yaml
+++ b/templates/cdi/cdi-operator/rbac-for-us.yaml
@@ -40,7 +40,7 @@ rules:
- update
- delete
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
- upload.cdi.kubevirt.io
resources:
- '*'
@@ -123,28 +123,28 @@ rules:
verbs:
- get
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - datavolumes
+ - dvpinternaldatavolumes
verbs:
- list
- get
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - datasources
+ - dvpinternaldatasources
verbs:
- get
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - cdis
+ - dvpinternalcdis
verbs:
- get
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - cdis/finalizers
+ - dvpinternalcdis/finalizers
verbs:
- update
- apiGroups:
@@ -219,7 +219,7 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- '*'
verbs:
@@ -286,9 +286,9 @@ rules:
verbs:
- get
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - dataimportcrons
+ - dvpinternaldataimportcrons
verbs:
- get
- list
diff --git a/templates/cdi/cdi.yaml b/templates/cdi/cdi.yaml
index 2adca57c2..065505e9e 100644
--- a/templates/cdi/cdi.yaml
+++ b/templates/cdi/cdi.yaml
@@ -2,9 +2,12 @@
{{- $nodeSelectorMaster := index (include "helm_lib_node_selector" (tuple . "master") | fromYaml) "nodeSelector" | default (dict) | toJson }}
{{- $tolerationsSystem := index (include "helm_lib_tolerations" (tuple . "system") | fromYaml) "tolerations" | default (list) | toJson }}
{{- $tolerationsAnyNode := index (include "helm_lib_tolerations" (tuple . "any-node") | fromYaml) "tolerations" | default (list) | toJson }}
+{{- $proxyImage := include "helm_lib_module_image" (list . "kubeApiProxy") | toJson }}
+{{- $kubeAPIProxyRewriter := true }}
+{{- $webhookProxyPort := 24192 }}
---
-apiVersion: x.virtualization.deckhouse.io/v1beta1
-kind: CDI
+apiVersion: internal.virtualization.deckhouse.io/v1beta1
+kind: DVPInternalCDI
metadata:
name: cdi
namespace: d8-{{ .Chart.Name }}
@@ -36,16 +39,6 @@ spec:
resourceName: cdi-uploadproxy
patch: '[{"op":"replace","path":"/spec/replicas","value":0}]'
type: json
- {{- if ( .Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
- - resourceType: Deployment
- resourceName: cdi-apiserver
- patch: '[{"op":"replace","path":"/spec/template/spec/containers/0/resources/requests","value":{}}]'
- type: json
- - resourceType: Deployment
- resourceName: cdi-deployment
- patch: '[{"op":"replace","path":"/spec/template/spec/containers/0/resources/requests","value":{}}]'
- type: json
- {{- end }}
{{- if (include "helm_lib_ha_enabled" .) }}
- resourceType: Deployment
@@ -54,8 +47,8 @@ spec:
type: json
- resourceType: Deployment
resourceName: cdi-apiserver
- patch: '[{"op":"replace","path":"/spec/template/spec/affinity","value":{"podAntiAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":[{"labelSelector":{"matchExpressions":[{"key":"cdi.kubevirt.io","operator":"In","values":["cdi-apiserver"]}]},"topologyKey":"kubernetes.io/hostname"}]}}}]'
- type: json
+ patch: {{ include "strategic_affinity_patch" (list "app" "cdi-apiserver") }}
+ type: strategic
- resourceType: Deployment
resourceName: cdi-deployment
@@ -63,9 +56,89 @@ spec:
type: json
- resourceType: Deployment
resourceName: cdi-deployment
- patch: '[{"op":"replace","path":"/spec/template/spec/affinity","value":{"podAntiAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":[{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["containerized-data-importer"]}]},"topologyKey":"kubernetes.io/hostname"}]}}}]'
+ patch: {{ include "strategic_affinity_patch" (list "app" "containerized-data-importer") }}
+ type: strategic
+ {{- end }}
+
+ {{- if $kubeAPIProxyRewriter }}
+ - resourceType: Deployment
+ resourceName: cdi-apiserver
+ patch: {{ include "cdi.apiserver_kubeproxy_patch" . }}
+ type: strategic
+
+ - resourceType: Deployment
+ resourceName: cdi-deployment
+ patch: {{ include "nowebhook_kubeproxy_patch" (list . "cdi-controller") }}
+ type: strategic
+
+ # Change service in webhook configurations to point to the rewriter proxy.
+ # Add port to cdi-api service.
+ - resourceName: cdi-api
+ resourceType: Service
+ patch: |
+ [{"op":"replace", "path":"/spec/ports/0/name", "value":"https"}]
type: json
+ - resourceName: cdi-api
+ resourceType: Service
+ patch: |
+ {"spec":{"ports":[
+ {"name": "webhook-proxy",
+ "port": {{ $webhookProxyPort }},
+ "protocol": "TCP",
+ "targetPort": "webhook-proxy"}
+ ]}}
+ type: strategic
+ - resourceName: cdi-api-datavolume-mutate
+ resourceType: MutatingWebhookConfiguration
+ patch: |
+ {"webhooks":[
+ { "name":"datavolume-mutate.cdi.kubevirt.io",
+ "clientConfig":{"service":{"port": {{ $webhookProxyPort }} }}}
+ ]}
+ type: strategic
+ - resourceName: cdi-api-dataimportcron-validate
+ resourceType: ValidatingWebhookConfiguration
+ patch: |
+ {"webhooks":[
+ { "name":"dataimportcron-validate.cdi.kubevirt.io",
+ "clientConfig":{"service":{"port": {{ $webhookProxyPort }} }}}
+ ]}
+ type: strategic
+ - resourceName: cdi-api-datavolume-validate
+ resourceType: ValidatingWebhookConfiguration
+ patch: |
+ {"webhooks":[
+ { "name":"datavolume-validate.cdi.kubevirt.io",
+ "clientConfig":{"service":{"port": {{ $webhookProxyPort }} }}}
+ ]}
+ type: strategic
+ - resourceName: cdi-api-populator-validate
+ resourceType: ValidatingWebhookConfiguration
+ patch: |
+ {"webhooks":[
+ { "name":"populator-validate.cdi.kubevirt.io",
+ "clientConfig":{"service":{"port": {{ $webhookProxyPort }} }}}
+ ]}
+ type: strategic
+ - resourceName: cdi-api-validate
+ resourceType: ValidatingWebhookConfiguration
+ patch: |
+ {"webhooks":[
+ { "name":"cdi-validate.cdi.kubevirt.io",
+ "clientConfig":{"service":{"port": {{ $webhookProxyPort }} }}}
+ ]}
+ type: strategic
+ - resourceName: objecttransfer-api-validate
+ resourceType: ValidatingWebhookConfiguration
+ patch: |
+ {"webhooks":[
+ { "name":"objecttransfer-validate.cdi.kubevirt.io",
+ "clientConfig":{"service":{"port": {{ $webhookProxyPort }} }}}
+ ]}
+ type: strategic
+
{{- end }}
+
workload:
nodeSelector:
kubernetes.io/os: linux
diff --git a/templates/kube-api-proxy-kubeconfig-cm.yaml b/templates/kube-api-proxy-kubeconfig-cm.yaml
new file mode 100644
index 000000000..5373651ac
--- /dev/null
+++ b/templates/kube-api-proxy-kubeconfig-cm.yaml
@@ -0,0 +1,20 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: kube-api-proxy-kubeconfig
+ namespace: d8-{{ .Chart.Name }}
+ {{- include "helm_lib_module_labels" (list .) | nindent 2 }}
+data:
+ proxy.kubeconfig: |
+ apiVersion: v1
+ kind: Config
+ clusters:
+ - cluster:
+ server: http://127.0.0.1:23915
+ name: proxy.api.server
+ contexts:
+ - context:
+ cluster: proxy.api.server
+ name: proxy.api.server
+ current-context: proxy.api.server
diff --git a/templates/kubevirt/_helpers.tpl b/templates/kubevirt/_helpers.tpl
new file mode 100644
index 000000000..4ac1abff3
--- /dev/null
+++ b/templates/kubevirt/_helpers.tpl
@@ -0,0 +1,62 @@
+
+{{- define "kubevirt.apiserver_kubeproxy_patch" -}}
+ '{{ include "kubevirt.apiserver_kubeproxy_patch_tmpl" . | fromYaml | toJson }}'
+{{- end }}
+
+
+{{- define "kubevirt.apiserver_kubeproxy_patch_tmpl" -}}
+ {{- $proxyImage := include "helm_lib_module_image" (list . "kubeApiProxy") }}
+metadata:
+ annotations:
+ kubectl.kubernetes.io/default-container: virt-api
+spec:
+ template:
+ spec:
+ volumes:
+ - name: kube-api-proxy-kubeconfig
+ configMap:
+ name: kube-api-proxy-kubeconfig
+ containers:
+ - name: proxy
+ image: {{ $proxyImage }}
+ imagePullPolicy: IfNotPresent
+ resources:
+ requests:
+ {{- include "helm_lib_module_ephemeral_storage_only_logs" . | nindent 12 }}
+ {{- if not ( .Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
+ {{- include "cdi.kubeproxy_resources" . | nindent 12 }}
+ {{- end }}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ seccompProfile:
+ type: RuntimeDefault
+ terminationMessagePath: /dev/termination-log
+ terminationMessagePolicy: File
+ ports:
+ - containerPort: 24192
+ name: webhook-proxy
+ protocol: TCP
+ env:
+ - name: WEBHOOK_ADDRESS
+ value: https://127.0.0.1:8443
+ - name: WEBHOOK_CERT_FILE
+ value: /etc/virt-api/certificates/tls.crt
+ - name: WEBHOOK_KEY_FILE
+ value: /etc/virt-api/certificates/tls.key
+ volumeMounts:
+ - name: kubevirt-virt-api-certs
+ mountPath: /etc/virt-api/certificates
+ readOnly: true
+ - name: virt-api
+ command:
+ - virt-api
+ - --kubeconfig=/kubeconfig.local/proxy.kubeconfig
+ volumeMounts:
+ - name: kube-api-proxy-kubeconfig
+ mountPath: /kubeconfig.local
+{{- end -}}
+
+
diff --git a/templates/kubevirt/kubevirt.yaml b/templates/kubevirt/kubevirt.yaml
index 0f19517ca..87630ee9f 100644
--- a/templates/kubevirt/kubevirt.yaml
+++ b/templates/kubevirt/kubevirt.yaml
@@ -2,9 +2,12 @@
{{- $nodeSelectorMaster := index (include "helm_lib_node_selector" (tuple . "master") | fromYaml) "nodeSelector" | default (dict) | toJson }}
{{- $tolerationsSystem := index (include "helm_lib_tolerations" (tuple . "system") | fromYaml) "tolerations" | default (list) | toJson }}
{{- $tolerationsAnyNode := index (include "helm_lib_tolerations" (tuple . "any-node") | fromYaml) "tolerations" | default (list) | toJson }}
+{{- $proxyImage := include "helm_lib_module_image" (list . "kubeApiProxy") | toJson }}
+{{- $kubeAPIProxyRewriter := true }}
+{{- $webhookProxyPort := 24192 }}
---
-apiVersion: x.virtualization.deckhouse.io/v1
-kind: KubeVirt
+apiVersion: internal.virtualization.deckhouse.io/v1
+kind: DVPInternalKubeVirt
metadata:
name: kubevirt
namespace: d8-{{ .Chart.Name }}
@@ -27,6 +30,9 @@ spec:
- Sidecar
- VolumeSnapshotDataSource
customizeComponents:
+ flags:
+ api: {}
+ controller: {}
patches:
- resourceType: Deployment
resourceName: virt-api
@@ -52,30 +58,203 @@ spec:
resourceName: virt-exportproxy
patch: '[{"op":"replace","path":"/spec/replicas","value":0}]'
type: json
- {{- if ( .Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
- - resourceType: Deployment
- resourceName: virt-api
- patch: '[{"op":"replace","path":"/spec/template/spec/containers/0/resources/requests","value":{}}]'
- type: json
- - resourceType: Deployment
- resourceName: virt-controller
- patch: '[{"op":"replace","path":"/spec/template/spec/containers/0/resources/requests","value":{}}]'
- type: json
- - resourceType: DaemonSet
- resourceName: virt-handler
- patch: '[{"op":"replace","path":"/spec/template/spec/containers/0/resources/requests","value":{}}]'
- type: json
- {{- end }}
{{- if (include "helm_lib_ha_enabled" .) }}
- resourceType: Deployment
resourceName: virt-api
- patch: '[{"op":"replace","path":"/spec/template/spec/affinity","value":{"podAntiAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":[{"labelSelector":{"matchExpressions":[{"key":"kubevirt.io","operator":"In","values":["virt-api"]}]},"topologyKey":"kubernetes.io/hostname"}]}}}]'
- type: json
+ patch: {{ include "strategic_affinity_patch" (list "kubevirt.io" "virt-api") }}
+ type: strategic
- resourceType: Deployment
resourceName: virt-controller
- patch: '[{"op":"replace","path":"/spec/template/spec/affinity","value":{"podAntiAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":[{"labelSelector":{"matchExpressions":[{"key":"kubevirt.io","operator":"In","values":["virt-controller"]}]},"topologyKey":"kubernetes.io/hostname"}]}}}]'
+ patch: {{ include "strategic_affinity_patch" (list "kubevirt.io" "virt-controller") }}
+ type: strategic
+ {{- end }}
+
+ {{- if $kubeAPIProxyRewriter }}
+ - resourceName: virt-controller
+ resourceType: Deployment
+ patch: {{ include "nowebhook_kubeproxy_patch" (list . "virt-controller") }}
+ type: strategic
+
+ - resourceName: virt-api
+ resourceType: Deployment
+ patch: {{ include "kubevirt.apiserver_kubeproxy_patch" . }}
+ type: strategic
+
+ - resourceName: virt-handler
+ resourceType: DaemonSet
+ patch: {{ include "nowebhook_kubeproxy_patch" (list . "virt-handler") }}
+ type: strategic
+
+ - resourceName: virt-exportproxy
+ resourceType: Deployment
+ patch: {{ include "nowebhook_kubeproxy_patch" (list . "exportproxy") }}
+ type: strategic
+
+ # Patch services used in webhook configurations, add webhook-proxy port 24192.
+ - resourceName: virt-api
+ resourceType: Service
+ patch: |
+ [{"op":"replace", "path":"/spec/ports/0/name", "value":"https"}]
type: json
+ - resourceName: virt-api
+ resourceType: Service
+ patch: |
+ {"spec":{"ports":[
+ {"name": "webhook-proxy",
+ "port": {{ $webhookProxyPort }},
+ "protocol": "TCP",
+ "targetPort": "webhook-proxy"}
+ ]}}
+ type: strategic
+ - resourceName: kubevirt-operator-webhook
+ resourceType: Service
+ patch: |
+ {"spec":{"ports":[
+ {"name": "webhook-proxy",
+ "port": {{ $webhookProxyPort }},
+ "protocol": "TCP",
+ "targetPort": "webhook-proxy"}
+ ]}}
+ type: strategic
+ # Change service in webhook configuration to point to the rewriter proxy.
+ # Patch was produced with this jq command:
+ # kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io virt-api-validator -o json | jq '{"webhooks": .webhooks|map({"name":.name, "clientConfig":{"service":{"port":24192}}}) }'
+ # virt-api-webhook-proxy service is created separately.
+ - resourceName: virt-api-validator
+ resourceType: ValidatingWebhookConfiguration
+ patch: |
+ {
+ "webhooks": [
+ {
+ "name": "virt-launcher-eviction-interceptor.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachineinstances-create-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachineinstances-update-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachine-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachinereplicaset-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachinepool-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachinepreset-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "migration-create-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "migration-update-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachinesnapshot-validator.snapshot.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachinerestore-validator.snapshot.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachineexport-validator.export.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachineinstancetype-validator.instancetype.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachineclusterinstancetype-validator.instancetype.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachinepreference-validator.instancetype.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachineclusterpreference-validator.instancetype.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "kubevirt-crd-status-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "migration-policy-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "vm-clone-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ }
+ ]
+ }
+ type: strategic
+ # Change service in webhook configuration to point to the rewriter proxy.
+ # Patch was produced with this jq command:
+ # kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io virt-api-mutator -o json | jq '{"webhooks": .webhooks|map({"name":.name, "clientConfig":{"service":{"port":24192}}}) }'
+ # virt-api-webhook-proxy service is created separately.
+ - resourceName: virt-api-mutator
+ resourceType: MutatingWebhookConfiguration
+ patch: |
+ {
+ "webhooks": [
+ {
+ "name": "virtualmachines-mutator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachineinstances-mutator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "migrations-mutator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "virtualmachineclones-mutator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ }
+ ]
+ }
+ type: strategic
+ # Change service in webhook configuration to point to the rewriter proxy.
+ # Patch was produced with this jq command:
+ # kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io virt-operator-validator -o json | jq '{"webhooks": .webhooks|map({"name":.name, "clientConfig":{"service":{"port":24192}}}) }'
+ # kubevirt-operator-webhook-proxy service is created separately.
+ - resourceName: virt-operator-validator
+ resourceType: ValidatingWebhookConfiguration
+ patch: |
+ {
+ "webhooks": [
+ {
+ "name": "kubevirt-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ },
+ {
+ "name": "kubevirt-update-validator.kubevirt.io",
+ "clientConfig": {"service": {"port": {{ $webhookProxyPort }} }}
+ }
+ ]
+ }
+
+ type: strategic
{{- end }}
+
imagePullPolicy: IfNotPresent
imagePullSecrets:
- name: virtualization-module-registry
diff --git a/templates/kubevirt/virt-operator/deployment.yaml b/templates/kubevirt/virt-operator/deployment.yaml
index cb89c8603..36166118f 100644
--- a/templates/kubevirt/virt-operator/deployment.yaml
+++ b/templates/kubevirt/virt-operator/deployment.yaml
@@ -61,6 +61,8 @@ metadata:
{{- include "helm_lib_module_labels" (list . (dict "app" "virt-operator")) | nindent 2 }}
name: virt-operator
namespace: d8-{{ .Chart.Name }}
+ annotations:
+ kubectl.kubernetes.io/default-container: virt-operator
spec:
{{- include "helm_lib_deployment_strategy_and_replicas_for_ha" . | nindent 2 }}
revisionHistoryLimit: 2
@@ -88,9 +90,44 @@ spec:
topologyKey: kubernetes.io/hostname
weight: 1
containers:
+ - name: proxy
+ image: {{ include "helm_lib_module_image" (list . "kubeApiProxy") }}
+ imagePullPolicy: Always
+ resources:
+ requests:
+ {{- include "helm_lib_module_ephemeral_storage_only_logs" . | nindent 12 }}
+ {{- if not ( .Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
+ {{- include "cdi_operator_resources" . | nindent 12 }}
+ {{- end }}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ seccompProfile:
+ type: RuntimeDefault
+ terminationMessagePath: /dev/termination-log
+ terminationMessagePolicy: File
+ ports:
+ - containerPort: 24192
+ name: webhook-proxy
+ protocol: TCP
+ env:
+ - name: WEBHOOK_ADDRESS
+ value: "https://127.0.0.1:8444"
+ - name: WEBHOOK_CERT_FILE
+ value: "/etc/virt-operator/certificates/tls.crt"
+ - name: WEBHOOK_KEY_FILE
+ value: "/etc/virt-operator/certificates/tls.key"
+ volumeMounts:
+ - mountPath: /etc/virt-operator/certificates
+ name: kubevirt-operator-certs
+ readOnly: true
- name: virt-operator
{{- include "helm_lib_module_container_security_context_read_only_root_filesystem_capabilities_drop_all" . | nindent 8 }}
args:
+ - --kubeconfig
+ - /kubeconfig.local/proxy.kubeconfig
- --port
- "8443"
- -v
@@ -131,6 +168,8 @@ spec:
readOnly: true
- mountPath: /profile-data
name: profile-data
+ - mountPath: /kubeconfig.local
+ name: kube-api-proxy-kubeconfig
{{- include "helm_lib_priority_class" (tuple . "system-cluster-critical") | nindent 6 }}
{{- include "helm_lib_node_selector" (tuple . "master") | nindent 6 }}
{{- include "helm_lib_tolerations" (tuple . "any-node") | nindent 6 }}
@@ -143,3 +182,6 @@ spec:
secretName: kubevirt-operator-certs
- emptyDir: {}
name: profile-data
+ - name: kube-api-proxy-kubeconfig
+ configMap:
+ name: kube-api-proxy-kubeconfig
diff --git a/templates/kubevirt/virt-operator/rbac-for-us.yaml b/templates/kubevirt/virt-operator/rbac-for-us.yaml
index 97d02eb39..26c39807d 100644
--- a/templates/kubevirt/virt-operator/rbac-for-us.yaml
+++ b/templates/kubevirt/virt-operator/rbac-for-us.yaml
@@ -86,9 +86,9 @@ metadata:
name: d8:virtualization:kubevirt-operator
rules:
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - kubevirts
+ - dvpinternalkubevirts
verbs:
- get
- list
@@ -294,10 +294,10 @@ rules:
- delete
- patch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachines
- - virtualmachineinstances
+ - dvpinternalvirtualmachines
+ - dvpinternalvirtualmachineinstances
verbs:
- get
- list
@@ -311,15 +311,15 @@ rules:
verbs:
- get
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachines/status
+ - dvpinternalvirtualmachines/status
verbs:
- patch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstancemigrations
+ - dvpinternalvirtualmachineinstancemigrations
verbs:
- create
- get
@@ -327,9 +327,9 @@ rules:
- watch
- patch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstancepresets
+ - dvpinternalvirtualmachineinstancepresets
verbs:
- watch
- list
@@ -357,50 +357,50 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - kubevirts
+ - dvpinternalkubevirts
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachinesnapshots
- - virtualmachinerestores
- - virtualmachinesnapshotcontents
+ - dvpinternalvirtualmachinesnapshots
+ - dvpinternalvirtualmachinerestores
+ - dvpinternalvirtualmachinesnapshotcontents
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - datasources
- - datavolumes
+ - dvpinternaldatasources
+ - dvpinternaldatavolumes
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstancetypes
- - virtualmachineclusterinstancetypes
- - virtualmachinepreferences
- - virtualmachineclusterpreferences
+ - dvpinternalvirtualmachineinstancetypes
+ - dvpinternalvirtualmachineclusterinstancetypes
+ - dvpinternalvirtualmachinepreferences
+ - dvpinternalvirtualmachineclusterpreferences
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstancetypes
- - virtualmachineclusterinstancetypes
- - virtualmachinepreferences
- - virtualmachineclusterpreferences
+ - dvpinternalvirtualmachineinstancetypes
+ - dvpinternalvirtualmachineclusterinstancetypes
+ - dvpinternalvirtualmachinepreferences
+ - dvpinternalvirtualmachineclusterpreferences
verbs:
- delete
- create
@@ -408,9 +408,9 @@ rules:
- patch
- deletecollection
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - migrationpolicies
+ - dvpinternalmigrationpolicies
verbs:
- get
- list
@@ -550,24 +550,24 @@ rules:
- delete
- patch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- '*'
verbs:
- '*'
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- '*'
verbs:
- '*'
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachinepools
- - virtualmachinepools/finalizers
- - virtualmachinepools/status
- - virtualmachinepools/scale
+ - dvpinternalvirtualmachinepools
+ - dvpinternalvirtualmachinepools/finalizers
+ - dvpinternalvirtualmachinepools/status
+ - dvpinternalvirtualmachinepools/scale
verbs:
- watch
- list
@@ -577,7 +577,7 @@ rules:
- patch
- get
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- '*'
verbs:
@@ -593,7 +593,7 @@ rules:
verbs:
- update
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- '*'
verbs:
@@ -648,30 +648,30 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstancetypes
- - virtualmachineclusterinstancetypes
- - virtualmachinepreferences
- - virtualmachineclusterpreferences
+ - dvpinternalvirtualmachineinstancetypes
+ - dvpinternalvirtualmachineclusterinstancetypes
+ - dvpinternalvirtualmachinepreferences
+ - dvpinternalvirtualmachineclusterpreferences
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - migrationpolicies
+ - dvpinternalmigrationpolicies
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineclones
- - virtualmachineclones/status
- - virtualmachineclones/finalizers
+ - dvpinternalvirtualmachineclones
+ - dvpinternalvirtualmachineclones/status
+ - dvpinternalvirtualmachineclones/finalizers
verbs:
- get
- list
@@ -717,9 +717,9 @@ rules:
- get
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstances
+ - dvpinternalvirtualmachineinstances
verbs:
- update
- list
@@ -757,17 +757,17 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - kubevirts
+ - dvpinternalkubevirts
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - migrationpolicies
+ - dvpinternalmigrationpolicies
verbs:
- get
- list
@@ -781,17 +781,17 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineexports
+ - dvpinternalvirtualmachineexports
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - kubevirts
+ - dvpinternalkubevirts
verbs:
- list
- watch
@@ -864,13 +864,13 @@ rules:
verbs:
- update
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachines
- - virtualmachineinstances
- - virtualmachineinstancepresets
- - virtualmachineinstancereplicasets
- - virtualmachineinstancemigrations
+ - dvpinternalvirtualmachines
+ - dvpinternalvirtualmachineinstances
+ - dvpinternalvirtualmachineinstancepresets
+ - dvpinternalvirtualmachineinstancereplicasets
+ - dvpinternalvirtualmachineinstancemigrations
verbs:
- get
- delete
@@ -881,11 +881,11 @@ rules:
- watch
- deletecollection
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachinesnapshots
- - virtualmachinesnapshotcontents
- - virtualmachinerestores
+ - dvpinternalvirtualmachinesnapshots
+ - dvpinternalvirtualmachinesnapshotcontents
+ - dvpinternalvirtualmachinerestores
verbs:
- get
- delete
@@ -896,9 +896,9 @@ rules:
- watch
- deletecollection
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineexports
+ - dvpinternalvirtualmachineexports
verbs:
- get
- delete
@@ -909,9 +909,9 @@ rules:
- watch
- deletecollection
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineclones
+ - dvpinternalvirtualmachineclones
verbs:
- get
- delete
@@ -922,12 +922,12 @@ rules:
- watch
- deletecollection
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstancetypes
- - virtualmachineclusterinstancetypes
- - virtualmachinepreferences
- - virtualmachineclusterpreferences
+ - dvpinternalvirtualmachineinstancetypes
+ - dvpinternalvirtualmachineclusterinstancetypes
+ - dvpinternalvirtualmachinepreferences
+ - dvpinternalvirtualmachineclusterpreferences
verbs:
- get
- delete
@@ -938,9 +938,9 @@ rules:
- watch
- deletecollection
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachinepools
+ - dvpinternalvirtualmachinepools
verbs:
- get
- delete
@@ -951,9 +951,9 @@ rules:
- watch
- deletecollection
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - migrationpolicies
+ - dvpinternalmigrationpolicies
verbs:
- get
- list
@@ -1009,13 +1009,13 @@ rules:
verbs:
- update
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachines
- - virtualmachineinstances
- - virtualmachineinstancepresets
- - virtualmachineinstancereplicasets
- - virtualmachineinstancemigrations
+ - dvpinternalvirtualmachines
+ - dvpinternalvirtualmachineinstances
+ - dvpinternalvirtualmachineinstancepresets
+ - dvpinternalvirtualmachineinstancereplicasets
+ - dvpinternalvirtualmachineinstancemigrations
verbs:
- get
- delete
@@ -1025,11 +1025,11 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachinesnapshots
- - virtualmachinesnapshotcontents
- - virtualmachinerestores
+ - dvpinternalvirtualmachinesnapshots
+ - dvpinternalvirtualmachinesnapshotcontents
+ - dvpinternalvirtualmachinerestores
verbs:
- get
- delete
@@ -1039,9 +1039,9 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineexports
+ - dvpinternalvirtualmachineexports
verbs:
- get
- delete
@@ -1051,9 +1051,9 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineclones
+ - dvpinternalvirtualmachineclones
verbs:
- get
- delete
@@ -1063,12 +1063,12 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstancetypes
- - virtualmachineclusterinstancetypes
- - virtualmachinepreferences
- - virtualmachineclusterpreferences
+ - dvpinternalvirtualmachineinstancetypes
+ - dvpinternalvirtualmachineclusterinstancetypes
+ - dvpinternalvirtualmachinepreferences
+ - dvpinternalvirtualmachineclusterpreferences
verbs:
- get
- delete
@@ -1078,9 +1078,9 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachinepools
+ - dvpinternalvirtualmachinepools
verbs:
- get
- delete
@@ -1090,16 +1090,16 @@ rules:
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - kubevirts
+ - dvpinternalkubevirts
verbs:
- get
- list
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - migrationpolicies
+ - dvpinternalmigrationpolicies
verbs:
- get
- list
@@ -1120,67 +1120,67 @@ rules:
verbs:
- update
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachines
- - virtualmachineinstances
- - virtualmachineinstancepresets
- - virtualmachineinstancereplicasets
- - virtualmachineinstancemigrations
+ - dvpinternalvirtualmachines
+ - dvpinternalvirtualmachineinstances
+ - dvpinternalvirtualmachineinstancepresets
+ - dvpinternalvirtualmachineinstancereplicasets
+ - dvpinternalvirtualmachineinstancemigrations
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachinesnapshots
- - virtualmachinesnapshotcontents
- - virtualmachinerestores
+ - dvpinternalvirtualmachinesnapshots
+ - dvpinternalvirtualmachinesnapshotcontents
+ - dvpinternalvirtualmachinerestores
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineexports
+ - dvpinternalvirtualmachineexports
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineclones
+ - dvpinternalvirtualmachineclones
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachineinstancetypes
- - virtualmachineclusterinstancetypes
- - virtualmachinepreferences
- - virtualmachineclusterpreferences
+ - dvpinternalvirtualmachineinstancetypes
+ - dvpinternalvirtualmachineclusterinstancetypes
+ - dvpinternalvirtualmachinepreferences
+ - dvpinternalvirtualmachineclusterpreferences
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachinepools
+ - dvpinternalvirtualmachinepools
verbs:
- get
- list
- watch
- deletecollection
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - migrationpolicies
+ - dvpinternalmigrationpolicies
verbs:
- get
- list
diff --git a/templates/kubevirt/vmi-router/rbac-for-us.yaml b/templates/kubevirt/vmi-router/rbac-for-us.yaml
index ca191d4e4..4e5ad8411 100644
--- a/templates/kubevirt/vmi-router/rbac-for-us.yaml
+++ b/templates/kubevirt/vmi-router/rbac-for-us.yaml
@@ -14,9 +14,9 @@ metadata:
{{- include "helm_lib_module_labels" (list . (dict "app" "vmi-router")) | nindent 2 }}
rules:
- apiGroups:
- - x.virtualization.deckhouse.io
+ - virtualization.deckhouse.io
resources:
- - virtualmachineinstances
+ - virtualmachines
verbs:
- get
- list
diff --git a/templates/pre-delete-hook/rbac-for-us.yaml b/templates/pre-delete-hook/rbac-for-us.yaml
index 96bf862c9..84b4d0f43 100644
--- a/templates/pre-delete-hook/rbac-for-us.yaml
+++ b/templates/pre-delete-hook/rbac-for-us.yaml
@@ -15,10 +15,10 @@ metadata:
{{- include "helm_lib_module_labels" (list . (dict "app" "virtualization-pre-delete-hook")) | nindent 2 }}
rules:
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - cdis
- - kubevirts
+ - dvpinternalcdis
+ - dvpinternalkubevirts
verbs:
- get
- delete
diff --git a/templates/virtualization-controller/_helpers.tpl b/templates/virtualization-controller/_helpers.tpl
index 00cbe7644..529e96ec1 100644
--- a/templates/virtualization-controller/_helpers.tpl
+++ b/templates/virtualization-controller/_helpers.tpl
@@ -1,5 +1,7 @@
{{- define "virtualization-controller.envs" -}}
{{- $registry := include "dvcr.get_registry" (list .) }}
+- name: KUBECONFIG
+ value: "/kubeconfig.local/proxy.kubeconfig"
- name: VERBOSITY
value: "3"
- name: FORCE_BRIDGE_NETWORK_BINDING
diff --git a/templates/virtualization-controller/deployment.yaml b/templates/virtualization-controller/deployment.yaml
index 9f86fe578..3ebfcc4e5 100644
--- a/templates/virtualization-controller/deployment.yaml
+++ b/templates/virtualization-controller/deployment.yaml
@@ -45,6 +45,8 @@ metadata:
name: virtualization-controller
namespace: d8-{{ .Chart.Name }}
{{- include "helm_lib_module_labels" (list . (dict "app" "virtualization-controller")) | nindent 2 }}
+ annotations:
+ kubectl.kubernetes.io/default-container: virtualization-controller
spec:
{{- include "helm_lib_deployment_strategy_and_replicas_for_ha" . | nindent 2 }}
revisionHistoryLimit: 2
@@ -60,12 +62,32 @@ spec:
spec:
{{ include "helm_lib_pod_anti_affinity_for_ha" (list . (dict "app" "virtualization-controller")) | nindent 6 }}
containers:
+ - name: proxy
+ image: {{ include "helm_lib_module_image" (list . "kubeApiProxy") }}
+ imagePullPolicy: IfNotPresent
+ resources:
+ requests:
+ {{- include "helm_lib_module_ephemeral_storage_only_logs" . | nindent 12 }}
+ {{- if not ( .Values.global.enabledModules | has "vertical-pod-autoscaler-crd") }}
+ {{- include "virtualization_controller_resources" . | nindent 12 }}
+ {{- end }}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ seccompProfile:
+ type: RuntimeDefault
+ terminationMessagePath: /dev/termination-log
+ terminationMessagePolicy: File
- name: virtualization-controller
image: {{ include "helm_lib_module_image" (list . "virtualizationController") }}
imagePullPolicy: Always
volumeMounts:
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: admission-webhook-secret
+ - mountPath: /kubeconfig.local
+ name: kube-api-proxy-kubeconfig
ports:
- containerPort: 9443
name: controller
@@ -80,6 +102,7 @@ spec:
{{- include "virtualization_controller_resources" . | nindent 12 }}
{{- end }}
env: {{ include "virtualization-controller.envs" . | nindent 12 }}
+
dnsPolicy: ClusterFirst
serviceAccountName: virtualization-controller
{{- include "helm_lib_priority_class" (tuple . "system-cluster-critical") | nindent 6 }}
@@ -89,3 +112,6 @@ spec:
- name: admission-webhook-secret
secret:
secretName: virtualization-controller-tls
+ - name: kube-api-proxy-kubeconfig
+ configMap:
+ name: kube-api-proxy-kubeconfig
diff --git a/templates/virtualization-controller/rbac-for-us.yaml b/templates/virtualization-controller/rbac-for-us.yaml
index f7d4ef69c..a4617a464 100644
--- a/templates/virtualization-controller/rbac-for-us.yaml
+++ b/templates/virtualization-controller/rbac-for-us.yaml
@@ -87,9 +87,9 @@ rules:
- create
- patch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - datavolumes
+ - dvpinternaldatavolumes
verbs:
- get
- create
@@ -98,10 +98,10 @@ rules:
- watch
- list
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachines
- - virtualmachineinstances
+ - dvpinternalvirtualmachines
+ - dvpinternalvirtualmachineinstances
verbs:
- get
- watch
@@ -111,17 +111,17 @@ rules:
- list
- delete
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - kubevirts
+ - dvpinternalkubevirts
verbs:
- get
- list
- watch
- apiGroups:
- - x.virtualization.deckhouse.io
+ - internal.virtualization.deckhouse.io
resources:
- - virtualmachines/status
+ - dvpinternalvirtualmachines/status
verbs:
- patch
- update