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