From 3ec0ce959435691df41db380274a8442927ecf66 Mon Sep 17 00:00:00 2001 From: Tadayoshi Sato Date: Sun, 3 Jul 2022 23:04:28 +0900 Subject: [PATCH] fix(digest): fix hash computation for the new Traits schema --- pkg/util/digest/digest.go | 82 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/pkg/util/digest/digest.go b/pkg/util/digest/digest.go index cdeee2834d..1ddda2059f 100644 --- a/pkg/util/digest/digest.go +++ b/pkg/util/digest/digest.go @@ -25,6 +25,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "hash" "io" "path" "sort" @@ -98,12 +99,29 @@ func ComputeForIntegration(integration *v1.Integration) (string, error) { } // Integration traits - traits, err := json.Marshal(integration.Spec.Traits) + // Calculation logic prior to 1.10.0 (the new Traits API schema) is maintained + // in order to keep consistency in the digest calculated from the same set of + // Trait configurations for backward compatibility. + traitsMap, err := toMap(integration.Spec.Traits) if err != nil { return "", err } - if _, err := hash.Write(traits); err != nil { - return "", err + for _, name := range sortedTraitsMapKeys(traitsMap) { + if name != "addons" { + if err := computeForTrait(hash, name, traitsMap[name]); err != nil { + return "", err + } + } else { + // Addons + addons := traitsMap["addons"] + for _, name := range util.SortedMapKeys(addons) { + if addon, ok := addons[name].(map[string]interface{}); ok { + if err := computeForTrait(hash, name, addon); err != nil { + return "", err + } + } + } + } } // Integration traits as annotations for _, k := range sortedTraitAnnotationsKeys(integration) { @@ -118,6 +136,53 @@ func ComputeForIntegration(integration *v1.Integration) (string, error) { return digest, nil } +func computeForTrait(hash hash.Hash, name string, trait map[string]interface{}) error { + if _, err := hash.Write([]byte(name + "[")); err != nil { + return err + } + // hash legacy configuration first + if trait["configuration"] != nil { + if config, ok := trait["configuration"].(map[string]interface{}); ok { + if err := computeForTraitProps(hash, config); err != nil { + return err + } + } + delete(trait, "configuration") + } + if err := computeForTraitProps(hash, trait); err != nil { + return err + } + if _, err := hash.Write([]byte("]")); err != nil { + return err + } + + return nil +} + +func computeForTraitProps(hash hash.Hash, props map[string]interface{}) error { + for _, prop := range util.SortedMapKeys(props) { + val := props[prop] + if _, err := hash.Write([]byte(fmt.Sprintf("%s=%v,", prop, val))); err != nil { + return err + } + } + + return nil +} + +func toMap(traits v1.Traits) (map[string]map[string]interface{}, error) { + data, err := json.Marshal(traits) + if err != nil { + return nil, err + } + traitsMap := make(map[string]map[string]interface{}) + if err = json.Unmarshal(data, &traitsMap); err != nil { + return nil, err + } + + return traitsMap, nil +} + // ComputeForIntegrationKit a digest of the fields that are relevant for the deployment // Produces a digest that can be used as docker image tag. func ComputeForIntegrationKit(kit *v1.IntegrationKit) (string, error) { @@ -216,6 +281,17 @@ func ComputeForSource(s v1.SourceSpec) (string, error) { return digest, nil } +func sortedTraitsMapKeys(m map[string]map[string]interface{}) []string { + res := make([]string, len(m)) + i := 0 + for k := range m { + res[i] = k + i++ + } + sort.Strings(res) + return res +} + func sortedTraitAnnotationsKeys(it *v1.Integration) []string { res := make([]string, 0, len(it.Annotations)) for k := range it.Annotations {