Skip to content

Commit

Permalink
Add ignore conditions
Browse files Browse the repository at this point in the history
We can't control status in Custom Resources that don't belong to Fleet or Rancher. This is causing Fleet to consider bundles in error state when they shouldn't.

Conditions inside ignore conditions field will be ignored when checking the Bundle state

Signed-off-by: raul <raul.cabello@suse.com>
  • Loading branch information
raulcabello committed Apr 20, 2023
1 parent fb09a95 commit 3b62dd4
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 3 deletions.
48 changes: 48 additions & 0 deletions charts/fleet-crd/templates/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,18 @@ spec:
waitForJobs:
type: boolean
type: object
ignore:
properties:
conditions:
items:
additionalProperties:
nullable: true
type: string
nullable: true
type: object
nullable: true
type: array
type: object
keepResources:
type: boolean
kustomize:
Expand Down Expand Up @@ -558,6 +570,18 @@ spec:
waitForJobs:
type: boolean
type: object
ignore:
properties:
conditions:
items:
additionalProperties:
nullable: true
type: string
nullable: true
type: object
nullable: true
type: array
type: object
keepResources:
type: boolean
kustomize:
Expand Down Expand Up @@ -1075,6 +1099,18 @@ spec:
waitForJobs:
type: boolean
type: object
ignore:
properties:
conditions:
items:
additionalProperties:
nullable: true
type: string
nullable: true
type: object
nullable: true
type: array
type: object
keepResources:
type: boolean
kustomize:
Expand Down Expand Up @@ -1226,6 +1262,18 @@ spec:
waitForJobs:
type: boolean
type: object
ignore:
properties:
conditions:
items:
additionalProperties:
nullable: true
type: string
nullable: true
type: object
nullable: true
type: array
type: object
keepResources:
type: boolean
kustomize:
Expand Down
61 changes: 58 additions & 3 deletions modules/agent/pkg/deployer/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package deployer

import (
"encoding/json"
"fmt"
"sort"

jsonpatch "github.com/evanphx/json-patch"
Expand All @@ -16,6 +17,7 @@ import (
"github.com/rancher/wrangler/pkg/merr"
"github.com/rancher/wrangler/pkg/objectset"
"github.com/rancher/wrangler/pkg/summary"
"github.com/sirupsen/logrus"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -166,7 +168,7 @@ func (m *Manager) MonitorBundle(bd *fleet.BundleDeployment) (DeploymentStatus, e
return status, err
}

status.NonReadyStatus = nonReady(plan)
status.NonReadyStatus = nonReady(plan, bd.Spec.Options.IgnoreOptions)
status.ModifiedStatus = modified(plan, resourcesPreviuosRelease)
status.Ready = false
status.NonModified = false
Expand Down Expand Up @@ -262,7 +264,7 @@ func isResourceInPreviousRelease(key objectset.ObjectKey, kind string, objsPrevi
return false
}

func nonReady(plan apply.Plan) (result []fleet.NonReadyStatus) {
func nonReady(plan apply.Plan, ignoreOptions fleet.IgnoreOptions) (result []fleet.NonReadyStatus) {
defer func() {
sort.Slice(result, func(i, j int) bool {
return result[i].UID < result[j].UID
Expand All @@ -274,6 +276,13 @@ func nonReady(plan apply.Plan) (result []fleet.NonReadyStatus) {
return
}
if u, ok := obj.(*unstructured.Unstructured); ok {
if ignoreOptions.Conditions != nil {
err := excludeIgnoredConditions(u, ignoreOptions)
if err != nil {
logrus.Errorf("failed to ignore conditions: %v", err)
}
}

summary := summary.Summarize(u)
if !summary.IsReady() {
result = append(result, fleet.NonReadyStatus{
Expand All @@ -288,5 +297,51 @@ func nonReady(plan apply.Plan) (result []fleet.NonReadyStatus) {
}
}

return
return result
}

// excludeIgnoredConditions remove the conditions that are included in ignoreOptions from the object passed as a parameter
func excludeIgnoredConditions(obj *unstructured.Unstructured, ignoreOptions fleet.IgnoreOptions) error {
conditions, _, err := unstructured.NestedSlice(obj.Object, "status", "conditions")
if err != nil {
return err
}
conditionsWithoutIgnored := make([]interface{}, 0)

for _, condition := range conditions {
condition, ok := condition.(map[string]interface{})
if !ok {
return fmt.Errorf("condition: %#v can't be converted to map[string]interface{}", condition)
}
excludeCondition := false
for _, ignoredCondition := range ignoreOptions.Conditions {
if shouldExcludeCondition(condition, ignoredCondition) {
excludeCondition = true
break
}
}
if !excludeCondition {
conditionsWithoutIgnored = append(conditionsWithoutIgnored, condition)
}
}

err = unstructured.SetNestedSlice(obj.Object, conditionsWithoutIgnored, "status", "conditions")
if err != nil {
return err
}

return nil
}

// shouldExcludeCondition returns true if all the elements of ignoredConditions are inside conditions
func shouldExcludeCondition(conditions map[string]interface{}, ignoredConditions map[string]string) bool {
if len(ignoredConditions) > len(conditions) {
return false
}
for k, v := range ignoredConditions {
if vc, found := conditions[k]; !found || vc != v {
return false
}
}
return true
}
78 changes: 78 additions & 0 deletions modules/agent/pkg/deployer/monitor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package deployer

import (
"testing"

"github.com/google/go-cmp/cmp"
fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)

func TestExcludeIgnoredConditions(t *testing.T) {
podInitializedAndNotReady := v1.Pod{Status: v1.PodStatus{
Conditions: []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionFalse}, {Type: v1.PodInitialized, Status: v1.ConditionTrue}},
}}
uPodInitializedAndNotReady, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&podInitializedAndNotReady)
if err != nil {
t.Errorf("can't convert podInitializedAndNotReady to unstructured: %v", err)
}
podInitialized := v1.Pod{Status: v1.PodStatus{
Conditions: []v1.PodCondition{{Type: v1.PodInitialized, Status: v1.ConditionTrue}},
}}
uPodInitialized, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&podInitialized)
if err != nil {
t.Errorf("can't convert podInitializedAndNotReady to unstructured: %v", err)
}
tests := map[string]struct {
obj *unstructured.Unstructured
ignoreOptions fleet.IgnoreOptions
expectedObj *unstructured.Unstructured
expectedErr error
}{
"nothing is changed without IgnoreOptions": {
obj: &unstructured.Unstructured{Object: uPodInitializedAndNotReady},
ignoreOptions: fleet.IgnoreOptions{},
expectedObj: &unstructured.Unstructured{Object: uPodInitializedAndNotReady},
expectedErr: nil,
},
"nothing is changed when IgnoreOptions don't match any condition": {
obj: &unstructured.Unstructured{Object: uPodInitializedAndNotReady},
ignoreOptions: fleet.IgnoreOptions{Conditions: []map[string]string{{"Not": "Found"}}},
expectedObj: &unstructured.Unstructured{Object: uPodInitializedAndNotReady},
expectedErr: nil,
},
"'Type: Ready' condition is excluded when IgnoreOptions contains 'Type: Ready' condition": {
obj: &unstructured.Unstructured{Object: uPodInitializedAndNotReady},
ignoreOptions: fleet.IgnoreOptions{Conditions: []map[string]string{{"type": "Ready"}}},
expectedObj: &unstructured.Unstructured{Object: uPodInitialized},
expectedErr: nil,
},
"'Type: Ready' condition is excluded when IgnoreOptions contains 'Type: Ready, status: False' condition": {
obj: &unstructured.Unstructured{Object: uPodInitializedAndNotReady},
ignoreOptions: fleet.IgnoreOptions{Conditions: []map[string]string{{"type": "Ready", "status": "False"}}},
expectedObj: &unstructured.Unstructured{Object: uPodInitialized},
expectedErr: nil,
},
"nothing is changed when IgnoreOptions contains 'type: Ready, status: True' condition": {
obj: &unstructured.Unstructured{Object: uPodInitializedAndNotReady},
ignoreOptions: fleet.IgnoreOptions{Conditions: []map[string]string{{"type": "Ready", "status": "True"}}},
expectedObj: &unstructured.Unstructured{Object: uPodInitializedAndNotReady},
expectedErr: nil,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
obj := test.obj
err := excludeIgnoredConditions(obj, test.ignoreOptions)
if err != test.expectedErr {
t.Errorf("expected error doesn't match: expected %v, got %v", test.expectedErr, err)
}
if !cmp.Equal(obj, test.expectedObj) {
t.Errorf("objects don't match: expected %v, got %v", test.expectedObj, obj)
}
})
}
}
8 changes: 8 additions & 0 deletions pkg/apis/fleet.cattle.io/v1alpha1/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ type BundleDeploymentOptions struct {

// KeepResources can be used to keep the deployed resources when removing the bundle
KeepResources bool `json:"keepResources,omitempty"`

//IgnoreOptions can be used to ignore fields when monitoring the bundle.
IgnoreOptions `json:"ignore,omitempty"`
}

type DiffOptions struct {
Expand Down Expand Up @@ -312,6 +315,11 @@ type HelmOptions struct {
DisablePreProcess bool `json:"disablePreProcess,omitempty"`
}

// IgnoreOptions defines conditions to be ignored when monitoring the Bundle.
type IgnoreOptions struct {
Conditions []map[string]string `json:"conditions,omitempty"`
}

// Define helm values that can come from configmap, secret or external. Credit: https://github.com/fluxcd/helm-operator/blob/0cfea875b5d44bea995abe7324819432070dfbdc/pkg/apis/helm.fluxcd.io/v1/types_helmrelease.go#L439
type ValuesFrom struct {
// The reference to a config map with release values.
Expand Down
30 changes: 30 additions & 0 deletions pkg/apis/fleet.cattle.io/v1alpha1/zz_generated_deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3b62dd4

Please sign in to comment.