github.com/zntrio/harp/v2@v2.0.9/pkg/bundle/compare/diff.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package compare 19 20 import ( 21 "fmt" 22 "sort" 23 "strings" 24 25 bundlev1 "github.com/zntrio/harp/v2/api/gen/go/harp/bundle/v1" 26 "github.com/zntrio/harp/v2/pkg/bundle/secret" 27 "github.com/zntrio/harp/v2/pkg/sdk/security" 28 ) 29 30 const ( 31 // Add describes an operation where the traget path object has been added. 32 Add string = "add" 33 // Remove describes that entity has been removed. 34 Remove string = "remove" 35 // Replace describes an operation to replace content of the target path object. 36 Replace string = "replace" 37 ) 38 39 // OpLog represents operation log calculate from bundle differences. 40 type OpLog []DiffItem 41 42 // DiffItem represents bundle comparison operations. 43 type DiffItem struct { 44 Operation string `json:"op"` 45 Type string `json:"type"` 46 Path string `json:"path"` 47 Value string `json:"value,omitempty"` 48 } 49 50 // ----------------------------------------------------------------------------- 51 52 // Diff calculates bundle differences. 53 // 54 //nolint:funlen,gocognit,gocyclo // To refactor 55 func Diff(src, dst *bundlev1.Bundle) ([]DiffItem, error) { 56 // Check arguments 57 if src == nil { 58 return nil, fmt.Errorf("unable to diff with a nil source") 59 } 60 if dst == nil { 61 return nil, fmt.Errorf("unable to diff with a nil destination") 62 } 63 64 diffs := []DiffItem{} 65 66 // Index source packages 67 srcIndex := map[string]*bundlev1.Package{} 68 for _, srcPkg := range src.Packages { 69 if srcPkg == nil || srcPkg.Secrets == nil { 70 continue 71 } 72 73 srcIndex[srcPkg.Name] = srcPkg 74 } 75 76 // Index destination packages 77 dstIndex := map[string]*bundlev1.Package{} 78 for _, dstPkg := range dst.Packages { 79 if dstPkg == nil || dstPkg.Secrets == nil { 80 continue 81 } 82 83 dstIndex[dstPkg.Name] = dstPkg 84 if _, ok := srcIndex[dstPkg.Name]; !ok { 85 // Package has been added 86 diffs = append(diffs, DiffItem{ 87 Operation: Add, 88 Type: "package", 89 Path: dstPkg.Name, 90 }) 91 92 // Add keys 93 for _, s := range dstPkg.Secrets.Data { 94 if s == nil { 95 continue 96 } 97 98 // Unpack secret value 99 var data string 100 if err := secret.Unpack(s.Value, &data); err != nil { 101 return nil, fmt.Errorf("unable to unpack %q - %q secret value: %w", dstPkg.Name, s.Key, err) 102 } 103 104 diffs = append(diffs, DiffItem{ 105 Operation: Add, 106 Type: "secret", 107 Path: fmt.Sprintf("%s#%s", dstPkg.Name, s.Key), 108 Value: data, 109 }) 110 } 111 } 112 } 113 114 // Compute package changes 115 for n, sp := range srcIndex { 116 dp, ok := dstIndex[n] 117 if !ok { 118 // Not exist in destination bundle 119 diffs = append(diffs, DiffItem{ 120 Operation: Remove, 121 Type: "package", 122 Path: sp.Name, 123 }) 124 continue 125 } 126 127 // Index secret data 128 srcSecretIndex := map[string]*bundlev1.KV{} 129 for _, ss := range sp.Secrets.Data { 130 if ss == nil { 131 continue 132 } 133 srcSecretIndex[ss.Key] = ss 134 } 135 dstSecretIndex := map[string]*bundlev1.KV{} 136 for _, ds := range dp.Secrets.Data { 137 if ds == nil { 138 continue 139 } 140 141 dstSecretIndex[ds.Key] = ds 142 oldValue, ok := srcSecretIndex[ds.Key] 143 if !ok { 144 // Secret has been added 145 var data string 146 if err := secret.Unpack(ds.Value, &data); err != nil { 147 return nil, fmt.Errorf("unable to unpack %q - %q secret value: %w", dp.Name, ds.Key, err) 148 } 149 150 diffs = append(diffs, DiffItem{ 151 Operation: Add, 152 Type: "secret", 153 Path: fmt.Sprintf("%s#%s", dp.Name, ds.Key), 154 Value: data, 155 }) 156 continue 157 } 158 159 // Skip if key does not match 160 if !strings.EqualFold(oldValue.Key, ds.Key) { 161 continue 162 } 163 164 // Compare values 165 if !security.SecureCompare(oldValue.Value, ds.Value) { 166 // Secret has been replaced 167 var data string 168 if err := secret.Unpack(ds.Value, &data); err != nil { 169 return nil, fmt.Errorf("unable to unpack %q - %q secret value: %w", dp.Name, ds.Key, err) 170 } 171 172 diffs = append(diffs, DiffItem{ 173 Operation: Replace, 174 Type: "secret", 175 Path: fmt.Sprintf("%s#%s", dp.Name, ds.Key), 176 Value: data, 177 }) 178 } 179 } 180 181 // Clean removed source secrets 182 for k := range srcSecretIndex { 183 if _, ok := dstSecretIndex[k]; !ok { 184 diffs = append(diffs, DiffItem{ 185 Operation: Remove, 186 Type: "secret", 187 Path: fmt.Sprintf("%s#%s", dp.Name, k), 188 }) 189 } 190 } 191 } 192 193 // Sort diff 194 sort.SliceStable(diffs, func(i, j int) bool { 195 var ( 196 x = diffs[i] 197 y = diffs[j] 198 ) 199 200 // Sort by patch descending 201 return x.Path < y.Path 202 }) 203 204 // No error 205 return diffs, nil 206 }