ext/cluster dry-run wrapper: RawAbsPath lets writes reach the real cluster
ext/cluster.Client.RawAbsPath lets every write reach the real cluster, defeating the dry-run wrapper.
ext/cluster.Client is documented as "Overwrite write actions to dry run — prevents performing actual operations". Every resource-based write method on it (PatchResource, DeleteResource, CreateResource, UpdateResource, UpdateStatusResource, ApplyResource, ApplyStatusResource) unconditionally routes to c.fake, never touching c.inner (the real client). RawAbsPath doesn't follow that pattern:
func (c *Client) RawAbsPath(ctx context.Context, path string, method string, dataReader io.Reader) ([]byte, error) {
if method == "" {
method = "GET"
}
// Try fake client first (for testing scenarios)
if result, err := c.fake.RawAbsPath(ctx, path, method, dataReader); err == nil {
return result, nil
}
return c.inner.RawAbsPath(ctx, path, method, dataReader)
}dclient's fake client hard-codes an error for any non-GET method (pkg/clients/dclient/client.go's rawAbsPathForFakeClient: if method != "GET" { return nil, fmt.Errorf("method %s not supported for fake client") }, confirmed by the existing TestRawAbsPath_PostNotSupportedOnFakeClient). So for POST/PUT/DELETE/PATCH, c.fake.RawAbsPath always errors, and this method always falls through to c.inner.RawAbsPath (the real cluster) regardless of method. A raw write through this wrapper always reaches the real cluster.
ext/cluster is exported for use outside this repo (it imports github.com/kyverno/playground/backend/data), and cluster.New wraps its client in exactly this Client for that reason. pkg/engine/apicall's executor calls RawAbsPath with whatever method a policy's APICall.Method specifies, so a policy evaluated through this dry-run path can issue a real POST/PUT/DELETE/PATCH against the live cluster.
Have a fix + tests ready, opening a PR.
Source: kyverno/kyverno