pkg/bundle/compare/diff.go (137 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package compare
import (
"fmt"
"sort"
"strings"
bundlev1 "github.com/elastic/harp/api/gen/go/harp/bundle/v1"
"github.com/elastic/harp/pkg/bundle/secret"
"github.com/elastic/harp/pkg/sdk/security"
)
const (
// Add describes an operation where the traget path object has been added.
Add string = "add"
// Remove describes that entity has been removed.
Remove string = "remove"
// Replace describes an operation to replace content of the target path object.
Replace string = "replace"
)
// OpLog represents operation log calculate from bundle differences.
type OpLog []DiffItem
// DiffItem represents bundle comparison operations.
type DiffItem struct {
Operation string `json:"op"`
Type string `json:"type"`
Path string `json:"path"`
Value string `json:"value,omitempty"`
}
// -----------------------------------------------------------------------------
// Diff calculates bundle differences.
//
//nolint:funlen,gocognit,gocyclo // To refactor
func Diff(src, dst *bundlev1.Bundle) ([]DiffItem, error) {
// Check arguments
if src == nil {
return nil, fmt.Errorf("unable to diff with a nil source")
}
if dst == nil {
return nil, fmt.Errorf("unable to diff with a nil destination")
}
diffs := []DiffItem{}
// Index source packages
srcIndex := map[string]*bundlev1.Package{}
for _, srcPkg := range src.Packages {
if srcPkg == nil || srcPkg.Secrets == nil {
continue
}
srcIndex[srcPkg.Name] = srcPkg
}
// Index destination packages
dstIndex := map[string]*bundlev1.Package{}
for _, dstPkg := range dst.Packages {
if dstPkg == nil || dstPkg.Secrets == nil {
continue
}
dstIndex[dstPkg.Name] = dstPkg
if _, ok := srcIndex[dstPkg.Name]; !ok {
// Package has been added
diffs = append(diffs, DiffItem{
Operation: Add,
Type: "package",
Path: dstPkg.Name,
})
// Add keys
for _, s := range dstPkg.Secrets.Data {
if s == nil {
continue
}
// Unpack secret value
var data string
if err := secret.Unpack(s.Value, &data); err != nil {
return nil, fmt.Errorf("unable to unpack '%s' - '%s' secret value: %w", dstPkg.Name, s.Key, err)
}
diffs = append(diffs, DiffItem{
Operation: Add,
Type: "secret",
Path: fmt.Sprintf("%s#%s", dstPkg.Name, s.Key),
Value: data,
})
}
}
}
// Compute package changes
for n, sp := range srcIndex {
dp, ok := dstIndex[n]
if !ok {
// Not exist in destination bundle
diffs = append(diffs, DiffItem{
Operation: Remove,
Type: "package",
Path: sp.Name,
})
continue
}
// Index secret data
srcSecretIndex := map[string]*bundlev1.KV{}
for _, ss := range sp.Secrets.Data {
if ss == nil {
continue
}
srcSecretIndex[ss.Key] = ss
}
dstSecretIndex := map[string]*bundlev1.KV{}
for _, ds := range dp.Secrets.Data {
if ds == nil {
continue
}
dstSecretIndex[ds.Key] = ds
oldValue, ok := srcSecretIndex[ds.Key]
if !ok {
// Secret has been added
var data string
if err := secret.Unpack(ds.Value, &data); err != nil {
return nil, fmt.Errorf("unable to unpack '%s' - '%s' secret value: %w", dp.Name, ds.Key, err)
}
diffs = append(diffs, DiffItem{
Operation: Add,
Type: "secret",
Path: fmt.Sprintf("%s#%s", dp.Name, ds.Key),
Value: data,
})
continue
}
// Skip if key does not match
if !strings.EqualFold(oldValue.Key, ds.Key) {
continue
}
// Compare values
if !security.SecureCompare(oldValue.Value, ds.Value) {
// Secret has been replaced
var data string
if err := secret.Unpack(ds.Value, &data); err != nil {
return nil, fmt.Errorf("unable to unpack '%s' - '%s' secret value: %w", dp.Name, ds.Key, err)
}
diffs = append(diffs, DiffItem{
Operation: Replace,
Type: "secret",
Path: fmt.Sprintf("%s#%s", dp.Name, ds.Key),
Value: data,
})
}
}
// Clean removed source secrets
for k := range srcSecretIndex {
if _, ok := dstSecretIndex[k]; !ok {
diffs = append(diffs, DiffItem{
Operation: Remove,
Type: "secret",
Path: fmt.Sprintf("%s#%s", dp.Name, k),
})
}
}
}
// Sort diff
sort.SliceStable(diffs, func(i, j int) bool {
var (
x = diffs[i]
y = diffs[j]
)
// Sort by patch descending
return x.Path < y.Path
})
// No error
return diffs, nil
}