package main
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/tools/clientcmd/api/latest"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/envtest"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
var (
codec runtime.Codec
environments Map[string, envtest.Environment]
jsonSerializer *json.Serializer
)
type Envtest struct{}
type createErrorType uint8
const (
createErrorTypeSetupBinaryAssetsDirectory createErrorType = iota
createErrorTypeDecodeCRD
createErrorTypeStartEnvironment
createErrorTypeBuildKubeconfig
createErrorTypeStopEnvironment
)
type destroyErrorType uint8
const (
destroyErrorEnvMissing destroyErrorType = iota
destroyErrorTypeStopEnvironment
)
func init() {
EnvTestImpl = Envtest{}
}
func init() {
ctrl.SetLogger(klog.Background())
jsonSerializer = json.NewSerializerWithOptions(json.DefaultMetaFactory, latest.Scheme, latest.Scheme, json.SerializerOptions{})
codec = versioning.NewDefaultingCodecForScheme(
latest.Scheme,
jsonSerializer,
jsonSerializer,
schema.GroupVersion{Version: latest.Version},
runtime.InternalGroupVersioner,
)
}
func FromEnvTestConfig(cfg *rest.Config) (string, error) {
contextName := fmt.Sprintf("%s@envtest", cfg.Username)
c := api.Config{
Clusters: map[string]*api.Cluster{
"envtest": {
Server: cfg.Host,
CertificateAuthorityData: cfg.CAData,
},
},
Contexts: map[string]*api.Context{
contextName: {
Cluster: "envtest",
AuthInfo: cfg.Username,
},
},
AuthInfos: map[string]*api.AuthInfo{
cfg.Username: {
ClientKeyData: cfg.KeyData,
ClientCertificateData: cfg.CertData,
},
},
CurrentContext: contextName,
}
data, err := runtime.Encode(codec, &c)
return string(data), err
}
func (e Envtest) create(req *Environment) (resp CreateResponse) {
storeErr := func(err error, errorType createErrorType) CreateResponse {
resp.err = append(resp.err, err.Error())
resp.error_type = append(resp.error_type, uint8(errorType))
return resp
}
env := &envtest.Environment{
DownloadBinaryAssets: req.binary_assets_settings.download_binary_assets,
CRDInstallOptions: envtest.CRDInstallOptions{
Paths: req.crd_install_options.paths,
ErrorIfPathMissing: req.crd_install_options.error_if_path_missing,
},
}
if len(req.binary_assets_settings.binary_assets_directory) > 0 {
env.BinaryAssetsDirectory = req.binary_assets_settings.binary_assets_directory[0]
} else if binaryAssetsDirectory, err := envtest.SetupEnvtestDefaultBinaryAssetsDirectory(); err != nil {
return storeErr(err, createErrorTypeSetupBinaryAssetsDirectory)
} else {
env.BinaryAssetsDirectory = binaryAssetsDirectory
}
if len(req.binary_assets_settings.download_binary_assets_version) > 0 {
env.DownloadBinaryAssetsVersion = req.binary_assets_settings.download_binary_assets_version[0]
}
if len(req.binary_assets_settings.download_binary_assets_index_url) > 0 {
env.DownloadBinaryAssetsIndexURL = req.binary_assets_settings.download_binary_assets_index_url[0]
}
destroy := func(env *envtest.Environment) {
if err := env.Stop(); err != nil {
storeErr(err, createErrorTypeStopEnvironment)
}
}
gvk := apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition")
for _, data := range req.crd_install_options.crds {
crd := &apiextensionsv1.CustomResourceDefinition{}
if _, _, err := jsonSerializer.Decode([]byte(data), &gvk, crd); err != nil {
return storeErr(err, createErrorTypeDecodeCRD)
}
env.CRDs = append(env.CRDs, crd)
}
config, err := env.Start()
if err != nil {
return storeErr(err, createErrorTypeStartEnvironment)
}
kubeconfig, err := FromEnvTestConfig(config)
if err != nil {
defer destroy(env)
return storeErr(err, createErrorTypeBuildKubeconfig)
}
environments.Store(kubeconfig, *env)
resp.server = Server{kubeconfig: kubeconfig}
return
}
func (e Envtest) destroy(kubeconfig *string) (resp DestroyResponse) {
if kubeconfig == nil || *kubeconfig == "" {
return
}
storeErr := func(err error, errorType destroyErrorType) DestroyResponse {
resp.err = append(resp.err, err.Error())
resp.error_type = append(resp.error_type, uint8(errorType))
return resp
}
env, ok := environments.Load(*kubeconfig)
if !ok {
return storeErr(fmt.Errorf("environement for the provided kubeconfig was already destroyed or does not exist"), destroyErrorEnvMissing)
}
err := env.Stop()
if err != nil {
return storeErr(err, destroyErrorTypeStopEnvironment)
}
return
}