Skip to main content

Migrating from envtest

github.com/yyewolf/petri/controllerruntime/envtest is a drop-in replacement for controller-runtime's envtest package. In a kubebuilder project, change the import in suite_test.go and nothing else:

- "sigs.k8s.io/controller-runtime/pkg/envtest"
+ "github.com/yyewolf/petri/controllerruntime/envtest"
suite_test.go
package envtest_test

import (
"path/filepath"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"

// The one line that changes from kubebuilder's scaffold, which imports
// "sigs.k8s.io/controller-runtime/pkg/envtest".
"github.com/yyewolf/petri/controllerruntime/envtest"
)

var (
cfg *rest.Config
k8sClient client.Client
testEnv *envtest.Environment
)

func TestControllers(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Controller Suite")
}

var _ = BeforeSuite(func() {
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "crds", "testdata")},
ErrorIfCRDPathMissing: true,
}

var err error
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())

k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
})

var _ = AfterSuite(func() {
Expect(testEnv.Stop()).To(Succeed())
})

You can then remove the setup-envtest step from your Makefile and CI, KUBEBUILDER_ASSETS, and any cache of downloaded binaries.

What Start does​

Environment.Start starts a dedicated control plane and installs the CRDs and webhook configurations. Like envtest, it fills in LocalServingHost, LocalServingPort and LocalServingCertDir for your webhook server. Stop removes the webhook configurations and the certificate directory, then stops the plane.

Supported fields: CRDDirectoryPaths, CRDs, ErrorIfCRDPathMissing, CRDInstallOptions.Paths, CRDInstallOptions.CRDs, WebhookInstallOptions.Paths, WebhookInstallOptions.MutatingWebhooks, WebhookInstallOptions.ValidatingWebhooks, WebhookInstallOptions.LocalServingHost, WebhookInstallOptions.LocalServingPort, WebhookInstallOptions.LocalServingCertDir, Scheme, AttachControlPlaneOutput and DisableDefaultControllers.

Some fields would change what your tests check, so Start fails when they are set instead of ignoring them:

  • UseExistingCluster: petri never talks to a real cluster;
  • BinaryAssetsDirectory: petri runs no binaries;
  • CRDInstallOptions.CleanUpAfterUse: petri never deletes CRDs.

A CRD path that is missing or has no manifests always fails, whatever ErrorIfCRDPathMissing says.

What changes​

The drop-in runs petri's default controllers, the namespace controller and the garbage collector, so your suite sees what a cluster would do:

namespace_test.go
package envtest_test

import (
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = Describe("Namespaces", func() {
It("go away when deleted, unlike with envtest", func(ctx SpecContext) {
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{GenerateName: "scratch-"}}
Expect(k8sClient.Create(ctx, ns)).To(Succeed())
Expect(k8sClient.Delete(ctx, ns)).To(Succeed())

// envtest runs no namespace controller, so there the namespace would
// stay Terminating forever. petri runs it by default.
Eventually(func(ctx SpecContext) error {
return k8sClient.Get(ctx, client.ObjectKeyFromObject(ns), &corev1.Namespace{})
}).WithContext(ctx).WithTimeout(30 * time.Second).Should(Satisfy(apierrors.IsNotFound))
})
})
envtestpetri
Deleted namespacestays Terminatingremoved
Deleting an ownerdependents staydependents deleted
Binarieskube-apiserver and etcd, downloadednone, compiled in
Kubernetes versionany, per binaryyour k8s.io/client-go minor
Workload controllersnoneopt-in with testEnv.EnableControllers(t, ...)

A test that relied on envtest's behaviour may notice. The usual case is a custom resource with a finalizer that nothing removes: its namespace now stays Terminating until the finalizer goes, as on a cluster. Remove the finalizer in the test's cleanup, or set DisableDefaultControllers: true to get envtest's behaviour back.

petri cannot test version skew, such as your client against an older API server; see compatibility.