Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
84 changes: 61 additions & 23 deletions controller/comparator.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,56 +82,84 @@ func (ks *KsonnetAppComparator) CompareAppState(

// Retrieve the live versions of the objects
liveObjs, err := kubeutil.GetResourcesWithLabel(clst.RESTConfig(), namespace, common.LabelApplicationName, app.Name)

if err != nil {
return nil, err
}
objByFullName := ks.groupLiveObjects(liveObjs, targetObjs)

liveObjByFullName := ks.groupLiveObjects(liveObjs, targetObjs)

controlledLiveObj := make([]*unstructured.Unstructured, len(targetObjs))

// Move live resources which have corresponding target object to controlledLiveObj
for i, targetObj := range targetObjs {
controlledLiveObj[i] = objByFullName[getResourceFullName(targetObj)]
fullName := getResourceFullName(targetObj)
controlledLiveObj[i] = liveObjByFullName[fullName]
delete(liveObjByFullName, fullName)
}

// Move root level live resources to controlledLiveObj and add nil to targetObjs to indicate that target object is missing
for fullName := range liveObjByFullName {
liveObj := liveObjByFullName[fullName]
if !hasParent(liveObj) {
targetObjs = append(targetObjs, nil)
controlledLiveObj = append(controlledLiveObj, liveObj)
}
}

// Do the actual comparison
diffResults, err := diff.DiffArray(targetObjs, controlledLiveObj)
if err != nil {
return nil, err
}
comparisonStatus := v1alpha1.ComparisonStatusSynced

resources := make([]v1alpha1.ResourceState, len(targetObjs))
for i := 0; i < len(targetObjs); i++ {
resState := v1alpha1.ResourceState{
ChildLiveResources: make([]v1alpha1.ResourceNode, 0),
}
targetObjBytes, err := json.Marshal(targetObjs[i].Object)
if err != nil {
return nil, err
diffResult := diffResults.Diffs[i]
if diffResult.Modified {
// Set resource state to 'OutOfSync' since target and corresponding live resource are different
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
resState.Status = v1alpha1.ComparisonStatusSynced
}

if targetObjs[i] == nil {
resState.TargetState = "null"
// Set resource state to 'OutOfSync' since target resource is missing and live resource is unexpected
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
targetObjBytes, err := json.Marshal(targetObjs[i].Object)
if err != nil {
return nil, err
}
resState.TargetState = string(targetObjBytes)
}
resState.TargetState = string(targetObjBytes)

if controlledLiveObj[i] == nil {
resState.LiveState = "null"
// Set resource state to 'OutOfSync' since target resource present but corresponding live resource is missing
resState.Status = v1alpha1.ComparisonStatusOutOfSync
comparisonStatus = v1alpha1.ComparisonStatusOutOfSync
} else {
liveObjBytes, err := json.Marshal(controlledLiveObj[i].Object)
if err != nil {
return nil, err
}
resState.LiveState = string(liveObjBytes)
}
diffResult := diffResults.Diffs[i]
if diffResult.Modified {
resState.Status = v1alpha1.ComparisonStatusOutOfSync
} else {
resState.Status = v1alpha1.ComparisonStatusSynced
}

resources[i] = resState
}

for i, resource := range resources {
liveResource := controlledLiveObj[i]
if liveResource != nil {
childResources, err := getChildren(liveResource, objByFullName)
childResources, err := getChildren(liveResource, liveObjByFullName)
if err != nil {
return nil, err
}
Expand All @@ -145,26 +173,36 @@ func (ks *KsonnetAppComparator) CompareAppState(
Server: clst.Server,
Namespace: namespace,
Resources: resources,
}
if diffResults.Modified {
compResult.Status = v1alpha1.ComparisonStatusOutOfSync
} else {
compResult.Status = v1alpha1.ComparisonStatusSynced
Status: comparisonStatus,
}
return &compResult, nil
}

func getChildren(parent *unstructured.Unstructured, objByFullName map[string]*unstructured.Unstructured) ([]v1alpha1.ResourceNode, error) {
func hasParent(obj *unstructured.Unstructured) bool {
// TODO: remove special case after Service and Endpoint get explicit relationship ( https://github.com/kubernetes/kubernetes/issues/28483 )
return obj.GetKind() == kubeutil.EndpointsKind || metav1.GetControllerOf(obj) != nil
}

func isControlledBy(obj *unstructured.Unstructured, parent *unstructured.Unstructured) bool {
// TODO: remove special case after Service and Endpoint get explicit relationship ( https://github.com/kubernetes/kubernetes/issues/28483 )
if obj.GetKind() == kubeutil.EndpointsKind && parent.GetKind() == kubeutil.ServiceKind {
return obj.GetName() == parent.GetName()
Copy link
Member

Choose a reason for hiding this comment

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

Nice!

}
return metav1.IsControlledBy(obj, parent)
}

func getChildren(parent *unstructured.Unstructured, liveObjByFullName map[string]*unstructured.Unstructured) ([]v1alpha1.ResourceNode, error) {
children := make([]v1alpha1.ResourceNode, 0)
for _, obj := range objByFullName {
if metav1.IsControlledBy(obj, parent) {
for fullName, obj := range liveObjByFullName {
if isControlledBy(obj, parent) {
delete(liveObjByFullName, fullName)
childResource := v1alpha1.ResourceNode{}
json, err := json.Marshal(obj)
if err != nil {
return nil, err
}
childResource.State = string(json)
childResourceChildren, err := getChildren(obj, objByFullName)
childResourceChildren, err := getChildren(obj, liveObjByFullName)
if err != nil {
return nil, err
}
Expand Down
5 changes: 5 additions & 0 deletions util/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const (
deleteCollectionVerb = "deletecollection"
)

const (
ServiceKind = "Service"
EndpointsKind = "Endpoints"
)

var (
// location to use for generating temporary files, such as the ca.crt needed by kubectl
kubectlTempDir string
Expand Down