From cfb9df99e57e6b0d464dd8b85d4c91ff2c28982f Mon Sep 17 00:00:00 2001 From: Ben Meier Date: Fri, 23 Feb 2024 16:27:19 +0000 Subject: [PATCH] chore: added ApplyCommonUpgradeTransforms Signed-off-by: Ben Meier --- schema/validate.go | 59 +++++++++++++++++++++++++++++++++++++++++ schema/validate_test.go | 41 ++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/schema/validate.go b/schema/validate.go index 3998265..4ac3987 100644 --- a/schema/validate.go +++ b/schema/validate.go @@ -11,6 +11,7 @@ import ( "encoding/json" "fmt" "io" + "strings" "github.com/santhosh-tekuri/jsonschema/v5" "gopkg.in/yaml.v3" @@ -56,3 +57,61 @@ func Validate(src interface{}) error { return schema.Validate(src) } + +// ApplyCommonUpgradeTransforms when we fix aspects of the score spec over time, we sometimes need to break compatibility. +// To reduce affects on users, we can apply a sequence of transformations to the yaml decoded structure so that we can +// fix things on their behalf. This reduces the impact on existing workflows. This function returns messages regarding +// any changes it has made or an error if the structure was unexpected. +// NOTE: this method should only be used for tools or utilities where there is already an established use-case and +// workflow for example score-compose and score-humanitec. +func ApplyCommonUpgradeTransforms(rawScore map[string]interface{}) ([]string, error) { + changes := make([]string, 0) + + if containersStruct, ok := rawScore["containers"].(map[string]interface{}); ok { + for name, rawContainerStruct := range containersStruct { + containerStruct, ok := rawContainerStruct.(map[string]interface{}) + if !ok { + continue + } + + // We no longer support multi-line content. Update any arrays in line to be newline-separated + if filesStruct, ok := containerStruct["files"].([]interface{}); ok { + for i, rawFileStruct := range filesStruct { + fileStruct, ok := rawFileStruct.(map[string]interface{}) + if !ok { + continue + } + if before, ok := fileStruct["content"].([]interface{}); ok { + delete(fileStruct, "content") + sb := new(strings.Builder) + for il, line := range before { + if il > 0 { + sb.WriteRune('\n') + } + sb.WriteString(fmt.Sprint(line)) + } + fileStruct["content"] = sb.String() + changes = append(changes, fmt.Sprintf("containers.%s.files.%d.content: converted from array", name, i)) + } + } + } + + // We have fixed the naming of the read_only field. It is now readOnly. + if volumesStruct, ok := containerStruct["volumes"].([]interface{}); ok { + for i, rawVolumeStruct := range volumesStruct { + volumeStruct, ok := rawVolumeStruct.(map[string]interface{}) + if !ok { + continue + } + if before, ok := volumeStruct["read_only"].(bool); ok { + delete(volumeStruct, "read_only") + volumeStruct["readOnly"] = before + changes = append(changes, fmt.Sprintf("containers.%s.volumes.%d.read_only: migrated to readOnly", name, i)) + } + } + } + } + } + + return changes, nil +} diff --git a/schema/validate_test.go b/schema/validate_test.go index 38aa071..259902f 100644 --- a/schema/validate_test.go +++ b/schema/validate_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) func TestValidateYaml(t *testing.T) { @@ -398,3 +399,43 @@ containers: err := ValidateYaml(bytes.NewReader(source)) assert.EqualError(t, err, "jsonschema: '/metadata' does not validate with https://score.dev/schemas/score#/properties/metadata/required: missing properties: 'name'") } + +func TestApplyCommonUpgradeTransforms(t *testing.T) { + var source = []byte(` +--- +apiVersion: score.dev/v1b1 +metadata: + name: hello-world +containers: + hello: + image: busybox + files: + - target: /etc/hello-world/config.yaml + mode: "666" + content: + - line1 + - line2 + volumes: + - source: ${resources.data} + target: /mnt/data + read_only: true +`) + + var obj map[string]interface{} + var dec = yaml.NewDecoder(bytes.NewReader(source)) + assert.NoError(t, dec.Decode(&obj)) + + // first validation attempt should fail + assert.Error(t, Validate(obj)) + + // apply transforms + changes, err := ApplyCommonUpgradeTransforms(obj) + assert.NoError(t, err) + assert.Len(t, changes, 2) + + // second validation attempt should succeed + assert.NoError(t, Validate(obj)) + + assert.Equal(t, "line1\nline2", obj["containers"].(map[string]interface{})["hello"].(map[string]interface{})["files"].([]interface{})[0].(map[string]interface{})["content"]) + assert.Equal(t, true, obj["containers"].(map[string]interface{})["hello"].(map[string]interface{})["volumes"].([]interface{})[0].(map[string]interface{})["readOnly"]) +}