Skip to content

Commit

Permalink
Merge pull request kubevirt#3612 from ashleyschuett/patch-configuration
Browse files Browse the repository at this point in the history
add patches to virt-controller
  • Loading branch information
kubevirt-bot authored Sep 7, 2020
2 parents 7b70b99 + 55b3d65 commit 78f37c8
Show file tree
Hide file tree
Showing 16 changed files with 775 additions and 73 deletions.
15 changes: 15 additions & 0 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -6626,6 +6626,18 @@
}
}
},
"v1.CustomizeComponents": {
"type": "object",
"properties": {
"patches": {
"type": "array",
"items": {
"$ref": "#/definitions/v1.Patch"
},
"x-kubernetes-list-type": "atomic"
}
}
},
"v1.DHCPOptions": {
"description": "Extra DHCP options to use in the interface.",
"type": "object",
Expand Down Expand Up @@ -7559,6 +7571,9 @@
"description": "holds kubevirt configurations. same as the virt-configMap",
"$ref": "#/definitions/v1.KubeVirtConfiguration"
},
"customizeComponents": {
"$ref": "#/definitions/v1.CustomizeComponents"
},
"imagePullPolicy": {
"description": "The ImagePullPolicy to use.",
"type": "string"
Expand Down
1 change: 1 addition & 0 deletions manifests/generated/kubevirt-cr.yaml.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ metadata:
spec:
certificateRotateStrategy: {}
configuration: {}
customizeComponents: {}
imagePullPolicy: {{.ImagePullPolicy}}
5 changes: 5 additions & 0 deletions pkg/virt-operator/install-strategy/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go_library(
"create.go",
"delete.go",
"generated_mock_create.go",
"patches.go",
"strategy.go",
],
importpath = "kubevirt.io/kubevirt/pkg/virt-operator/install-strategy",
Expand All @@ -24,6 +25,7 @@ go_library(
"//tools/util:go_default_library",
"//vendor/github.com/blang/semver:go_default_library",
"//vendor/github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/openshift/api/security/v1:go_default_library",
Expand All @@ -35,7 +37,9 @@ go_library(
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1:go_default_library",
],
Expand All @@ -46,6 +50,7 @@ go_test(
srcs = [
"create_test.go",
"install_strategy_suite_test.go",
"patches_test.go",
"strategy_test.go",
],
embed = [":go_default_library"],
Expand Down
155 changes: 97 additions & 58 deletions pkg/virt-operator/install-strategy/create.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/virt-operator/install-strategy/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ var _ = Describe("Create", func() {
kv.Status.TargetDeploymentID = Id

mockPodDisruptionBudgetCacheStore.get = cachedPodDisruptionBudget
injectOperatorMetadata(kv, &cachedPodDisruptionBudget.ObjectMeta, Version, Registry, Id)
injectOperatorMetadata(kv, &cachedPodDisruptionBudget.ObjectMeta, Version, Registry, Id, true)

err = syncPodDisruptionBudgetForDeployment(deployment, clientset, kv, expectations, stores)

Expand Down
171 changes: 171 additions & 0 deletions pkg/virt-operator/install-strategy/patches.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package installstrategy

import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"
"strings"

jsonpatch "github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/strategicpatch"

v1 "kubevirt.io/client-go/api/v1"
)

type Customizer struct {
Patches []v1.Patch

hash string
}

func NewCustomizer(customizations v1.CustomizeComponents) (*Customizer, error) {
hash, err := getHash(customizations)
if err != nil {
return &Customizer{}, err
}

return &Customizer{
Patches: customizations.Patches,
hash: hash,
}, nil
}

func (c *Customizer) GenericApplyPatches(objects interface{}) error {
switch reflect.TypeOf(objects).Kind() {
case reflect.Slice:
s := reflect.ValueOf(objects)
for i := 0; i < s.Len(); i++ {
o := s.Index(i)
obj, ok := o.Interface().(runtime.Object)
if !ok {
return errors.New("Slice must contain objects of type 'runtime.Object'")
}

kind := obj.GetObjectKind().GroupVersionKind().Kind

v := reflect.Indirect(o).FieldByName("ObjectMeta").FieldByName("Name")
name := v.String()

patches := c.GetPatchesForResource(kind, name)

patches = append(patches, v1.Patch{
Patch: fmt.Sprintf(`{"metadata":{"annotations":{"%s":"%s"}}}`, v1.KubeVirtCustomizeComponentAnnotationHash, c.hash),
Type: v1.StrategicMergePatchType,
})

err := applyPatches(obj, patches)
if err != nil {
return err
}
}
}

return nil
}

func applyPatches(obj runtime.Object, patches []v1.Patch) error {
if len(patches) == 0 {
return nil
}

for _, p := range patches {
err := applyPatch(obj, p)
if err != nil {
return err
}
}

return nil
}

func applyPatch(obj runtime.Object, patch v1.Patch) error {
if obj == nil {
return nil
}

old, err := json.Marshal(obj)
if err != nil {
return err
}

// reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields
// in obj that are removed by patch are cleared
value := reflect.ValueOf(obj)
value.Elem().Set(reflect.New(value.Type().Elem()).Elem())

switch patch.Type {
case v1.JSONPatchType:
patch, err := jsonpatch.DecodePatch([]byte(patch.Patch))
if err != nil {
return err
}
modified, err := patch.Apply(old)
if err != nil {
return err
}

if err = json.Unmarshal(modified, obj); err != nil {
return err
}
case v1.MergePatchType:
modified, err := jsonpatch.MergePatch(old, []byte(patch.Patch))
if err != nil {
return err
}

if err := json.Unmarshal(modified, obj); err != nil {
return err
}
case v1.StrategicMergePatchType:
mergedByte, err := strategicpatch.StrategicMergePatch(old, []byte(patch.Patch), obj)
if err != nil {
return err
}

if err = json.Unmarshal(mergedByte, obj); err != nil {
return err
}
default:
return fmt.Errorf("PatchType is not supported")
}

return nil
}

func (c *Customizer) GetPatches() []v1.Patch {
return c.Patches
}

func (c *Customizer) GetPatchesForResource(resourceType, name string) []v1.Patch {
allPatches := c.Patches
patches := make([]v1.Patch, 0)

for _, p := range allPatches {
if strings.EqualFold(p.ResourceType, resourceType) && strings.EqualFold(p.ResourceName, name) {
patches = append(patches, p)
}
}

return patches
}

func getHash(customizations v1.CustomizeComponents) (string, error) {
hasher := sha1.New()

sort.SliceStable(customizations.Patches, func(i, j int) bool {
return len(customizations.Patches[i].Patch) < len(customizations.Patches[j].Patch)
})

values, err := json.Marshal(customizations)
if err != nil {
return "", err
}
hasher.Write(values)

return hex.EncodeToString(hasher.Sum(nil)), nil
}
149 changes: 149 additions & 0 deletions pkg/virt-operator/install-strategy/patches_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* This file is part of the KubeVirt project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Red Hat, Inc.
*
*/

package installstrategy

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"kubevirt.io/client-go/log"

appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

v1 "kubevirt.io/client-go/api/v1"
)

var _ = Describe("Patches", func() {
log.Log.SetIOWriter(GinkgoWriter)

namespace := "fake-namespace"

deployment := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "virt-controller",
},
Spec: appsv1.DeploymentSpec{},
}

getCustomizer := func() *Customizer {
c, _ := NewCustomizer(v1.CustomizeComponents{
Patches: []v1.Patch{
{
ResourceName: "virt-controller",
ResourceType: "Deployment",
Patch: `{"metadata":{"labels":{"new-key":"added-this-label"}}}`,
Type: v1.StrategicMergePatchType,
},
},
})

return c
}

config := getCustomizer()

Context("generically apply patches", func() {

It("should apply to deployments", func() {
deployments := []*appsv1.Deployment{
deployment,
}

err := config.GenericApplyPatches(deployments)
Expect(err).ToNot(HaveOccurred())
Expect(deployment.ObjectMeta.Labels["new-key"]).To(Equal("added-this-label"))

err = config.GenericApplyPatches([]string{"string"})
Expect(err).To(HaveOccurred())
})
})

Context("apply patch", func() {

It("should not error on empty patch", func() {
err := applyPatch(nil, v1.Patch{})
Expect(err).ToNot(HaveOccurred())
})
})

Context("get hash", func() {

It("should be equal", func() {
c1 := v1.CustomizeComponents{
Patches: []v1.Patch{
{
ResourceName: "virt-controller",
ResourceType: "Deployment",
Patch: `{"metadata":{"labels":{"new-key":"added-this-label"}}}`,
Type: v1.StrategicMergePatchType,
},
{
ResourceName: "virt-api",
ResourceType: "Deployment",
Patch: `{"metadata":{"labels":{"my-custom-label":"custom-label"}}}`,
Type: v1.StrategicMergePatchType,
},
{
ResourceName: "virt-controller",
ResourceType: "Deployment",
Patch: `{"metadata":{"annotation":{"key":"value"}}}`,
Type: v1.StrategicMergePatchType,
},
},
}

c2 := v1.CustomizeComponents{
Patches: []v1.Patch{
{
ResourceName: "virt-api",
ResourceType: "Deployment",
Patch: `{"metadata":{"labels":{"my-custom-label":"custom-label"}}}`,
Type: v1.StrategicMergePatchType,
},
{
ResourceName: "virt-controller",
ResourceType: "Deployment",
Patch: `{"metadata":{"labels":{"new-key":"added-this-label"}}}`,
Type: v1.StrategicMergePatchType,
},
{
ResourceName: "virt-controller",
ResourceType: "Deployment",
Patch: `{"metadata":{"annotation":{"key":"value"}}}`,
Type: v1.StrategicMergePatchType,
},
},
}

h1, err := getHash(c1)
Expect(err).ToNot(HaveOccurred())
h2, err := getHash(c2)
Expect(err).ToNot(HaveOccurred())

Expect(h1).To(Equal(h2))
})
})
})
Loading

0 comments on commit 78f37c8

Please sign in to comment.