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 Apr 30, 2024
1 parent a7fe028 commit 4ebe224
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 21 deletions.
2 changes: 1 addition & 1 deletion controllers/shipwrightbuild_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +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.TruncateField("description", 50))
transformerfncs = append(transformerfncs, common.TruncateCRDFieldTransformer("description", 50))
if common.IsOpenShiftPlatform() {
transformerfncs = append(transformerfncs, manifestival.InjectNamespace(targetNamespace))
transformerfncs = append(transformerfncs, common.DeploymentImages(images))
Expand Down
44 changes: 25 additions & 19 deletions pkg/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,36 +87,42 @@ func ToLowerCaseKeys(keyValues map[string]string) map[string]string {
return newMap
}

// Recursively truncates the given "field" from CustomResourceDefinition to 50 characters.
func truncateFieldRecursively(data map[string]interface{}, maxLength int, field string) {
for key, value := range data {
if key == field {
if str, ok := value.(string); ok && len(str) > maxLength {
data[key] = str[:maxLength]
}
continue
}
if subObj, ok := value.(map[string]interface{}); ok {
truncateFieldRecursively(subObj, 50, field)
}
if subObjs, ok := value.([]interface{}); ok {
for _, subObj := range subObjs {
if subObjMap, ok := subObj.(map[string]interface{}); ok {
truncateFieldRecursively(subObjMap, 50, field)
// 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)
}
}
}
}
}
}
}

// truncates the given "field" from CustomResourceDefinition to 50 characters.
func TruncateField(field string, targetLength int) manifestival.Transformer {
// 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
truncateFieldRecursively(data, targetLength, field)
truncateNestedFields(data, maxLength, field)
return nil
}
}
Expand Down
94 changes: 93 additions & 1 deletion pkg/common/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
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 +62,98 @@ 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) {
crdYaml := `
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: test.crd.com
spec:
group: crd.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
properties:
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
`
u := &unstructured.Unstructured{}
yaml.Unmarshal([]byte(crdYaml), &u.Object)
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 4ebe224

Please sign in to comment.