Skip to content

Commit

Permalink
Implement Diff (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
ohkinozomu authored Jul 18, 2024
1 parent 6bad13f commit 964586d
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 3 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ You first need to create a WorkUtilsClient. It requires passing `*runtime.Scheme

```go
import(
"github.com/ohkinozomu/workutils"
"k8s.io/client-go/kubernetes/scheme"
"github.com/ohkinozomu/workutils"
"k8s.io/client-go/kubernetes/scheme"
)

client := workutils.NewWorkUtilsClient(scheme.Scheme)
client := workutils.NewWorkUtilsClient(scheme.Scheme)
```

If resources not included in `scheme.Scheme` are present in ManifestWork, `AddToScheme` for those resources is necessary.
Expand Down Expand Up @@ -46,4 +46,6 @@ func (client *WorkUtilsClient) Add(work workapiv1.ManifestWork, obj runtime.Obje
func (client *WorkUtilsClient) Update(work workapiv1.ManifestWork, obj runtime.Object) (workapiv1.ManifestWork, error)

func (client *WorkUtilsClient) Remove(work workapiv1.ManifestWork, resource Resource) (workapiv1.ManifestWork, error)

func (client *WorkUtilsClient) Diff(old workapiv1.ManifestWork, new workapiv1.ManifestWork) (added []runtime.Object, removed []runtime.Object, updated []runtime.Object, err error)
```
25 changes: 25 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,31 @@ func getNamespaceFromObject(obj runtime.Object) (string, error) {
return accessor.GetNamespace(), nil
}

func getResourceFromObject(obj runtime.Object) (Resource, error) {
group, version, kind, err := getGVKFromObject(obj)
if err != nil {
return Resource{}, err
}

name, err := getNameFromObject(obj)
if err != nil {
return Resource{}, err
}

namespace, err := getNamespaceFromObject(obj)
if err != nil {
return Resource{}, err
}

return Resource{
Group: group,
Version: version,
Kind: kind,
Name: name,
Namespace: namespace,
}, nil
}

func stringToRawExtension(manifest string) (runtime.RawExtension, error) {
obj, _, err := decode([]byte(manifest), scheme.Scheme)
if err != nil {
Expand Down
79 changes: 79 additions & 0 deletions diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package workutils

import (
"bytes"
"fmt"

"k8s.io/apimachinery/pkg/runtime"
workapiv1 "open-cluster-management.io/api/work/v1"
)

func (client *WorkUtilsClient) Diff(old workapiv1.ManifestWork, new workapiv1.ManifestWork) (added []runtime.Object, removed []runtime.Object, updated []runtime.Object, err error) {
oldMap := make(map[string]runtime.Object)
for _, manifest := range old.Spec.Workload.Manifests {
obj, _, err := decode(manifest.Raw, client.Scheme)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to decode manifest in old: %v", err)
}
key, err := manifestKey(obj)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to get manifest key: %v", err)
}
oldMap[key] = obj
}

newMap := make(map[string]runtime.Object)
for _, manifest := range new.Spec.Workload.Manifests {
obj, _, err := decode(manifest.Raw, client.Scheme)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to decode manifest in new: %v", err)
}
key, err := manifestKey(obj)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to get manifest key: %v", err)
}
newMap[key] = obj
}

for key, obj1 := range oldMap {
if obj2, found := newMap[key]; found {
e, err := equal(client.Scheme, obj1, obj2)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to compare objects: %v", err)
}
if !e {
updated = append(updated, obj2)
}
delete(newMap, key)
} else {
removed = append(removed, obj1)
}
}

for _, obj := range newMap {
added = append(added, obj)
}

return added, removed, updated, nil
}

func manifestKey(obj runtime.Object) (string, error) {
r, err := getResourceFromObject(obj)
if err != nil {
return "", err
}
key := fmt.Sprintf("%s/%s/%s/%s/%s", r.Group, r.Version, r.Kind, r.Namespace, r.Name)
return key, nil
}

func equal(scheme *runtime.Scheme, obj1, obj2 runtime.Object) (bool, error) {
raw1, err := objToRawExtension(obj1, scheme)
if err != nil {
return false, err
}
raw2, err := objToRawExtension(obj2, scheme)
if err != nil {
return false, err
}
return bytes.Equal(raw1.Raw, raw2.Raw), nil
}
106 changes: 106 additions & 0 deletions diff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package workutils

import (
"testing"

"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/scheme"
workapiv1 "open-cluster-management.io/api/work/v1"
)

func TestDiff(t *testing.T) {
client := &WorkUtilsClient{
Scheme: scheme.Scheme,
}

manifest1 := `
apiVersion: v1
kind: ConfigMap
metadata:
name: config1
namespace: default
data:
key1: value1
`

manifest2 := `
apiVersion: v1
kind: ConfigMap
metadata:
name: config2
namespace: default
data:
key2: value2
`

manifest3 := `
apiVersion: v1
kind: ConfigMap
metadata:
name: config1
namespace: default
data:
key1: updatedValue
`

manifest4 := `
apiVersion: v1
kind: ConfigMap
metadata:
name: config3
namespace: default
data:
key3: value3
`

raw1, err := stringToRawExtension(manifest1)
require.NoError(t, err)

raw2, err := stringToRawExtension(manifest2)
require.NoError(t, err)

raw3, err := stringToRawExtension(manifest3)
require.NoError(t, err)

raw4, err := stringToRawExtension(manifest4)
require.NoError(t, err)

work1 := workapiv1.ManifestWork{
Spec: workapiv1.ManifestWorkSpec{
Workload: workapiv1.ManifestsTemplate{
Manifests: []workapiv1.Manifest{{
RawExtension: raw1,
}, {
RawExtension: raw4,
}},
},
},
}
work2 := workapiv1.ManifestWork{
Spec: workapiv1.ManifestWorkSpec{
Workload: workapiv1.ManifestsTemplate{
Manifests: []workapiv1.Manifest{{
RawExtension: raw2,
}, {
RawExtension: raw3,
}},
},
},
}

added, removed, updated, err := client.Diff(work1, work2)
require.NoError(t, err)

o2, _, err := decode([]byte(manifest2), scheme.Scheme)
require.NoError(t, err)
require.Equal(t, []runtime.Object{o2}, added)

o4, _, err := decode([]byte(manifest4), scheme.Scheme)
require.NoError(t, err)
require.Equal(t, []runtime.Object{o4}, removed)

o3, _, err := decode([]byte(manifest3), scheme.Scheme)
require.NoError(t, err)
require.Equal(t, []runtime.Object{o3}, updated)
}

0 comments on commit 964586d

Please sign in to comment.