devops/shuttles/terraform/terraform-provider-helm/helm/provider_test.go

351 lines
8.4 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package helm
import (
"context"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"sync"
"testing"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/tools/clientcmd"
)
const (
testNamespacePrefix = "terraform-acc-test"
testResourceName = "test"
testChartsPath = "./testdata/charts"
testRepositoryDir = "./testdata/repository"
)
var (
accTest bool
testRepositoryURL string
client kubernetes.Interface = nil
testMeta *Meta
)
var providerFactory map[string]func() (tfprotov6.ProviderServer, error)
func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) {
if len(providerFactory) != 0 {
return providerFactory
}
providerFactory = map[string]func() (tfprotov6.ProviderServer, error){
"helm": providerserver.NewProtocol6WithError(New("test")()),
}
return providerFactory
}
func TestMain(m *testing.M) {
home, err := ioutil.TempDir(os.TempDir(), "helm")
if err != nil {
panic(err)
}
defer os.RemoveAll(home)
err = os.Setenv("HELM_REPOSITORY_CONFIG", filepath.Join(home, "config/repositories.yaml"))
if err != nil {
panic(err)
}
err = os.Setenv("HELM_REPOSITORY_CACHE", filepath.Join(home, "cache/helm/repository"))
if err != nil {
panic(err)
}
err = os.Setenv("HELM_REGISTRY_CONFIG", filepath.Join(home, "config/registry.json"))
if err != nil {
panic(err)
}
err = os.Setenv("HELM_PLUGINS", filepath.Join(home, "plugins"))
if err != nil {
panic(err)
}
err = os.Setenv("XDG_CACHE_HOME", filepath.Join(home, "cache"))
if err != nil {
panic(err)
}
accTest = os.Getenv("TF_ACC") == "1"
var stopRepositoryServer func()
if accTest {
_, err := exec.LookPath("helm")
if err != nil {
panic(`command "helm" needs to be available to run the test suite`)
}
// create the Kubernetes client
c, err := createKubernetesClient()
if err != nil {
panic(err)
}
client = c
// Build the test repository and start the server
buildChartRepository()
testRepositoryURL, stopRepositoryServer = startRepositoryServer()
}
ec := m.Run()
if accTest {
stopRepositoryServer()
cleanupChartRepository()
}
os.Exit(ec)
}
// todo
func TestProvider(t *testing.T) {
ctx := context.Background()
provider := New("test")()
// Create the provider server
providerServer, err := createProviderServer(provider)
if err != nil {
t.Fatalf("Failed to create provider server: %s", err)
}
// Perform config validation
validateResponse, err := providerServer.ValidateProviderConfig(ctx, &tfprotov6.ValidateProviderConfigRequest{})
if err != nil {
t.Fatalf("Provider config validation failed, error: %v", err)
}
if hasError(validateResponse.Diagnostics) {
t.Fatalf("Provider config validation failed, diagnostics: %v", validateResponse.Diagnostics)
}
}
func createProviderServer(provider provider.Provider) (tfprotov6.ProviderServer, error) {
providerServerFunc := providerserver.NewProtocol6WithError(provider)
server, err := providerServerFunc()
if err != nil {
} else {
}
return server, err
}
func buildChartRepository() {
if _, err := os.Stat(testRepositoryDir); os.IsNotExist(err) {
os.Mkdir(testRepositoryDir, os.ModePerm)
}
charts, err := ioutil.ReadDir(testChartsPath)
if err != nil {
panic(err)
}
// package all the charts
for _, c := range charts {
cmd := exec.Command("helm", "package", "-u",
filepath.Join(testChartsPath, c.Name()),
"-d", testRepositoryDir)
out, err := cmd.CombinedOutput()
if err != nil {
log.Println(string(out))
panic(err)
}
log.Printf("Created repository package for %q\n", c.Name())
}
// build the repository index
cmd := exec.Command("helm", "repo", "index", testRepositoryDir)
out, err := cmd.CombinedOutput()
if err != nil {
log.Println(string(out))
panic(err)
}
log.Println("Built chart repository index")
}
func cleanupChartRepository() {
if _, err := os.Stat(testRepositoryDir); err == nil {
err := os.RemoveAll(testRepositoryDir)
if err != nil {
fmt.Println(err)
}
}
}
func startRepositoryServer() (string, func()) {
wg := sync.WaitGroup{}
wg.Add(1)
var shutdownFunc func()
go func() {
fileserver := http.Server{
Handler: http.FileServer(http.Dir(testRepositoryDir)),
}
fileserver.SetKeepAlivesEnabled(false)
shutdownFunc = func() { fileserver.Shutdown(context.Background()) }
listener, err := net.Listen("tcp", ":0")
if err != nil {
panic(err)
}
port := listener.Addr().(*net.TCPAddr).Port
testRepositoryURL = fmt.Sprintf("http://localhost:%d", port)
wg.Done()
err = fileserver.Serve(listener)
if err != nil && err != http.ErrServerClosed {
panic(err)
}
}()
wg.Wait()
return testRepositoryURL, shutdownFunc
}
func createAndConfigureProviderServer(provider provider.Provider, ctx context.Context) (tfprotov6.ProviderServer, error) {
log.Println("Starting createAndConfigureProviderServer...")
providerServerFunc := providerserver.NewProtocol6WithError(provider)
providerServer, err := providerServerFunc()
if err != nil {
return nil, fmt.Errorf("Failed to create protocol6 provider: %w", err)
}
log.Println("Provider server function created successfully.")
configResponse, err := providerServer.ConfigureProvider(ctx, nil)
if err != nil {
return nil, fmt.Errorf("Error configuring provider: %w", err)
}
log.Println("Provider configured successfully.")
if hasError(configResponse.Diagnostics) {
return nil, fmt.Errorf("Provider configuration failed, diagnostics: %#v", configResponse.Diagnostics[0])
}
if helmProvider, ok := provider.(*HelmProvider); ok {
testMeta = helmProvider.meta
if testMeta == nil {
log.Println("testMeta is nil after type assertion.")
} else {
log.Printf("testMeta initialized: %+v", testMeta)
}
} else {
return nil, fmt.Errorf("Failed to type assert provider to HelmProvider")
}
return providerServer, nil
}
func testAccPreCheck(t *testing.T) {
if testing.Short() {
t.Skip("skipping acceptance tests in short mode")
}
http.DefaultClient.CloseIdleConnections()
ctx := context.TODO()
provider := New("test")()
// Create and configure the ProviderServer
_, err := createAndConfigureProviderServer(provider, ctx)
if err != nil {
t.Fatalf("Pre-check failed: %v", err)
}
}
func createKubernetesClient() (kubernetes.Interface, error) {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
kubeconfig := os.Getenv("KUBE_CONFIG_PATH")
if kubeconfig == "" {
panic("Need to set KUBE_CONFIG_PATH")
}
rules.ExplicitPath = kubeconfig
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, err
}
c, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return c, nil
}
func createRandomNamespace(t *testing.T) string {
if !accTest {
t.Skip("TF_ACC=1 not set")
return ""
}
namespace := fmt.Sprintf("%s-%s", testNamespacePrefix, acctest.RandString(10))
ns := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}
_, err := client.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Could not create test namespace %q: %s", namespace, err)
}
return namespace
}
func deleteNamespace(t *testing.T, namespace string) {
if !accTest {
t.Skip("TF_ACC=1 not set")
return
}
gracePeriodSeconds := int64(0)
deleteOptions := metav1.DeleteOptions{
GracePeriodSeconds: &gracePeriodSeconds,
}
err := client.CoreV1().Namespaces().Delete(context.TODO(), namespace, deleteOptions)
if err != nil {
t.Fatalf("An error occurred while deleting namespace %q: %q", namespace, err)
}
}
func randName(prefix string) string {
return fmt.Sprintf("%s-%s", prefix, acctest.RandString(10))
}
func hasError(diagnostics []*tfprotov6.Diagnostic) bool {
for _, diagnostic := range diagnostics {
if diagnostic.Severity == tfprotov6.DiagnosticSeverityError {
return true
}
}
return false
}
func DynamicValueEmpty() *tfprotov6.DynamicValue {
return &tfprotov6.DynamicValue{
MsgPack: nil,
JSON: nil,
}
}