mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-10-19 02:54:20 +02:00
Use lazy copy to patch resources to ensure multiple modifications are applied to the base resource (#4580)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot App
parent
6e4d85f28f
commit
d386789092
@@ -0,0 +1,113 @@
|
||||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func TestLazyCopyDoesNotCopyUntilMutated(t *testing.T) {
|
||||
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod"}}
|
||||
lazy := newLazyCopy(pod)
|
||||
|
||||
assert.False(t, lazy.Modified())
|
||||
assert.Panics(t, func() { lazy.MergeFrom() })
|
||||
}
|
||||
|
||||
func TestLazyCopySnapshotsBeforeTheFirstMutation(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod",
|
||||
Annotations: map[string]string{"key": "old"},
|
||||
},
|
||||
}
|
||||
lazy := newLazyCopy(pod)
|
||||
|
||||
lazy.Mutate().Annotations["key"] = "new"
|
||||
require.True(t, lazy.Modified())
|
||||
|
||||
data, err := lazy.MergeFrom().Data(pod)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"metadata":{"annotations":{"key":"new"}}}`, string(data))
|
||||
}
|
||||
|
||||
func TestLazyCopySnapshotsOnlyOnce(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod",
|
||||
Annotations: map[string]string{"first": "old", "second": "old"},
|
||||
},
|
||||
}
|
||||
lazy := newLazyCopy(pod)
|
||||
|
||||
lazy.Mutate().Annotations["first"] = "new"
|
||||
// The second mutation must diff against the state before the first one,
|
||||
// otherwise the earlier change is dropped from the patch.
|
||||
lazy.Mutate().Annotations["second"] = "new"
|
||||
|
||||
data, err := lazy.MergeFrom().Data(pod)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"metadata":{"annotations":{"first":"new","second":"new"}}}`, string(data))
|
||||
}
|
||||
|
||||
func TestLazyCopyMergeFromIsAMergePatch(t *testing.T) {
|
||||
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod"}}
|
||||
lazy := newLazyCopy(pod)
|
||||
lazy.Mutate().Labels = map[string]string{"key": "value"}
|
||||
|
||||
assert.Equal(t, client.MergeFrom(pod).Type(), lazy.MergeFrom().Type())
|
||||
}
|
||||
|
||||
// Pins the hazard documented on lazyCopy: the caller keeps the pointer it
|
||||
// handed to newLazyCopy, so it can write through it without going via Mutate.
|
||||
// A write that lands before the first Mutate is already in the snapshot, so it
|
||||
// is absent from the patch and never reaches the API server. The type cannot
|
||||
// prevent this, which is why the ordering is a caller invariant rather than a
|
||||
// guarantee.
|
||||
func TestLazyCopyDropsWritesMadeBeforeTheFirstMutate(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod",
|
||||
Annotations: map[string]string{"smuggled": "old", "declared": "old"},
|
||||
},
|
||||
}
|
||||
lazy := newLazyCopy(pod)
|
||||
|
||||
// Bypasses Mutate, so it is captured by the snapshot taken below.
|
||||
pod.Annotations["smuggled"] = "new"
|
||||
|
||||
lazy.Mutate().Annotations["declared"] = "new"
|
||||
|
||||
data, err := lazy.MergeFrom().Data(pod)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"metadata":{"annotations":{"declared":"new"}}}`, string(data))
|
||||
}
|
||||
|
||||
// The status patch in patchAppliedActionableRevisionStatus relies on being able
|
||||
// to attach an optimistic lock, so that a patch computed from a stale read is
|
||||
// rejected by the API server rather than silently moving the applied revision
|
||||
// backwards. Without the precondition the patch can never conflict, so the
|
||||
// surrounding RetryOnConflict would never fire.
|
||||
func TestLazyCopyMergeFromForwardsMergeOptions(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod",
|
||||
ResourceVersion: "42",
|
||||
},
|
||||
}
|
||||
lazy := newLazyCopy(pod)
|
||||
lazy.Mutate().Labels = map[string]string{"key": "value"}
|
||||
|
||||
data, err := lazy.MergeFrom(client.MergeFromWithOptimisticLock{}).Data(pod)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(
|
||||
t,
|
||||
`{"metadata":{"labels":{"key":"value"},"resourceVersion":"42"}}`,
|
||||
string(data),
|
||||
"the lock is carried as a resourceVersion precondition in the patch body, taken from the snapshot rather than the mutated object",
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user