#17790·kyverno

ext/cluster dry-run wrapper: RawAbsPath lets writes reach the real cluster

Author: RudraDudhat2509Created Sep 30, 2026Updated Sep 30, 2026

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:

go
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.