-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding support to get Yaml for k8s object (#45)
* Adding support to get Yaml for k8s object * Added logic to compute yaml diff, refactored hashing helper * Nit changes for review * Updating link for unified context
- Loading branch information
1 parent
41d174e
commit a689c52
Showing
6 changed files
with
267 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package hashing | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
) | ||
|
||
// function to get the relevant information out of k8s object that is needed to compute hash | ||
func GetFilteredK8sObjectForHashing(object interface{}) (map[string]interface{}, error) { | ||
// By marshaling/unmarshaling the object via JSON we're copying it into a | ||
// map, which is easier to manipulate in generic way than structs. | ||
if b, err := json.Marshal(object); err != nil { | ||
return nil, fmt.Errorf("error while encoding %+v into JSON: %s", object, err) | ||
} else { | ||
var v map[string]interface{} | ||
if err := json.Unmarshal(b, &v); err != nil { | ||
return nil, fmt.Errorf("error while decoding JSON %s into an object: %s", v, err) | ||
} else { | ||
// We need only kind/version/spec and labels excluding the label with the | ||
// current hash value while calculating the hash. Also Kubernetes adds | ||
// its own info into `metadata` which we need to ignore. | ||
meta := v["metadata"].(map[string]interface{}) | ||
labels := meta["labels"] | ||
if labels != nil { | ||
delete(labels.(map[string]interface{}), "yelp.com/operator_config_hash") | ||
} else { | ||
labels = make(map[string]interface{}) | ||
} | ||
m := map[string]interface{}{ | ||
"kind": v["kind"], | ||
"apiVersion": v["apiVersion"], | ||
"spec": v["spec"], | ||
"metadata": map[string]interface{}{ | ||
"name": meta["name"], | ||
"namespace": meta["namespace"], | ||
"labels": labels, | ||
}, | ||
} | ||
return m, nil | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package hashing | ||
|
||
import ( | ||
"encoding/json" | ||
assert "github.com/stretchr/testify/assert" | ||
appsv1 "k8s.io/api/apps/v1" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"testing" | ||
) | ||
|
||
func TestGetHashObjectOfKubernetes(t *testing.T) { | ||
labels := map[string]string{ | ||
"yelp.com/rick": "andmortyadventures", | ||
"yelp.com/operator_config_hash": "somerandomhash", | ||
} | ||
labelsWithoutHash := map[string]string{ | ||
"yelp.com/rick": "andmortyadventures", | ||
} | ||
replicas := int32(2) | ||
someStatefulSet := &appsv1.StatefulSet{ | ||
TypeMeta: metav1.TypeMeta{ | ||
APIVersion: "apps/v1", | ||
Kind: "StatefulSet", | ||
}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "morty-test-cluster", | ||
Namespace: "paasta-cassandra", | ||
Labels: labels, | ||
}, | ||
Spec: appsv1.StatefulSetSpec{ | ||
Replicas: &replicas, | ||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{corev1.PersistentVolumeClaim{}}, | ||
Template: corev1.PodTemplateSpec{ | ||
Spec: corev1.PodSpec{ | ||
Volumes: []corev1.Volume{}, | ||
Containers: []corev1.Container{}, | ||
}, | ||
}, | ||
}, | ||
} | ||
expectedHashObject := map[string]interface{}{ | ||
"kind": someStatefulSet.TypeMeta.Kind, | ||
"apiVersion": someStatefulSet.TypeMeta.APIVersion, | ||
"spec": someStatefulSet.Spec, | ||
"metadata": map[string]interface{}{ | ||
"name": someStatefulSet.ObjectMeta.Name, | ||
"namespace": someStatefulSet.ObjectMeta.Namespace, | ||
"labels": labelsWithoutHash, | ||
}, | ||
} | ||
expectedOutString, err := json.Marshal(expectedHashObject) | ||
_ = json.Unmarshal(expectedOutString, &expectedHashObject) | ||
hashObject, err := GetFilteredK8sObjectForHashing(someStatefulSet) | ||
if err != nil { | ||
t.Errorf("Failed to calculate hash object") | ||
} | ||
assert.Equal(t, expectedHashObject, hashObject) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package utils | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/pmezard/go-difflib/difflib" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
// function to get the yaml output of an object | ||
// marshal/unmarshal to and from json in order to maintain consistency between json and yaml | ||
// currently k8s objects have json field tags, so this helps in skipping empty fields | ||
// and give consistent output yaml similar to json | ||
func getYamlOfObject(object interface{}) (string, error) { | ||
if b, err := json.Marshal(object); err != nil { | ||
return "", fmt.Errorf("error while encoding %+v into JSON: %s", object, err) | ||
} else { | ||
var v map[string]interface{} | ||
if err := json.Unmarshal(b, &v); err != nil { | ||
return "", fmt.Errorf("error while decoding JSON %s into an object: %s", v, err) | ||
} else { | ||
if y, err := yaml.Marshal(&v); err != nil { | ||
return "", fmt.Errorf("error while encoding Map %s into YAML: %s", v, err) | ||
} else { | ||
return string(y), nil | ||
} | ||
} | ||
} | ||
} | ||
|
||
func generateYamlDiff(yaml1 string, yaml2 string, context int) string { | ||
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ | ||
A: difflib.SplitLines(yaml1), | ||
B: difflib.SplitLines(yaml2), | ||
FromFile: "Old", | ||
FromDate: "", | ||
ToFile: "New", | ||
ToDate: "", | ||
Context: context, | ||
}) | ||
return diff | ||
} | ||
|
||
// func to generate yaml diff of objects used for hashing | ||
// context : number of context lines to use for generating diff | ||
// for more reference on context : https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html#Unified-Format | ||
func GetYamlDiffForObjects(objectOld interface{}, objectNew interface{}, context int) (string, error) { | ||
yamlOld, err := getYamlOfObject(objectOld) | ||
if err != nil { | ||
return "", err | ||
} | ||
yamlNew, err := getYamlOfObject(objectNew) | ||
if err != nil { | ||
return "", err | ||
} | ||
return generateYamlDiff(yamlOld, yamlNew, context), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package utils | ||
|
||
import ( | ||
assert "github.com/stretchr/testify/assert" | ||
appsv1 "k8s.io/api/apps/v1" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"testing" | ||
) | ||
|
||
func TestGetYamlDiffForObjects(t *testing.T) { | ||
labels1 := map[string]string{ | ||
"yelp.com/rick": "andmortyadventures1", | ||
"yelp.com/operator_config_hash": "somerandomhash1", | ||
} | ||
replicas1 := int32(2) | ||
someStatefulSet1 := &appsv1.StatefulSet{ | ||
TypeMeta: metav1.TypeMeta{ | ||
APIVersion: "apps/v1", | ||
Kind: "StatefulSet", | ||
}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "morty-test-cluster1", | ||
Namespace: "paasta-cassandra", | ||
Labels: labels1, | ||
}, | ||
Spec: appsv1.StatefulSetSpec{ | ||
Replicas: &replicas1, | ||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{corev1.PersistentVolumeClaim{}}, | ||
Template: corev1.PodTemplateSpec{ | ||
Spec: corev1.PodSpec{ | ||
Volumes: []corev1.Volume{}, | ||
Containers: []corev1.Container{}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
labels2 := map[string]string{ | ||
"yelp.com/rick": "andmortyadventures2", | ||
"yelp.com/operator_config_hash": "somerandomhash2", | ||
} | ||
replicas2 := int32(2) | ||
someStatefulSet2 := &appsv1.StatefulSet{ | ||
TypeMeta: metav1.TypeMeta{ | ||
APIVersion: "apps/v1", | ||
Kind: "StatefulSet", | ||
}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "morty-test-cluster2", | ||
Namespace: "paasta-cassandra", | ||
Labels: labels2, | ||
}, | ||
Spec: appsv1.StatefulSetSpec{ | ||
Replicas: &replicas2, | ||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{corev1.PersistentVolumeClaim{}}, | ||
Template: corev1.PodTemplateSpec{ | ||
Spec: corev1.PodSpec{ | ||
Volumes: []corev1.Volume{ | ||
{ | ||
Name: "volume1", | ||
VolumeSource: corev1.VolumeSource{}, | ||
}, | ||
}, | ||
Containers: []corev1.Container{}, | ||
}, | ||
}, | ||
}, | ||
} | ||
expectedDiff := `--- Old | ||
+++ New | ||
@@ -3,9 +3,9 @@ | ||
metadata: | ||
creationTimestamp: null | ||
labels: | ||
- yelp.com/operator_config_hash: somerandomhash1 | ||
- yelp.com/rick: andmortyadventures1 | ||
- name: morty-test-cluster1 | ||
+ yelp.com/operator_config_hash: somerandomhash2 | ||
+ yelp.com/rick: andmortyadventures2 | ||
+ name: morty-test-cluster2 | ||
namespace: paasta-cassandra | ||
spec: | ||
replicas: 2 | ||
@@ -16,6 +16,8 @@ | ||
creationTimestamp: null | ||
spec: | ||
containers: [] | ||
+ volumes: | ||
+ - name: volume1 | ||
updateStrategy: {} | ||
volumeClaimTemplates: | ||
- metadata: | ||
` | ||
actualDiff, _ := GetYamlDiffForObjects(someStatefulSet1, someStatefulSet2, 3) | ||
assert.Equal(t, expectedDiff, actualDiff) | ||
} |