Skip to content

Commit

Permalink
adding a manifestival transformer and unit tests for the same for CRD…
Browse files Browse the repository at this point in the history
… field truncation. It truncates description fields in CRDs to apply a nightly build in the operator and potentially paving way for syncing nightly release of shipwright/builds with shipwright/operator
  • Loading branch information
ayushsatyam146 committed May 3, 2024
1 parent 1000268 commit 8f57b3b
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 1 deletion.
1 change: 1 addition & 0 deletions controllers/shipwrightbuild_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ func (r *ShipwrightBuildReconciler) Reconcile(ctx context.Context, req ctrl.Requ
images := common.ToLowerCaseKeys(common.ImagesFromEnv(common.ShipwrightImagePrefix))

transformerfncs := []manifestival.Transformer{}
transformerfncs = append(transformerfncs, common.TruncateCRDFieldTransformer("description", 50))
if common.IsOpenShiftPlatform() {
transformerfncs = append(transformerfncs, manifestival.InjectNamespace(targetNamespace))
transformerfncs = append(transformerfncs, common.DeploymentImages(images))
Expand Down
27 changes: 27 additions & 0 deletions pkg/common/testdata/test-truncate-crd-field.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: test.crd.com
spec:
group: crd.com
versions:
- name: v1
served: true
description: This is a long string that should be truncated
storage: true
schema:
openAPIV3Schema:
properties:
description: This is a long string that should be truncated
spec:
properties:
field1:
type: string
description: This is a long string that should be truncated
scope: Namespaced
names:
plural: tests
singular: test
kind: Test
shortNames:
- tst
40 changes: 40 additions & 0 deletions pkg/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,46 @@ func ToLowerCaseKeys(keyValues map[string]string) map[string]string {
return newMap
}

// truncateNestedFields truncates the named "field" from the given data object and all of its sub-objects to maxLength characters.
func truncateNestedFields(data map[string]interface{}, maxLength int, field string) {
queue := []map[string]interface{}{data}

for len(queue) > 0 {
curr := queue[0]
queue = queue[1:]

for key, value := range curr {
if key == field {
if str, ok := value.(string); ok && len(str) > maxLength {
curr[key] = str[:maxLength]
}
} else {
if subObj, ok := value.(map[string]interface{}); ok {
queue = append(queue, subObj)
} else if subObjs, ok := value.([]interface{}); ok {
for _, subObj := range subObjs {
if subObjMap, ok := subObj.(map[string]interface{}); ok {
queue = append(queue, subObjMap)
}
}
}
}
}
}
}

// TruncateCRDFieldTransformer returns a manifestival.Transformer that truncates the value of the given field within a CRD spec to the provided max length.
func TruncateCRDFieldTransformer(field string, maxLength int) manifestival.Transformer {
return func(u *unstructured.Unstructured) error {
if u.GetKind() != "CustomResourceDefinition" {
return nil
}
data := u.Object
truncateNestedFields(data, maxLength, field)
return nil
}
}

// deploymentImages replaces container and env vars images.
func DeploymentImages(images map[string]string) manifestival.Transformer {
return func(u *unstructured.Unstructured) error {
Expand Down
71 changes: 70 additions & 1 deletion pkg/common/util_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package common

import (
"os"
"path"
"testing"

mf "github.com/manifestival/manifestival"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"gopkg.in/yaml.v2"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -62,6 +63,74 @@ func TestDeploymentImages(t *testing.T) {
})
}

func TestTruncateNestedFields(t *testing.T) {
RegisterFailHandler(Fail)
t.Run("test truncation of manifests", func(t *testing.T) {
testData := map[string]interface{}{
"field1": "This is a long string that should be truncated",
"field2": map[string]interface{}{
"field1": "This is another long string that should be truncated",
},
}

expected := map[string]interface{}{
"field1": "This is a ",
"field2": map[string]interface{}{
"field1": "This is an",
},
}

truncateNestedFields(testData, 10, "field1")
Expect(testData).To(Equal(expected))
})
}

func CheckNestedFieldLengthWithinLimit(data map[string]interface{}, maxLength int, field string) bool {
isFieldSizeInLimit := true
queue := []map[string]interface{}{data}

for len(queue) > 0 {
curr := queue[0]
queue = queue[1:]

for key, value := range curr {
if key == field {
if str, ok := value.(string); ok {
isFieldSizeInLimit = isFieldSizeInLimit && (len(str) <= maxLength)
}
} else {
if subObj, ok := value.(map[string]interface{}); ok {
queue = append(queue, subObj)
} else if subObjs, ok := value.([]interface{}); ok {
for _, subObj := range subObjs {
if subObjMap, ok := subObj.(map[string]interface{}); ok {
queue = append(queue, subObjMap)
}
}
}
}
}
}

return isFieldSizeInLimit
}

func TestTruncateCRDFieldTransformer(t *testing.T) {
RegisterFailHandler(Fail)
t.Run("test truncate CRD field Transformer", func(t *testing.T) {
testData, err := os.ReadFile(path.Join("testdata", "test-truncate-crd-field.yaml"))
Expect(err).NotTo(HaveOccurred())

u := &unstructured.Unstructured{}
err = yaml.Unmarshal(testData, u)
Expect(err).NotTo(HaveOccurred())

TruncateCRDFieldTransformer("description", 10)(u)
isDscriptionTruncated := CheckNestedFieldLengthWithinLimit(u.Object, 10, "description")
Expect(isDscriptionTruncated).To(Equal(true))
})
}

func deploymentFor(t *testing.T, unstr unstructured.Unstructured) *appsv1.Deployment {
deployment := &appsv1.Deployment{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, deployment)
Expand Down

0 comments on commit 8f57b3b

Please sign in to comment.