Fix: unmarshal key values in Java, Go, and Conan metadata (#2603)

Previously, Syft represented several metadata fields as map[string]string,
however this representation erased ordering, so Syft now represents these values
as []KeyValue. Add custom unmarshaling so that JSON that was written by
older versions of Syft using the map[string]string representation can be parsed
into the new []KeyValue representation.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
This commit is contained in:
William Murphy
2024-02-07 11:26:23 -05:00
committed by GitHub
parent bbd34f61fd
commit ce67927a98
5 changed files with 340 additions and 1 deletions
+56
View File
@@ -1,6 +1,9 @@
package pkg
import (
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/anchore/syft/internal"
@@ -70,6 +73,59 @@ type JavaManifest struct {
Sections []KeyValues `json:"sections,omitempty"`
}
type unmarshalJavaManifest JavaManifest
type legacyJavaManifest struct {
Main map[string]string `json:"main"`
NamedSections map[string]map[string]string `json:"namedSections"`
}
func (m *JavaManifest) UnmarshalJSON(b []byte) error {
var either map[string]any
err := json.Unmarshal(b, &either)
if err != nil {
return fmt.Errorf("could not unmarshal java manifest: %w", err)
}
if _, ok := either["namedSections"]; ok {
var lm legacyJavaManifest
if err = json.Unmarshal(b, &lm); err != nil {
return fmt.Errorf("could not unmarshal java manifest: %w", err)
}
*m = lm.toNewManifest()
return nil
}
var jm unmarshalJavaManifest
err = json.Unmarshal(b, &jm)
if err != nil {
return fmt.Errorf("could not unmarshal java manifest: %w", err)
}
*m = JavaManifest(jm)
return nil
}
func (lm legacyJavaManifest) toNewManifest() JavaManifest {
var result JavaManifest
result.Main = keyValuesFromMap(lm.Main)
var sectionNames []string
for k := range lm.NamedSections {
sectionNames = append(sectionNames, k)
}
sort.Strings(sectionNames)
var sections []KeyValues
for _, name := range sectionNames {
section := KeyValues{
KeyValue{
Key: "Name",
Value: name,
},
}
section = append(section, keyValuesFromMap(lm.NamedSections[name])...)
sections = append(sections, section)
}
result.Sections = sections
return result
}
func (m JavaManifest) Section(name string) KeyValues {
for _, section := range m.Sections {
if sectionName, ok := section.Get("Name"); ok && sectionName == name {
+101
View File
@@ -3,6 +3,7 @@ package pkg
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@@ -109,3 +110,103 @@ func TestPomProperties_PkgTypeIndicated(t *testing.T) {
})
}
}
func Test_legacyJavaManifest_toNewManifest(t *testing.T) {
tests := []struct {
name string
lm legacyJavaManifest
want JavaManifest
}{
{
name: "empty",
lm: legacyJavaManifest{},
want: JavaManifest{},
},
{
name: "main sections are sorted",
lm: legacyJavaManifest{
Main: map[string]string{
"a key": "a value",
"b key": "b value",
"c key": "c value",
},
},
want: JavaManifest{Main: KeyValues{
{
Key: "a key",
Value: "a value",
},
{
Key: "b key",
Value: "b value",
},
{
Key: "c key",
Value: "c value",
},
}},
},
{
name: "named sections have their name in the result",
lm: legacyJavaManifest{
NamedSections: map[string]map[string]string{
"a section": {
"a key": "a value",
"b key": "b value",
"c key": "c value",
},
"b section": {
"d key": "d value",
"e key": "e value",
"f key": "f value",
},
},
},
want: JavaManifest{Sections: []KeyValues{
{
{
Key: "Name",
Value: "a section",
},
{
Key: "a key",
Value: "a value",
},
{
Key: "b key",
Value: "b value",
},
{
Key: "c key",
Value: "c value",
},
},
{
{
Key: "Name",
Value: "b section",
},
{
Key: "d key",
Value: "d value",
},
{
Key: "e key",
Value: "e value",
},
{
Key: "f key",
Value: "f value",
},
},
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if diff := cmp.Diff(tt.want, tt.lm.toNewManifest()); diff != "" {
t.Errorf("unexpected diff in manifest (-want +got):\n%s", diff)
}
})
}
}
+42
View File
@@ -1,5 +1,11 @@
package pkg
import (
"encoding/json"
"fmt"
"sort"
)
type KeyValue struct {
Key string `json:"key"`
Value string `json:"value"`
@@ -26,3 +32,39 @@ func (k KeyValues) MustGet(key string) string {
return ""
}
func keyValuesFromMap(m map[string]string) KeyValues {
var result KeyValues
var mapKeys []string
for k := range m {
mapKeys = append(mapKeys, k)
}
sort.Strings(mapKeys)
for _, k := range mapKeys {
result = append(result, KeyValue{
Key: k,
Value: m[k],
})
}
return result
}
func (k *KeyValues) UnmarshalJSON(b []byte) error {
var kvs []KeyValue
if err := json.Unmarshal(b, &kvs); err != nil {
var legacyMap map[string]string
if err := json.Unmarshal(b, &legacyMap); err != nil {
return fmt.Errorf("unable to unmarshal KeyValues: %w", err)
}
var keys []string
for k := range legacyMap {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
kvs = append(kvs, KeyValue{Key: k, Value: legacyMap[k]})
}
}
*k = kvs
return nil
}