Skip to content

Commit

Permalink
chore: added ApplyCommonUpgradeTransforms
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Meier <[email protected]>
  • Loading branch information
astromechza committed Feb 23, 2024
1 parent f07ba2c commit cfb9df9
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 0 deletions.
59 changes: 59 additions & 0 deletions schema/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"encoding/json"
"fmt"
"io"
"strings"

"github.com/santhosh-tekuri/jsonschema/v5"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -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
}
41 changes: 41 additions & 0 deletions schema/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)

func TestValidateYaml(t *testing.T) {
Expand Down Expand Up @@ -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"])
}

0 comments on commit cfb9df9

Please sign in to comment.