Skip to content
Open
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
18 changes: 16 additions & 2 deletions api/v1/clusterextensionrevision_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ type ProgressionProbe struct {
// +required
// <opcon:experimental>
Assertions []Assertion `json:"assertions,omitempty"`

// observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to
// .metadata.generation before running the given assertions on the selected object(s). If a probed object does
// not contain the status.observedGeneration field, the given prober is executed directly.
//
// +optional
// +kubebuilder:default:=false
// <opcon:experimental>
ObservedGeneration *bool `json:"observedGeneration,omitempty"`
}

// ObjectSelector is a discriminated union which defines the method by which we select objects to make assertions against.
Expand Down Expand Up @@ -227,9 +236,14 @@ const (
type ProbeType string

const (
ProbeTypeFieldCondition ProbeType = "ConditionEqual"
ProbeTypeFieldEqual ProbeType = "FieldsEqual"
ProbeTypeConditionEqual ProbeType = "ConditionEqual"
ProbeTypeFieldsEqual ProbeType = "FieldsEqual"
ProbeTypeFieldValue ProbeType = "FieldValue"

// Deprecated: use ProbeTypeConditionEqual instead.
ProbeTypeFieldCondition = ProbeTypeConditionEqual
// Deprecated: use ProbeTypeFieldsEqual instead.
ProbeTypeFieldEqual = ProbeTypeFieldsEqual
)

// Assertion is a discriminated union which defines the probe type and definition used as an assertion.
Expand Down
5 changes: 5 additions & 0 deletions api/v1/zz_generated.deepcopy.go

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

14 changes: 14 additions & 0 deletions applyconfigurations/api/v1/progressionprobe.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ spec:
minItems: 1
type: array
x-kubernetes-list-type: atomic
observedGeneration:
default: false
description: |-
observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to
.metadata.generation before running the given assertions on the selected object(s). If a probed object does
not contain the status.observedGeneration field, the given prober is executed directly.
type: boolean
selector:
description: |-
selector is a required field which defines the method by which we select objects to apply the below
Expand Down
116 changes: 115 additions & 1 deletion internal/operator-controller/applier/boxcutter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (

"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -208,10 +211,12 @@ func (r *SimpleRevisionGenerator) buildClusterExtensionRevision(
annotations[labels.ServiceAccountNamespaceKey] = ext.Spec.Namespace

phases := PhaseSort(objects)
progressionProbes := defaultProgressionProbes()

spec := ocv1ac.ClusterExtensionRevisionSpec().
WithLifecycleState(ocv1.ClusterExtensionRevisionLifecycleStateActive).
WithPhases(phases...)
WithPhases(phases...).
WithProgressionProbes(progressionProbes...)
if p := ext.Spec.ProgressDeadlineMinutes; p > 0 {
spec.WithProgressDeadlineMinutes(p)
}
Expand Down Expand Up @@ -602,6 +607,115 @@ func latestRevisionNumber(prevRevisions []ocv1.ClusterExtensionRevision) int64 {
return prevRevisions[len(prevRevisions)-1].Spec.Revision
}

func defaultProgressionProbes() []*ocv1ac.ProgressionProbeApplyConfiguration {
crdProbe := ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: "apiextensions.k8s.io",
Kind: "CustomResourceDefinition",
})).
WithAssertions(ocv1ac.Assertion().
WithType(ocv1.ProbeTypeConditionEqual).
WithConditionEqual(
ocv1ac.ConditionEqualProbe().
WithType(string(apiextensions.Established)).
WithStatus(string(corev1.ConditionTrue)))).
WithObservedGeneration(true)

// Checks if the Type: "Ready" Condition is "True"
readyConditionAssertion := ocv1ac.Assertion().
WithType(ocv1.ProbeTypeConditionEqual).
WithConditionEqual(
ocv1ac.ConditionEqualProbe().
WithType("Ready").
WithStatus("True"))

certProbe := ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: "acme.cert-manager.io",
Kind: "Certificate",
})).
WithAssertions(readyConditionAssertion).
WithObservedGeneration(true)
issuerProbe := ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: "acme.cert-manager.io",
Kind: "Issuer",
})).
WithAssertions(readyConditionAssertion).
WithObservedGeneration(true)

// namespaceActiveProbe is a probe which asserts that the namespace is in "Active" phase
namespaceActiveProbe := ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: corev1.GroupName,
Kind: "Namespace",
})).
WithAssertions(ocv1ac.Assertion().
WithType(ocv1.ProbeTypeFieldValue).
WithFieldValue(ocv1ac.FieldValueProbe().
WithFieldPath("status.phase").
WithValue(string(corev1.NamespaceActive))))

// pvcBoundProbe is a probe which asserts that the PVC is in "Bound" phase
pvcBoundProbe := ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: corev1.GroupName,
Kind: "PersistentVolumeClaim",
})).
WithAssertions(ocv1ac.Assertion().
WithType(ocv1.ProbeTypeFieldValue).
WithFieldValue(ocv1ac.FieldValueProbe().
WithFieldPath("status.phase").
WithValue(string(corev1.ClaimBound))))

// Checks if the Type: "Available" Condition is "True".
availableConditionAssertion := ocv1ac.Assertion().
WithType(ocv1.ProbeTypeConditionEqual).
WithConditionEqual(ocv1ac.ConditionEqualProbe().
WithType(string(appsv1.DeploymentAvailable)).
WithStatus(string(corev1.ConditionTrue)))

// Checks if status.updatedReplicas == status.replicas.
// Works for StatefulSets, Deployments and ReplicaSets.
replicasUpdatedAssertion := ocv1ac.Assertion().
WithType(ocv1.ProbeTypeFieldsEqual).
WithFieldsEqual(ocv1ac.FieldsEqualProbe().
WithFieldA("status.updatedReplicas").
WithFieldB("status.replicas"))

statefulSetProbe := ocv1ac.ProgressionProbe().WithSelector(
ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: appsv1.GroupName,
Kind: "StatefulSet",
}),
).WithAssertions(replicasUpdatedAssertion, availableConditionAssertion).
WithObservedGeneration(true)

deploymentProbe := ocv1ac.ProgressionProbe().WithSelector(
ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: appsv1.GroupName,
Kind: "Deployment",
}),
).WithAssertions(replicasUpdatedAssertion, availableConditionAssertion).
WithObservedGeneration(true)

return []*ocv1ac.ProgressionProbeApplyConfiguration{
deploymentProbe, statefulSetProbe, pvcBoundProbe, namespaceActiveProbe, issuerProbe, certProbe, crdProbe,
}
}

func splitManifestDocuments(file string) []string {
// Estimate: typical manifests have ~50-100 lines per document
// Pre-allocate for reasonable bundle size to reduce allocations
Expand Down
106 changes: 105 additions & 1 deletion internal/operator-controller/applier/boxcutter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apierrors "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -143,7 +144,110 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T)
},
},
}),
),
)).
WithProgressionProbes(
ocv1ac.ProgressionProbe().WithSelector(
ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: appsv1.GroupName,
Kind: "Deployment",
})).
WithAssertions(
ocv1ac.Assertion().
WithType(ocv1.ProbeTypeFieldsEqual).
WithFieldsEqual(ocv1ac.FieldsEqualProbe().
WithFieldA("status.updatedReplicas").
WithFieldB("status.replicas")),
ocv1ac.Assertion().
WithType(ocv1.ProbeTypeConditionEqual).
WithConditionEqual(ocv1ac.ConditionEqualProbe().
WithType(string(appsv1.DeploymentAvailable)).
WithStatus(string(corev1.ConditionTrue)))).
WithObservedGeneration(true),
ocv1ac.ProgressionProbe().WithSelector(
ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: appsv1.GroupName,
Kind: "StatefulSet",
})).
WithAssertions(
ocv1ac.Assertion().
WithType(ocv1.ProbeTypeFieldsEqual).
WithFieldsEqual(ocv1ac.FieldsEqualProbe().
WithFieldA("status.updatedReplicas").
WithFieldB("status.replicas")),
ocv1ac.Assertion().
WithType(ocv1.ProbeTypeConditionEqual).
WithConditionEqual(ocv1ac.ConditionEqualProbe().
WithType(string(appsv1.DeploymentAvailable)).
WithStatus(string(corev1.ConditionTrue)))).
WithObservedGeneration(true),
ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: corev1.GroupName,
Kind: "PersistentVolumeClaim",
})).
WithAssertions(ocv1ac.Assertion().
WithType(ocv1.ProbeTypeFieldValue).
WithFieldValue(ocv1ac.FieldValueProbe().
WithFieldPath("status.phase").
WithValue(string(corev1.ClaimBound)))),
ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: corev1.GroupName,
Kind: "Namespace",
})).
WithAssertions(ocv1ac.Assertion().
WithType(ocv1.ProbeTypeFieldValue).
WithFieldValue(ocv1ac.FieldValueProbe().
WithFieldPath("status.phase").
WithValue(string(corev1.NamespaceActive)))),
ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: "acme.cert-manager.io",
Kind: "Issuer",
})).
WithAssertions(ocv1ac.Assertion().
WithType(ocv1.ProbeTypeConditionEqual).
WithConditionEqual(
ocv1ac.ConditionEqualProbe().
WithType("Ready").
WithStatus("True"))).
WithObservedGeneration(true),
ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: "acme.cert-manager.io",
Kind: "Certificate",
})).
WithAssertions(ocv1ac.Assertion().
WithType(ocv1.ProbeTypeConditionEqual).
WithConditionEqual(
ocv1ac.ConditionEqualProbe().
WithType("Ready").
WithStatus("True"))).
WithObservedGeneration(true),
ocv1ac.ProgressionProbe().
WithSelector(ocv1ac.ObjectSelector().
WithType(ocv1.SelectorTypeGroupKind).
WithGroupKind(metav1.GroupKind{
Group: "apiextensions.k8s.io",
Kind: "CustomResourceDefinition",
})).
WithAssertions(ocv1ac.Assertion().
WithType(ocv1.ProbeTypeConditionEqual).
WithConditionEqual(
ocv1ac.ConditionEqualProbe().
WithType(string(apiextensions.Established)).
WithStatus(string(corev1.ConditionTrue)))).
WithObservedGeneration(true),
),
)
assert.Equal(t, expected, rev)
Expand Down
Loading
Loading