Skip to content

Commit c775bf3

Browse files
WIP: Capture warnings using custom client
1 parent 023e47e commit c775bf3

File tree

2 files changed

+75
-8
lines changed

2 files changed

+75
-8
lines changed

pkg/admissionpolicy/testutils/util.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ limitations under the License.
1717
package testutils
1818

1919
import (
20+
"context"
2021
"errors"
2122
"io"
2223
"os"
2324
"path/filepath"
2425
"strings"
26+
"sync"
2527

2628
corev1 "k8s.io/api/core/v1"
2729

@@ -32,6 +34,7 @@ import (
3234
"k8s.io/apimachinery/pkg/runtime"
3335
"k8s.io/apimachinery/pkg/util/yaml"
3436
"k8s.io/client-go/kubernetes/scheme"
37+
"k8s.io/client-go/rest"
3538

3639
"sigs.k8s.io/controller-runtime/pkg/client"
3740
)
@@ -261,3 +264,55 @@ func LoadTransportConfigMaps() map[string][]client.Object {
261264

262265
return mapObjs
263266
}
267+
268+
// WarningCollector is to provide a way to collect
269+
// kube client warnings, intended for testing VAPs that return warnings.
270+
type WarningCollector struct {
271+
sync.Mutex
272+
messages []string
273+
}
274+
275+
// HandleWarningHeaderWithContext implements rest.WarningHandlerWithContext.
276+
func (w *WarningCollector) HandleWarningHeaderWithContext(_ context.Context, code int, _ string, message string) {
277+
w.Lock()
278+
w.messages = append(w.messages, message)
279+
w.Unlock()
280+
}
281+
282+
// Messages returns messages collected by a warning collector.
283+
func (w *WarningCollector) Messages() []string {
284+
w.Lock()
285+
defer w.Unlock()
286+
287+
// return a copy for thread-safety
288+
out := make([]string, len(w.messages))
289+
copy(out, w.messages)
290+
291+
return out
292+
}
293+
294+
// Reset clears the messages, used betwen tests to reset state.
295+
func (w *WarningCollector) Reset() {
296+
w.Lock()
297+
w.messages = nil
298+
w.Unlock()
299+
}
300+
301+
// SetupClientWithWarningCollector creates a new client.Client, with a warning handler that writes to a WarningCollector that is also returned.
302+
// TODO: english good.
303+
func SetupClientWithWarningCollector(cfg *rest.Config, scheme *runtime.Scheme) (client.Client, *WarningCollector, error) {
304+
warnSink := &WarningCollector{}
305+
// copy to avoid mutating the passed-in config
306+
newcfg := rest.CopyConfig(cfg)
307+
308+
// Use the per-config handler (preferred in modern client-go)
309+
newcfg.WarningHandlerWithContext = warnSink
310+
311+
// Build the client with this config
312+
client, err := client.New(newcfg, client.Options{Scheme: scheme})
313+
if err != nil {
314+
return nil, nil, err
315+
}
316+
317+
return client, warnSink, nil
318+
}

pkg/controllers/machinesync/machine_sync_controller_test.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,8 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
13651365
})
13661366

13671367
FContext("Updates to MAPI machine warns user if the Synchronized condition is set to false", func() {
1368+
var warnClient client.Client
1369+
var warnSink *admissiontestutils.WarningCollector
13681370
BeforeEach(func() {
13691371
By("Waiting for VAP to be ready")
13701372
machineVap = &admissionregistrationv1.ValidatingAdmissionPolicy{}
@@ -1406,14 +1408,20 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
14061408
sentinelMachine := mapiMachineBuilder.WithName("sentinel-machine").WithAuthoritativeAPI(mapiv1beta1.MachineAuthorityClusterAPI).Build()
14071409
Eventually(k8sClient.Create(ctx, sentinelMachine), timeout).Should(Succeed())
14081410

1409-
capiSentinelMachine := clusterv1resourcebuilder.Machine().WithName("sentinel-machine").WithNamespace(capiNamespace.Name).Build()
1410-
Expect(k8sClient.Create(ctx, capiSentinelMachine)).To(Succeed())
1411+
var err error
1412+
warnClient, warnSink, err = admissiontestutils.SetupClientWithWarningCollector(cfg, testScheme)
1413+
Expect(err).To(Not(HaveOccurred()))
14111414

1412-
Eventually(k.Get(capiSentinelMachine)).Should(Succeed())
1415+
// TODO: I don't know if doing this, or setting the komega client
1416+
// to be the warnClient (then unsetting it with DeferCleanup()) is better
1417+
Eventually(func(g Gomega) {
1418+
warnSink.Reset() // keep each probe self-contained
14131419

1414-
Eventually(k.Update(sentinelMachine, func() {
14151420
sentinelMachine.ObjectMeta.Labels = map[string]string{"test-sentinel": "fubar"}
1416-
}), timeout).Should(Succeed())
1421+
g.Expect(warnClient.Update(ctx, sentinelMachine)).To(Succeed())
1422+
g.Expect(warnSink.Messages()).To(ContainElement(ContainSubstring("policy in place")))
1423+
}, timeout).Should(Succeed())
1424+
14171425
})
14181426

14191427
It("warns the user when the machine is still synchronzing", func() {
@@ -1430,10 +1438,14 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
14301438
}
14311439
})).Should(Succeed())
14321440

1433-
By("Attempting to update the authoritativeAPI should be blocked")
1434-
Eventually(k.Update(mapiMachine, func() {
1441+
By("Attempting to update the authoritativeAPI should emit a warning")
1442+
Eventually(func(g Gomega) {
1443+
warnSink.Reset() // keep each probe self-contained
1444+
14351445
mapiMachine.Spec.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI
1436-
}), timeout).Should(Succeed()) // This should succeed but show a warning?
1446+
g.Expect(warnClient.Update(ctx, mapiMachine)).To(Succeed())
1447+
g.Expect(warnSink.Messages()).To(ContainElement(ContainSubstring("updating .spec.authoritativeAPI when Synchronized=false on resource means changes may not take effect")))
1448+
}, timeout).Should(Succeed())
14371449
})
14381450

14391451
})

0 commit comments

Comments
 (0)