Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: sort endpoint IP addresses before diffing #160

Merged
merged 4 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw=
github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b h1:vCplRbYcTTeBVLjIU0KvipEeVBSxl6sakUBRmeLBTkw=
github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc=
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
Expand Down
48 changes: 46 additions & 2 deletions pkg/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,17 @@ import (
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes/scheme"

"k8s.io/kubernetes/pkg/api/endpoints"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: can you please group third party dependencies together?


"k8s.io/kubernetes/pkg/apis/core"
v1 "k8s.io/kubernetes/pkg/apis/core/v1"

jsonutil "github.com/argoproj/gitops-engine/pkg/utils/json"
kubescheme "github.com/argoproj/gitops-engine/pkg/utils/kube/scheme"
)

const couldNotMarshalErrMsg = "Could not unmarshal to object of type %s: %v"

// Holds diffing settings
type DiffOptions struct {
// If set to true then differences caused by aggregated roles in RBAC resources are ignored.
Expand Down Expand Up @@ -370,6 +377,8 @@ func Normalize(un *unstructured.Unstructured, normalizer Normalizer, options Dif
NormalizeSecret(un)
} else if gvk.Group == "rbac.authorization.k8s.io" && (gvk.Kind == "ClusterRole" || gvk.Kind == "Role") {
normalizeRole(un, options)
} else if gvk.Group == "" && gvk.Kind == "Endpoints" {
normalizeEndpoint(un)
}

if normalizer != nil {
Expand Down Expand Up @@ -424,6 +433,41 @@ func NormalizeSecret(un *unstructured.Unstructured) {
}
}

// normalizeEndpoint normalizes endpoint meaning that EndpointSubsets are sorted lexicographically
func normalizeEndpoint(un *unstructured.Unstructured) {
if un == nil {
return
}
gvk := un.GroupVersionKind()
if gvk.Group != "" || gvk.Kind != "Endpoints" {
return
}
var ep corev1.Endpoints
err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &ep)
if err != nil {
return
}
var coreEp core.Endpoints
err = v1.Convert_v1_Endpoints_To_core_Endpoints(&ep, &coreEp, nil)
if err != nil {
log.Warnf("Could not convert from v1 to core endpoint type %s: %v", gvk, err)
return
}

endpoints.SortSubsets(coreEp.Subsets)

err = v1.Convert_core_Endpoints_To_v1_Endpoints(&coreEp, &ep, nil)
if err != nil {
log.Warnf("Could not convert from core to vi endpoint type %s: %v", gvk, err)
return
}
un.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&ep)
if err != nil {
log.Warnf(couldNotMarshalErrMsg, gvk, err)
return
}
}

// normalizeRole mutates the supplied Role/ClusterRole and sets rules to null if it is an empty list or an aggregated role
func normalizeRole(un *unstructured.Unstructured, options DiffOptions) {
if un == nil {
Expand Down Expand Up @@ -583,12 +627,12 @@ func remarshal(obj *unstructured.Unstructured) *unstructured.Unstructured {
err = json.Unmarshal(data, &unmarshalledObj)
if err != nil {
// User may have specified an invalid spec in git. Return original object
log.Debugf("Could not unmarshal to object of type %s: %v", gvk, err)
log.Debugf(couldNotMarshalErrMsg, gvk, err)
return obj
}
unstrBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(unmarshalledObj)
if err != nil {
log.Warnf("Could not unmarshal to object of type %s: %v", gvk, err)
log.Warnf(couldNotMarshalErrMsg, gvk, err)
return obj
}
// remove all default values specified by custom formatter (e.g. creationTimestamp)
Expand Down
11 changes: 11 additions & 0 deletions pkg/diff/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,17 @@ func TestNullCreationTimestamp(t *testing.T) {
}
}

func TestUnsortedEndpoints(t *testing.T) {
configUn := unmarshalFile("testdata/endpoints-config.json")
liveUn := unmarshalFile("testdata/endpoints-live.json")
dr := diff(t, configUn, liveUn, GetDefaultDiffOptions())
if !assert.False(t, dr.Modified) {
ascii, err := printDiff(dr)
assert.Nil(t, err)
t.Log(ascii)
}
}

func createSecret(data map[string]string) *unstructured.Unstructured {
secret := corev1.Secret{TypeMeta: metav1.TypeMeta{Kind: "Secret"}}
if data != nil {
Expand Down
42 changes: 42 additions & 0 deletions pkg/diff/testdata/endpoints-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"apiVersion": "v1",
"kind": "Endpoints",
"metadata": {
"annotations": {
"description": "A workaround to support a set of backend IPs for solr",
"linkerd.io/inject": "disabled"
},
"labels": {
"app.kubernetes.io/instance": "guestbook"
},
"name": "solrcloud",
"namespace": "default"
},
"subsets": [
{
"addresses": [
{
"ip": "172.20.10.97"
},
{
"ip": "172.20.10.98"
},
{
"ip": "172.20.10.99"
},
{
"ip": "172.20.10.100"
},
{
"ip": "172.20.10.101"
}
],
"ports": [
{
"name": "solr-http",
"port": 8080
}
]
}
]
}
72 changes: 72 additions & 0 deletions pkg/diff/testdata/endpoints-live.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"apiVersion": "v1",
"kind": "Endpoints",
"metadata": {
"annotations": {
"description": "A workaround to support a set of backend IPs for solr",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Endpoints\",\"metadata\":{\"annotations\":{\"description\":\"A workaround to support a set of backend IPs for solr\",\"linkerd.io/inject\":\"disabled\"},\"labels\":{\"app.kubernetes.io/instance\":\"guestbook\"},\"name\":\"solrcloud\",\"namespace\":\"default\"},\"subsets\":[{\"addresses\":[{\"ip\":\"172.20.10.97\"},{\"ip\":\"172.20.10.98\"},{\"ip\":\"172.20.10.99\"},{\"ip\":\"172.20.10.100\"},{\"ip\":\"172.20.10.101\"}],\"ports\":[{\"name\":\"solr-http\",\"port\":8080}]}]}\n",
"linkerd.io/inject": "disabled"
},
"creationTimestamp": null,
"labels": {
"app.kubernetes.io/instance": "guestbook"
},
"managedFields": [
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:description": {},
"f:kubectl.kubernetes.io/last-applied-configuration": {},
"f:linkerd.io/inject": {}
},
"f:labels": {
".": {},
"f:app.kubernetes.io/instance": {}
}
},
"f:subsets": {}
},
"manager": "main",
"operation": "Update",
"time": "2020-10-09T17:26:49Z"
}
],
"name": "solrcloud",
"namespace": "default",
"resourceVersion": "139834",
"selfLink": "/api/v1/namespaces/default/endpoints/solrcloud",
"uid": "f11285f4-987b-4194-bda8-6372b3f3f08f"
},
"subsets": [
{
"addresses": [
{
"ip": "172.20.10.100"
},
{
"ip": "172.20.10.101"
},
{
"ip": "172.20.10.97"
},
{
"ip": "172.20.10.98"
},
{
"ip": "172.20.10.99"
}
],
"ports": [
{
"name": "solr-http",
"port": 8080,
"protocol": "TCP"
}
]
}
]
}