-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
api: introduce
forceAt
and resetAt
annotations
Signed-off-by: Hidde Beydals <[email protected]>
- Loading branch information
Showing
5 changed files
with
311 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
Copyright 2023 The Flux authors | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package v2beta2 | ||
|
||
import "github.com/fluxcd/pkg/apis/meta" | ||
|
||
const ( | ||
// ForceRequestAnnotation is the annotation used for triggering a one-off forced | ||
// Helm release, even when there are no new changes in the HelmRelease. | ||
// The value is interpreted as a token, and must equal the value of | ||
// meta.ReconcileRequestAnnotation in order to trigger a release. | ||
ForceRequestAnnotation string = "reconcile.fluxcd.io/forceAt" | ||
|
||
// ResetRequestAnnotation is the annotation used for resetting the failure counts | ||
// of a HelmRelease, so that it can be retried again. | ||
// The value is interpreted as a token, and must equal the value of | ||
// meta.ReconcileRequestAnnotation in order to reset the failure counts. | ||
ResetRequestAnnotation string = "reconcile.fluxcd.io/resetAt" | ||
) | ||
|
||
// ShouldHandleResetRequest returns true if the HelmRelease has a reset request | ||
// annotation, and the value of the annotation matches the value of the | ||
// meta.ReconcileRequestAnnotation annotation. | ||
// | ||
// To ensure that the reset request is handled only once, the value of | ||
// HelmReleaseStatus.LastHandledResetAt is updated to match the value of the | ||
// reset request annotation (even if the reset request is not handled because | ||
// the value of the meta.ReconcileRequestAnnotation annotation does not match). | ||
func ShouldHandleResetRequest(obj *HelmRelease) bool { | ||
return handleRequest(obj, ResetRequestAnnotation, &obj.Status.LastHandledResetAt) | ||
} | ||
|
||
// ShouldHandleForceRequest returns true if the HelmRelease has a force request | ||
// annotation, and the value of the annotation matches the value of the | ||
// meta.ReconcileRequestAnnotation annotation. | ||
// | ||
// To ensure that the force request is handled only once, the value of | ||
// HelmReleaseStatus.LastHandledForceAt is updated to match the value of the | ||
// force request annotation (even if the force request is not handled because | ||
// the value of the meta.ReconcileRequestAnnotation annotation does not match). | ||
func ShouldHandleForceRequest(obj *HelmRelease) bool { | ||
return handleRequest(obj, ForceRequestAnnotation, &obj.Status.LastHandledForceAt) | ||
} | ||
|
||
// handleRequest returns true if the HelmRelease has a request annotation, and | ||
// the value of the annotation matches the value of the meta.ReconcileRequestAnnotation | ||
// annotation. | ||
// | ||
// The lastHandled argument is used to ensure that the request is handled only | ||
// once, and is updated to match the value of the request annotation (even if | ||
// the request is not handled because the value of the meta.ReconcileRequestAnnotation | ||
// annotation does not match). | ||
func handleRequest(obj *HelmRelease, annotation string, lastHandled *string) bool { | ||
requestAt, requestOk := obj.GetAnnotations()[annotation] | ||
reconcileAt, reconcileOk := meta.ReconcileAnnotationValue(obj.GetAnnotations()) | ||
|
||
var lastHandledRequest string | ||
if requestOk { | ||
lastHandledRequest = *lastHandled | ||
*lastHandled = requestAt | ||
} | ||
|
||
if requestOk && reconcileOk && requestAt == reconcileAt { | ||
lastHandledReconcile := obj.Status.GetLastHandledReconcileRequest() | ||
if lastHandledReconcile != reconcileAt && lastHandledRequest != requestAt { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
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,165 @@ | ||
/* | ||
Copyright 2023 The Flux authors | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package v2beta2 | ||
|
||
import ( | ||
"testing" | ||
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
||
"github.com/fluxcd/pkg/apis/meta" | ||
) | ||
|
||
func TestShouldHandleResetRequest(t *testing.T) { | ||
t.Run("should handle reset request", func(t *testing.T) { | ||
obj := &HelmRelease{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
meta.ReconcileRequestAnnotation: "b", | ||
ResetRequestAnnotation: "b", | ||
}, | ||
}, | ||
Status: HelmReleaseStatus{ | ||
LastHandledResetAt: "a", | ||
ReconcileRequestStatus: meta.ReconcileRequestStatus{ | ||
LastHandledReconcileAt: "a", | ||
}, | ||
}, | ||
} | ||
|
||
if !ShouldHandleResetRequest(obj) { | ||
t.Error("ShouldHandleResetRequest() = false") | ||
} | ||
|
||
if obj.Status.LastHandledResetAt != "b" { | ||
t.Error("ShouldHandleResetRequest did not update LastHandledResetAt") | ||
} | ||
}) | ||
} | ||
|
||
func TestShouldHandleForceRequest(t *testing.T) { | ||
t.Run("should handle force request", func(t *testing.T) { | ||
obj := &HelmRelease{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
meta.ReconcileRequestAnnotation: "b", | ||
ForceRequestAnnotation: "b", | ||
}, | ||
}, | ||
Status: HelmReleaseStatus{ | ||
LastHandledForceAt: "a", | ||
ReconcileRequestStatus: meta.ReconcileRequestStatus{ | ||
LastHandledReconcileAt: "a", | ||
}, | ||
}, | ||
} | ||
|
||
if !ShouldHandleForceRequest(obj) { | ||
t.Error("ShouldHandleForceRequest() = false") | ||
} | ||
|
||
if obj.Status.LastHandledForceAt != "b" { | ||
t.Error("ShouldHandleForceRequest did not update LastHandledForceAt") | ||
} | ||
}) | ||
} | ||
|
||
func Test_handleRequest(t *testing.T) { | ||
const requestAnnotation = "requestAnnotation" | ||
|
||
tests := []struct { | ||
name string | ||
annotations map[string]string | ||
lastHandledReconcile string | ||
lastHandledRequest string | ||
want bool | ||
expectLastHandledRequest string | ||
}{ | ||
{ | ||
name: "valid request and reconcile annotations", | ||
annotations: map[string]string{ | ||
meta.ReconcileRequestAnnotation: "b", | ||
requestAnnotation: "b", | ||
}, | ||
want: true, | ||
expectLastHandledRequest: "b", | ||
}, | ||
{ | ||
name: "mismatched annotations", | ||
annotations: map[string]string{ | ||
meta.ReconcileRequestAnnotation: "b", | ||
requestAnnotation: "c", | ||
}, | ||
want: false, | ||
expectLastHandledRequest: "c", | ||
}, | ||
{ | ||
name: "reconcile matches previous request", | ||
annotations: map[string]string{ | ||
meta.ReconcileRequestAnnotation: "b", | ||
requestAnnotation: "b", | ||
}, | ||
lastHandledReconcile: "a", | ||
lastHandledRequest: "b", | ||
want: false, | ||
expectLastHandledRequest: "b", | ||
}, | ||
{ | ||
name: "request matches previous reconcile", | ||
annotations: map[string]string{ | ||
meta.ReconcileRequestAnnotation: "b", | ||
requestAnnotation: "b", | ||
}, | ||
lastHandledReconcile: "b", | ||
lastHandledRequest: "a", | ||
want: false, | ||
expectLastHandledRequest: "b", | ||
}, | ||
{ | ||
name: "missing annotations", | ||
annotations: map[string]string{}, | ||
lastHandledRequest: "a", | ||
want: false, | ||
expectLastHandledRequest: "a", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
obj := &HelmRelease{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: tt.annotations, | ||
}, | ||
Status: HelmReleaseStatus{ | ||
ReconcileRequestStatus: meta.ReconcileRequestStatus{ | ||
LastHandledReconcileAt: tt.lastHandledReconcile, | ||
}, | ||
}, | ||
} | ||
|
||
lastHandled := tt.lastHandledRequest | ||
result := handleRequest(obj, requestAnnotation, &lastHandled) | ||
|
||
if result != tt.want { | ||
t.Errorf("handleRequest() = %v, want %v", result, tt.want) | ||
} | ||
if lastHandled != tt.expectLastHandledRequest { | ||
t.Errorf("lastHandledRequest = %v, want %v", lastHandled, tt.expectLastHandledRequest) | ||
} | ||
}) | ||
} | ||
} |
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