From 703292249a5c02a6e687dd2942f5e86d322965d3 Mon Sep 17 00:00:00 2001 From: Ben Meier <1651305+astromechza@users.noreply.github.com> Date: Mon, 20 May 2024 17:04:30 +0100 Subject: [PATCH] fix(types): universal improvement to metadata decoding (#40) Signed-off-by: Ben Meier --- framework/state.go | 19 ------------ framework/state_test.go | 69 +++++++++++++++++++++++++++++++++++++++++ types/types.go | 18 +++++++++++ 3 files changed, 87 insertions(+), 19 deletions(-) diff --git a/framework/state.go b/framework/state.go index 14a9a31..e186419 100644 --- a/framework/state.go +++ b/framework/state.go @@ -97,12 +97,6 @@ func (s *State[StateExtras, WorkloadExtras, ResourceExtras]) WithWorkload(spec * out.Workloads = maps.Clone(s.Workloads) } - spec.Metadata = coerceAnnotationsType(spec.Metadata) - for resName, resource := range spec.Resources { - resource.Metadata = coerceAnnotationsType(resource.Metadata) - spec.Resources[resName] = resource - } - name, ok := spec.Metadata["name"].(string) if !ok { return nil, fmt.Errorf("metadata: name: is missing or is not a string") @@ -182,19 +176,6 @@ func (s *State[StateExtras, WorkloadExtras, ResourceExtras]) WithPrimedResources return &out, nil } -// The metadata maps (workload metadata and resource metadata) decode in a weird way in yaml.v3 which causes type -// coercions elsewhere in the software to fail. We have to rewrite the type to be clear. -func coerceAnnotationsType(in map[string]interface{}) map[string]interface{} { - if a, ok := in["annotations"].(score.WorkloadMetadata); ok { - in = maps.Clone(in) - in["annotations"] = map[string]interface{}(a) - } else if a, ok := in["annotations"].(score.ResourceMetadata); ok { - in = maps.Clone(in) - in["annotations"] = map[string]interface{}(a) - } - return in -} - func (s *State[StateExtras, WorkloadExtras, ResourceExtras]) getResourceDependencies(workloadName, resName string) (map[ResourceUid]bool, error) { outMap := make(map[ResourceUid]bool) res := s.Workloads[workloadName].Spec.Resources[resName] diff --git a/framework/state_test.go b/framework/state_test.go index 4754de9..b506f54 100644 --- a/framework/state_test.go +++ b/framework/state_test.go @@ -15,6 +15,7 @@ package framework import ( + "encoding/json" "testing" "github.com/stretchr/testify/assert" @@ -190,15 +191,25 @@ resources: id: elephant metadata: x: a + annotations: + acme.org/x: y two: type: thing id: elephant metadata: x: a + annotations: + acme.org/x: y `) next, err := next.WithPrimedResources() assert.NoError(t, err) assert.Len(t, next.Resources, 1) + assert.Equal(t, map[string]interface{}{ + "annotations": map[string]interface{}{ + "acme.org/x": "y", + }, + "x": "a", + }, next.Resources["thing.default#elephant"].Metadata) }) t.Run("one workload - same resource - diff metadata", func(t *testing.T) { @@ -291,6 +302,64 @@ resources: } +func TestEncodeDecodeMetadata_yaml(t *testing.T) { + state := new(State[NoExtras, NoExtras, NoExtras]) + state = mustAddWorkload(t, state, ` +apiVersion: score.dev/v1b1 +metadata: + name: example + annotations: + acme.org/x: a +containers: + main: + image: nginx +resources: + eg: + type: example + metadata: + annotations: + acme.org/x: b +`) + raw, err := yaml.Marshal(state) + assert.NoError(t, err) + state = new(State[NoExtras, NoExtras, NoExtras]) + assert.NoError(t, yaml.Unmarshal(raw, &state)) + assert.Equal(t, score.WorkloadMetadata{"name": "example", "annotations": map[string]interface{}{"acme.org/x": "a"}}, state.Workloads["example"].Spec.Metadata) + assert.Equal(t, score.ResourceMetadata{"annotations": map[string]interface{}{"acme.org/x": "b"}}, state.Workloads["example"].Spec.Resources["eg"].Metadata) + + state, err = state.WithPrimedResources() + assert.NoError(t, err) + assert.Equal(t, score.WorkloadMetadata{"name": "example", "annotations": map[string]interface{}{"acme.org/x": "a"}}, state.Workloads["example"].Spec.Metadata) + assert.Equal(t, score.ResourceMetadata{"annotations": map[string]interface{}{"acme.org/x": "b"}}, state.Workloads["example"].Spec.Resources["eg"].Metadata) + assert.Equal(t, map[string]interface{}{"annotations": map[string]interface{}{"acme.org/x": "b"}}, state.Resources["example.default#example.eg"].Metadata) +} + +func TestEncodeDecodeMetadata_json(t *testing.T) { + state := new(State[NoExtras, NoExtras, NoExtras]) + state = mustAddWorkload(t, state, ` +apiVersion: score.dev/v1b1 +metadata: + name: example + annotations: + acme.org/x: a +containers: + main: + image: nginx +resources: + eg: + type: example + metadata: + annotations: + acme.org/x: b +`) + raw, err := json.Marshal(state) + assert.NoError(t, err) + state = new(State[NoExtras, NoExtras, NoExtras]) + assert.NoError(t, json.Unmarshal(raw, &state)) + assert.Equal(t, score.WorkloadMetadata{"name": "example", "annotations": map[string]interface{}{"acme.org/x": "a"}}, state.Workloads["example"].Spec.Metadata) + assert.Equal(t, score.ResourceMetadata{"annotations": map[string]interface{}{"acme.org/x": "b"}}, state.Workloads["example"].Spec.Resources["eg"].Metadata) +} + func TestGetSortedResourceUids(t *testing.T) { t.Run("empty", func(t *testing.T) { diff --git a/types/types.go b/types/types.go index 5eb92bf..7e7a252 100644 --- a/types/types.go +++ b/types/types.go @@ -15,3 +15,21 @@ package types //go:generate go run github.com/atombender/go-jsonschema@v0.15.0 -v --schema-output=https://score.dev/schemas/score=types.gen.go --schema-package=https://score.dev/schemas/score=types --schema-root-type=https://score.dev/schemas/score=Workload ../schema/files/score-v1b1.json.modified + +func (m *ResourceMetadata) UnmarshalYAML(unmarshal func(interface{}) error) error { + var out map[string]interface{} + if err := unmarshal(&out); err != nil { + return err + } + *m = ResourceMetadata(out) + return nil +} + +func (m *WorkloadMetadata) UnmarshalYAML(unmarshal func(interface{}) error) error { + var out map[string]interface{} + if err := unmarshal(&out); err != nil { + return err + } + *m = WorkloadMetadata(out) + return nil +}