cluster-api-provider-rke2/test/helpers/envtest.go

208 lines
5.4 KiB
Go

package helpers
import (
"context"
"go/build"
"os"
"path"
"path/filepath"
"regexp"
goruntime "runtime"
"strings"
"github.com/onsi/ginkgo/v2"
admissionv1 "k8s.io/api/admissionregistration/v1"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kerrors "k8s.io/apimachinery/pkg/util/errors"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
klog "k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/manager"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
)
var (
root string
clusterAPIVersionRegex = regexp.MustCompile(`^(\W)sigs.k8s.io/cluster-api v(.+)`)
)
func init() {
klog.InitFlags(nil)
// additionally force all the controllers to use the Ginkgo logger.
ctrl.SetLogger(klog.Background())
logf.SetLogger(klog.Background())
// add logger for ginkgo
klog.SetOutput(ginkgo.GinkgoWriter)
// Calculate the scheme.
utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme))
utilruntime.Must(admissionv1.AddToScheme(scheme.Scheme))
utilruntime.Must(clusterv1.AddToScheme(scheme.Scheme))
// Get the root of the current file to use in CRD paths.
_, filename, _, _ := goruntime.Caller(0) //nolint
root = path.Join(path.Dir(filename), "..", "..")
}
// TestEnvironmentConfiguration is a wrapper configuration for envtest.
type TestEnvironmentConfiguration struct {
env *envtest.Environment
}
// TestEnvironment encapsulates a Kubernetes local test environment.
type TestEnvironment struct {
manager.Manager
client.Client
Config *rest.Config
env *envtest.Environment
cancel context.CancelFunc
}
// Cleanup deletes all the given objects.
func (t *TestEnvironment) Cleanup(ctx context.Context, objs ...client.Object) error {
errs := []error{}
for _, o := range objs {
err := t.Delete(ctx, o)
if apierrors.IsNotFound(err) {
continue
}
errs = append(errs, err)
}
return kerrors.NewAggregate(errs)
}
// CreateNamespace creates a new namespace with a generated name.
func (t *TestEnvironment) CreateNamespace(ctx context.Context, generateName string) (*corev1.Namespace, error) {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateName + "-",
Labels: map[string]string{
"testenv/original-name": generateName,
},
},
}
if err := t.Create(ctx, ns); err != nil {
return nil, err
}
return ns, nil
}
// NewTestEnvironmentConfiguration creates a new test environment configuration for running tests.
func NewTestEnvironmentConfiguration(crdDirectoryPaths []string) *TestEnvironmentConfiguration {
resolvedCrdDirectoryPaths := []string{}
for _, p := range crdDirectoryPaths {
resolvedCrdDirectoryPaths = append(resolvedCrdDirectoryPaths, path.Join(root, p))
}
if capiPath := getFilePathToCAPICRDs(root); capiPath != "" {
resolvedCrdDirectoryPaths = append(resolvedCrdDirectoryPaths, capiPath)
}
return &TestEnvironmentConfiguration{
env: &envtest.Environment{
ErrorIfCRDPathMissing: true,
CRDDirectoryPaths: resolvedCrdDirectoryPaths,
},
}
}
// Build creates a new environment spinning up a local api-server.
// This function should be called only once for each package you're running tests within,
// usually the environment is initialized in a suite_test.go file within a `BeforeSuite` ginkgo block.
func (t *TestEnvironmentConfiguration) Build() (*TestEnvironment, error) {
if _, err := t.env.Start(); err != nil {
panic(err)
}
options := manager.Options{
Scheme: scheme.Scheme,
Metrics: metricsserver.Options{
BindAddress: "0",
},
Client: client.Options{
Cache: &client.CacheOptions{
DisableFor: []client.Object{
&corev1.ConfigMap{},
&corev1.Secret{},
&corev1.Node{},
&clusterv1.Machine{},
},
},
},
}
mgr, err := ctrl.NewManager(t.env.Config, options)
if err != nil {
klog.Fatalf("Failed to start testenv manager: %v", err)
}
return &TestEnvironment{
Manager: mgr,
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
env: t.env,
}, nil
}
// StartManager starts the test controller against the local API server.
func (t *TestEnvironment) StartManager(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
t.cancel = cancel
return t.Start(ctx)
}
// Stop stops the test environment.
func (t *TestEnvironment) Stop() error {
t.cancel()
return t.env.Stop()
}
func getFilePathToCAPICRDs(root string) string {
modBits, err := os.ReadFile(filepath.Join(root, "go.mod")) //nolint:gosec
if err != nil {
return ""
}
var clusterAPIVersion string
for _, line := range strings.Split(string(modBits), "\n") {
matches := clusterAPIVersionRegex.FindStringSubmatch(line)
if len(matches) == 3 {
clusterAPIVersion = matches[2]
}
}
if clusterAPIVersion == "" {
return ""
}
gopath := envOr("GOPATH", build.Default.GOPATH)
return filepath.Join(gopath, "pkg", "mod", "sigs.k8s.io", "cluster-api@v"+clusterAPIVersion, "config", "crd", "bases")
}
func envOr(envKey, defaultValue string) string {
if value, ok := os.LookupEnv(envKey); ok {
return value
}
return defaultValue
}