<|start_filename|>e2e/e2e.go<|end_filename|>
package e2e

import (
	"log"
	"sync"
	"testing"
	"time"

	"github.com/gruntwork-io/terratest/modules/k8s"
)

type Secret struct {
	Path    string
	Key     string
	Value   string
	Env     string
	Version int
}

type K8SCluster interface {
	GetK8SOptions() *k8s.KubectlOptions
	LoadImages(images ...string) error
	Delete() error
}

type TestContext struct {
	cluster        K8SCluster
	KubectlOptions *k8s.KubectlOptions
	Secrets        []Secret
	VSFImage       string
	KindImage      string
	VaultImage     string
}

func (c *TestContext) setup(vsfImage, vaultImage, kindImage string, secrets []Secret) (*TestContext, error) {
	c.Secrets = secrets
	c.VSFImage = vsfImage
	c.VaultImage = vaultImage
	log.Println("Preparing local k8s cluster for tests...")
	k8sCluster, err := NewKindCluster(kindImage)
	if err != nil {
		return nil, err
	}
	log.Println("Local k8s cluster is ready")
	c.cluster = k8sCluster
	c.KubectlOptions = c.cluster.GetK8SOptions()
	log.Printf("Uploading %s image to k8s...\n", c.VSFImage)
	err = c.cluster.LoadImages(c.VSFImage, c.VaultImage)
	if err != nil {
		return nil, err
	}
	log.Printf("The image %s was uploaded...\n", c.VSFImage)
	return c, nil
}

func (c *TestContext) teardown() error {
	log.Println("Deleting local k8s cluster...")
	return c.cluster.Delete()
}

func (c *TestContext) withDependencies(t *testing.T, dependencies ...*Dependency) {
	for _, dependency := range dependencies {
		if dependency.isApplied {
			continue
		}
		dependency.mu.Lock()
		defer dependency.mu.Unlock()
		log.Printf("Applying dependency %s for tests...\n", dependency.name)
		dependency.apply(t, dependency, c)
	}
}

type testContextApplyFunc func(t *testing.T, d *Dependency, c *TestContext)

type Dependency struct {
	mu sync.Mutex

	name      string
	apply     testContextApplyFunc
	isApplied bool
}

var Vault = &Dependency{
	name: "vault",
	apply: func(t *testing.T, d *Dependency, c *TestContext) {
		templatePath := "./resources/vault.yml.tmpl"
		values := struct {
			VaultImage string
			Secrets    []Secret
		}{
			VaultImage: c.VaultImage,
			Secrets:    c.Secrets,
		}
		kubectlApplyWithTemplate(t, c.KubectlOptions, templatePath, values)
		k8s.WaitUntilPodAvailable(t, c.KubectlOptions, "vault", 10, 5*time.Second)
		_, err := k8s.RunKubectlAndGetOutputE(t, c.KubectlOptions, "exec", "vault", "--", "/bin/sh", "-c", "/vault-config/init.sh")
		if err != nil {
			t.Error(err)
			return
		}
		d.isApplied = true
	},
}

var AppWithV1Matcher = &Dependency{
	name: "app-with-matcher-v1",
	apply: func(t *testing.T, d *Dependency, c *TestContext) {
		templatePath := "./resources/app-with-matcher-v1.yml.tmpl"
		values := struct {
			PodImage string
			Secrets  []Secret
		}{
			PodImage: c.VSFImage,
			Secrets:  c.Secrets,
		}
		kubectlApplyWithTemplate(t, c.KubectlOptions, templatePath, values)
		k8s.WaitUntilPodAvailable(t, c.KubectlOptions, "app-with-matcher-v1", 10, 5*time.Second)
		d.isApplied = true
	},
}

var AppWithV2Matcher = &Dependency{
	name: "app-with-matcher-v2",
	apply: func(t *testing.T, d *Dependency, c *TestContext) {
		templatePath := "./resources/app-with-matcher-v2.yml.tmpl"
		values := struct {
			PodImage string
			Secrets  []Secret
		}{
			PodImage: c.VSFImage,
			Secrets:  c.Secrets,
		}
		kubectlApplyWithTemplate(t, c.KubectlOptions, templatePath, values)
		k8s.WaitUntilPodAvailable(t, c.KubectlOptions, "app-with-matcher-v2", 10, 5*time.Second)
		d.isApplied = true
	},
}


<|start_filename|>main.go<|end_filename|>
package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
	"syscall"
)

const (
	namespacePath                     = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
	secretFetcherVersionName          = "FETCHER_FORMAT_VERSION"
	secretFetcherDebugMode = "FETCHER_DEBUG"
	PinnedCert = `-----BEGIN CERTIFICATE-----.-----END CERTIFICATE-----`
)

var (
	build string
	debugMode = os.Getenv(secretFetcherDebugMode) == "true"
)

type vaultSecretFetcherError struct {
	msg string
}

func (e vaultSecretFetcherError) Error() string {
	return fmt.Sprintf("%s", e.msg)
}

func SysExec() {
	flag.Parse()

	cmd, err := exec.LookPath(os.Args[1])
	if err != nil {
		log.Fatalf("Fatal error: SysExec() failed to locate the entrypoint '%s' - %s", os.Args[1], err)
	}

	if err := syscall.Exec(cmd, flag.Args(), os.Environ()); err != nil {
		log.Fatalf("Fatal error: SysExec() failed to perform execv syscall - %s", err)
	}
}

func GetNamespace() string {
	namespaceFile, err := ioutil.ReadFile(namespacePath)
	if err != nil {
		log.Fatalf("ERROR: GetNamespace() failed to read namespace from %s", namespacePath)
	}

	namespace := strings.TrimSpace(string(namespaceFile))
	if len(namespace) == 0 {
		log.Fatalf("ERROR: Namespace value in %s is empty", namespacePath)
	}

	return namespace
}

func GetenvSafe(key string, strict bool) string {
	value := os.Getenv(key)
	if len(value) == 0 {
		if strict {
			log.Fatalf("ERROR: GetenvSafe() failed to load '%s'. It's either empty or not set", key)
		}
		if debugMode {
			log.Printf("INFO: GetenvSafe() failed to load '%s'. It's either empty or not set", key)
		}
		return ""
	}
	return value
}

func GetFormatVersion() string {
	var value string
	var ok bool

	if value, ok = os.LookupEnv(secretFetcherVersionName); ok {
		switch value {
		case SecretFormatV1, SecretFormatV2:
			return value
		default:
			return value
		}
	}
	return SecretFormatV1
}

func Auth() {
	client := NewVaultClient()
	log.Printf("INFO: authenticating with endpoint '%s' using role %s", client.authURL, client.role)

	resp, err := client.auth()
	if err != nil {
		log.Printf("%s", err)
		log.Print(client.LogString())
		os.Exit(1)
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			log.Printf("WARN: error closing auth request response body: %s\n", err.Error())
		}
	}()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("ERROR: Auth() failed reading auth request response body: %s\n", err.Error())
	}
	if resp.StatusCode != 200 {
		log.Fatalf("ERROR: Auth() failed to authenticate with Vault - Response code: %d - %s", resp.StatusCode, body)
	}

	var responseInterface map[string]interface{}
	err = json.Unmarshal(body, &responseInterface)
	if err != nil {
		log.Fatalf("ERROR: Auth() failed to unmarshal JSON response: %s\n", err.Error())
	}
	token := responseInterface["auth"].(map[string]interface{})["client_token"]
	if tokenStr, ok := token.(string); ok {
		client.SetToken(tokenStr)
		return
	}

	log.Fatal("Fatal error: Auth() failed to read token from authentication JSON response")
}

func FetchSecret(secret Secret) error {
	var resp VaultReadResponse
	var secretStr string
	var err error

	client := NewVaultClient()

	if resp, err = client.readSecret(secret.GetPath()); err != nil {
		return err
	}

	if secretStr, err = resp.GetSecret(secret.GetKey()); err != nil {
		message := fmt.Sprintf("error extracting secret [%s] from response: %s", secret.GetKey(), err.Error())
		return errors.New(message)
	}
	secret.SetValue(secretStr)
	return nil
}

func SetSecretToEnvVar(varName, value string) error {
	return os.Setenv(varName, value)
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "%s /path/to/entrypoint\n\n", os.Args[0])
		flag.PrintDefaults()
	}

	versionFlagPointer := flag.Bool("v", false, "Print version number")
	flag.Parse()

	if *versionFlagPointer {
		fmt.Printf("Vault Secret Fetcher - revision: %s \n", build)
		os.Exit(0)
	}

	log.Printf("INFO: Vault Secret Fetcher started (revision: %s)", build)
	secretsFetched := 0

	if len(os.Args) == 1 {
		log.Fatal("ERROR: Missing entrypoint argument")
	}

	token := os.Getenv("VAULT_TOKEN")
	if len(token) == 0 {
		Auth()
	} else {
		log.Println("INFO: A VAULT_TOKEN has been provided. Will skip authentication and use the provided token")
	}

	secretFormat := GetFormatVersion()
	matcher := NewMatcher(secretFormat)
	for _, e := range os.Environ() {
		var secret Secret
		var err error

		if secret, err = matcher.Match(e); err != nil {
			switch err.(type) {
			case NoMatchError:
				if debugMode {
					log.Printf("DEBUG: %s", err.Error())
				}
				continue
			default:
				log.Fatalf("ERROR: %s", err.Error())
			}
		}
		if err := FetchSecret(secret); err != nil {
			log.Fatalf("ERROR: Failed to retrieve secret from %s::%s: %s", secret.GetPath(), secret.GetKey(), err.Error())
		}
		secretsFetched = secretsFetched + 1
		if err := SetSecretToEnvVar(secret.VarName(), secret.GetValue()); err != nil {
			message := fmt.Sprintf("ERROR: Failed to set %s in environment: %s", secret.VarName(), err.Error())
			log.Fatalf(message)
		}
	}

	log.Printf("INFO: Secrets fetched: %d/%d", secretsFetched, matcher.ToFetch())

	if secretsFetched != matcher.ToFetch() {
		log.Fatal("ERROR: Was not able to successfully fetch/set all secrets. Failing deployment")
	}

	// Equivalent to 'exec $@'.
	SysExec()
}


<|start_filename|>client.go<|end_filename|>
package main

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/sethgrid/pester"
)

func newPesterClient(httpClient *http.Client) *pester.Client {
	pesterClient := pester.NewExtendedClient(httpClient)
	pesterClient.Concurrency = 1
	pesterClient.MaxRetries = 5
	pesterClient.Backoff = pester.ExponentialBackoff
	pesterClient.KeepLog = true
	return pesterClient
}

func PrepareHTTPSClient() *pester.Client {
	sysCertStoreFailed := false
	embeddedCertFailed := false

	certPool, err := x509.SystemCertPool()
	if err != nil || certPool == nil {
		sysCertStoreFailed = true
		certPool = x509.NewCertPool()
		log.Println("INFO: PrepareHTTPClient() failed to get the system's cert store; using embedded cert")
	}
	ok := certPool.AppendCertsFromPEM([]byte(PinnedCert))
	if !ok {
		embeddedCertFailed = false
		log.Println("INFO: PrepareHTTPClient() failed to parse the embedded cert; using system cert store")
	}

	if sysCertStoreFailed && embeddedCertFailed {
		log.Fatal("ERROR: Both embedded cert and system cert store failed.")
	}

	tlsConfig := &tls.Config{
		RootCAs: certPool,
	}
	tlsConfig.BuildNameToCertificate()
	transport := &http.Transport{TLSClientConfig: tlsConfig}
	client := &http.Client{Transport: transport}

	return newPesterClient(client)
}

type VaultV1Data map[string]string

type VaultV2Data struct {
	Data VaultV1Data `json:"data"`
}

type VaultReadResponse interface {
	GetSecret(string) (string, error)
}

type VaultBaseResponse struct {
	Warnings      []string `json:"warnings" yaml:"warnings" `
	Errors        []string `json:"errors" yaml:"errors" `
	RequestID     string   `json:"request_id" yaml:"request_id" `
	LeaseID       string   `json:"lease_id" yaml:"lease_id" `
	Renewable     bool     `json:"renewable" yaml:"renewable" `
	LeaseDuration int      `json:"lease_duration" yaml:"lease_duration" `
}

type VaultV1Response struct {
	VaultBaseResponse
	Data VaultV1Data `json:"data" yaml:"data" `
}

func NewVaultV1Response(data []byte) (VaultReadResponse, error) {
	var resp VaultV1Response

	if err := json.Unmarshal(data, &resp); err != nil {
		return nil, err
	}
	return resp, nil
}

func (r VaultV1Response) GetSecret(key string) (string, error) {
	var value string
	var ok bool

	if value, ok = r.Data[key]; !ok {
		return "", errors.New(fmt.Sprintf("no value for key: %s", key))
	}
	return value, nil
}

type VaultV2Response struct {
	VaultBaseResponse
	Data VaultV2Data `json:"data" yaml:"data" `
}

func NewVaultV2Response(data []byte) (VaultReadResponse, error) {
	var resp VaultV2Response

	if err := json.Unmarshal(data, &resp); err != nil {
		return nil, err
	}
	return resp, nil
}

func (r VaultV2Response) GetSecret(key string) (string, error) {
	var value string
	var ok bool

	if value, ok = r.Data.Data[key]; !ok {
		return "", errors.New(fmt.Sprintf("no value for key: %s", key))
	}
	return value, nil
}

type VaultClient struct {
	vaultAddress   string
	cluster        string
	jwt            string
	authURL        string
	namespace      string
	role           string
	client         *pester.Client
	token          string
	BackendVersion string
}

var vaultClient *VaultClient

func NewVaultClient() *VaultClient {
	if vaultClient == nil {
		vaultAddress := GetenvSafe("VAULT_ADDR", true)
		cluster := GetenvSafe("KUBERNETES_CLUSTER", true)
		jwt := GetenvSafe("VAULT_SERVICE_ACCOUNT_JWT", true)
		authURL := fmt.Sprintf("%s/v1/auth/kubernetes-%s/login", vaultAddress, cluster)
		namespace := GetNamespace()
		role := fmt.Sprintf("%s-vault-sa", namespace)
		client := PrepareHTTPSClient()
		vaultBackendVersion := GetenvSafe("VAULT_KV_VERSION", false)
		token := GetenvSafe("VAULT_TOKEN", false)

		vaultClient = &VaultClient{
			vaultAddress:   vaultAddress,
			cluster:        cluster,
			jwt:            jwt,
			authURL:        authURL,
			namespace:      namespace,
			role:           role,
			client:         client,
			BackendVersion: vaultBackendVersion,
			token:          token,
		}
	}
	return vaultClient
}

func (vc VaultClient) payloadJSON() []byte {
	if payload, err := json.Marshal(map[string]string{"jwt": vc.jwt, "role": vc.role}); err != nil {
		log.Fatalf("error creating vault JSON payload: %s", err.Error())
	} else {
		return payload
	}
	return nil
}

func (vc VaultClient) newAuthRequest() *http.Request {
	var req *http.Request
	var err error

	if req, err = http.NewRequest(http.MethodPost, vc.authURL, bytes.NewBuffer(vc.payloadJSON())); err != nil {
		log.Fatalf("error creating vault auth request: %s", err.Error())
	}
	req.Header.Set("Content-Type", "application/json")
	return req
}

func (vc VaultClient) newReadSecretRequest(secretPath string) *http.Request {
	var req *http.Request
	var err error

	secretURL := fmt.Sprintf("%s/v1/%s", vc.vaultAddress, secretPath)
	if req, err = http.NewRequest("GET", secretURL, nil); err != nil {
		log.Fatalf("error creating vault read request: %s", err.Error())
	}
	req.Header.Set("X-Vault-Token", vc.token)
	return req
}

func (vc VaultClient) auth() (*http.Response, error) {
	req := vc.newAuthRequest()
	return vc.client.Do(req)
}

func (vc VaultClient) readSecret(secretPath string) (VaultReadResponse, error) {
	req := vc.newReadSecretRequest(secretPath)
	if resp, err := vc.client.Do(req); err != nil {
		return nil, err
	} else {
		body, err := ioutil.ReadAll(resp.Body)
		defer func() {
			if err := resp.Body.Close(); err != nil {
				log.Printf("WARN: error closing read request response body: %s\n", err.Error())
			}
		}()
		if err != nil {
			return nil, errors.New(fmt.Sprintf("FetchSecret() failed reading auth request response body: %s\n", err.Error()))
		}
		if resp.StatusCode != 200 {
			return nil, vaultSecretFetcherError{fmt.Sprintf("FetchSecret() failed to fetch '%s' - Response code: %d", secretPath, resp.StatusCode)}
		}
		if vc.BackendVersion == "" || vc.BackendVersion == "1" {
			return NewVaultV1Response([]byte(body))
		} else if vc.BackendVersion == "2" {
			return NewVaultV2Response([]byte(body))
		} else {
			return nil, errors.New(fmt.Sprintf("received invalid kv version of: %s", vc.BackendVersion))
		}
	}
}

func (vc VaultClient) LogString() string {
	return vc.client.LogString()
}

func (vc *VaultClient) SetToken(token string) {
	vc.token = token
}


<|start_filename|>e2e/helpers.go<|end_filename|>
package e2e

import (
	"bytes"
	"os"
	"path/filepath"
	"testing"
	"text/template"

	"github.com/gruntwork-io/terratest/modules/k8s"
)

func kubectlApplyWithTemplate(t *testing.T, kubectlOptions *k8s.KubectlOptions, templatePath string, values interface{}) {
	config, err := renderTemplateToString(t, templatePath, values)
	if err != nil {
		t.Error(err)
	}
	k8s.KubectlApplyFromString(t, kubectlOptions, config)
}

func renderTemplateToString(t *testing.T, f string, data interface{}) (string, error) {
	tmpl, err := template.New("").ParseFiles(f)
	baseName := filepath.Base(f)
	if err != nil {
		t.Error(err)
	}
	config := bytes.NewBufferString("")
	err = tmpl.ExecuteTemplate(config, baseName, data)
	return config.String(), err
}

func readEnvWithDefault(name, defaultValue string) string {
	value := os.Getenv(name)
	if value == "" {
		return defaultValue
	}
	return value
}


<|start_filename|>e2e/Dockerfile<|end_filename|>
FROM golang:1.15-alpine

WORKDIR /src

RUN apk --no-cache add curl docker-cli gcc && \
    curl -L -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v1.19.0/bin/linux/amd64/kubectl && \
    chmod +x /usr/local/bin/kubectl
COPY . .



<|start_filename|>Dockerfile<|end_filename|>
FROM golang:1.15.2 as builder
WORKDIR /go/src/vault-secret-fetcher
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -a -ldflags "-X main.build=$(git rev-parse --short HEAD) -extldflags '-static' -s -w" -v -o ./vault-secret-fetcher

FROM alpine:latest
WORKDIR /root/

COPY --from=builder /go/src/vault-secret-fetcher .

# Make the binary readable for any user. Otherwise pods that are not running a non-root security context are unable to initialize.
RUN chmod +x /root
RUN chmod 755 /root/vault-secret-fetcher


<|start_filename|>e2e/e2e_test.go<|end_filename|>
package e2e

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"os"
	"strings"
	"testing"

	"github.com/gruntwork-io/terratest/modules/k8s"
)

var testContext = &TestContext{}

var (
	defaultVaultImage = "vault:1.6.0"
	defaultKindImage  = "kindest/node:v1.20.0"
)

var secrets = []Secret{
	{Path: "secrets/a", Key: "secret", Value: "valueA", Env: "SECRET_A", Version: 1},
	{Path: "secrets/b", Key: "secret", Value: "valueB", Env: "SECRET_B", Version: 1},
	{Path: "secrets/c", Key: "secret", Value: "valueC", Env: "SECRET_C", Version: 1},
	{Path: "secrets/d", Key: "secret", Value: "valueD", Env: "SECRET_D", Version: 2},
	{Path: "secrets/e", Key: "key1", Value: "valueE", Env: "SECRET_E", Version: 2},
	{Path: "secrets/f", Key: "key2", Value: "valueF", Env: "SECRET_F", Version: 2},
}

type Config struct {
	VSFImage   string
	VaultImage string
	KindImage  string
}

func (c *Config) String() string {
	w := bytes.NewBufferString("")
	json.NewEncoder(w).Encode(c)
	return w.String()
}

func initConfig() (*Config, error) {
	vsfImage := readEnvWithDefault("E2E_VAULT_SECRET_FETCHER_IMAGE", "")
	if vsfImage == "" {
		return nil, errors.New("environment variable VAULT_SECRET_FETCHER_IMAGE is empty")
	}

	vaultImage := readEnvWithDefault("E2E_VAULT_IMAGE", defaultVaultImage)
	kindImage := readEnvWithDefault("E2E_K8S_KIND_IMAGE", defaultKindImage)

	return &Config{
		VSFImage: vsfImage, VaultImage: vaultImage, KindImage: kindImage,
	}, nil
}

func TestMain(m *testing.M) {
	config, err := initConfig()
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Running e2e test for vault-secret-fetcher with config: %s", config)
	tContext, err := testContext.setup(config.VSFImage, config.VaultImage, config.KindImage, secrets)
	if err != nil {
		log.Fatal(err)
	}
	testContext = tContext
	exitCode := m.Run()
	err = testContext.teardown()
	if err != nil {
		log.Println(err)
	}
	os.Exit(exitCode)
}

func TestVSFMatcherV1(t *testing.T) {
	testContext.withDependencies(t, Vault, AppWithV1Matcher)
	stdout, err := k8s.RunKubectlAndGetOutputE(t, testContext.KubectlOptions, "exec", "app-with-matcher-v1", "--", "/bin/sh", "-c", "cat /tmp/env")
	if err != nil {
		t.Error(err)
	}
	for _, tt := range testContext.Secrets {
		if tt.Version != 1 {
			continue
		}
		t.Run(tt.Path, func(t *testing.T) {
			want := fmt.Sprintf("%s=%s", tt.Env, tt.Value)
			if !strings.Contains(stdout, want) {
				t.Errorf("MatcherV1 = %v, want %v", stdout, want)
			}
		})
	}
}

func TestVSFMatcherV2(t *testing.T) {
	testContext.withDependencies(t, Vault, AppWithV2Matcher)
	stdout, err := k8s.RunKubectlAndGetOutputE(t, testContext.KubectlOptions, "exec", "app-with-matcher-v2", "--", "/bin/sh", "-c", "cat /tmp/env")
	if err != nil {
		t.Error(err)
	}
	for _, tt := range testContext.Secrets {
		if tt.Version != 2 {
			continue
		}
		t.Run(tt.Path, func(t *testing.T) {
			want := fmt.Sprintf("%s=%s", tt.Env, tt.Value)
			if !strings.Contains(stdout, want) {
				t.Errorf("MatcherV2 = %v, want %v", stdout, want)
			}
		})
	}
}


<|start_filename|>e2e/kind.go<|end_filename|>
package e2e

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/gruntwork-io/terratest/modules/k8s"
	"github.com/gruntwork-io/terratest/modules/random"
	"sigs.k8s.io/kind/pkg/cluster"
	"sigs.k8s.io/kind/pkg/cluster/nodeutils"
	"sigs.k8s.io/kind/pkg/fs"
)

type KindCluster struct {
	Name     string
	Provider *cluster.Provider
}

func NewKindCluster(nodeImage string) (*KindCluster, error) {
	clusterName := fmt.Sprintf("cluster-%s", strings.ToLower(random.UniqueId()))
	f, err := ioutil.TempFile("", "kubecfg-")
	if err != nil {
		return nil, err
	}
	os.Setenv("KUBECONFIG", f.Name())
	provider := cluster.NewProvider()
	err = provider.Create(
		clusterName,
		cluster.CreateWithNodeImage(nodeImage),
		cluster.CreateWithWaitForReady(120*time.Second),
	)
	if err != nil {
		return nil, err
	}
	s, err := provider.KubeConfig(clusterName, true)
	f.Write([]byte(s))
	return &KindCluster{Provider: provider, Name: clusterName}, nil
}

func (c *KindCluster) GetK8SOptions() *k8s.KubectlOptions {
	return k8s.NewKubectlOptions("kind-"+c.Name, "", "default")
}

func (c *KindCluster) Delete() error {
	return c.Provider.Delete(c.Name, "")
}

func (c *KindCluster) LoadImage(image string) error {
	dir, err := fs.TempDir("", "image-tar")
	defer os.RemoveAll(dir)
	imageTarName := filepath.Join(dir, "image.tar")
	err = c.saveImage(image, imageTarName)
	if err != nil {
		return err
	}
	f, err := os.Open(imageTarName)
	if err != nil {
		return fmt.Errorf("failed to open image %s", image)
	}
	defer f.Close()
	nodes, err := c.Provider.ListNodes(c.Name)
	if err != nil {
		return err
	}
	err = nil
	for _, node := range nodes {
		err = nodeutils.LoadImageArchive(node, f)
	}
	return err
}

func (c *KindCluster) saveImage(image, dest string) error {
	return exec.Command("docker", "save", "-o", dest, image).Run()
}

func (c *KindCluster) LoadImages(images ...string) error {
	var err error
	for _, image := range images {
		err = c.LoadImage(image)
	}
	return err
}


<|start_filename|>matcher.go<|end_filename|>
package main

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"
)

const (
	SecretFormatV1       = "1"
	SecretFormatV2       = "2"
	v1CheckPattern       = "{{[\\s]*vault-secret ([a-zA-Z0-9_-]+\\/[a-zA-Z0-9\\/_-]+)[\\s]*}}$"
	v1VaultSecretPattern = "{{[\\s]*vault-secret "
	v1Key                = "secret"
	v2SecretPattern      = `(?P<prefix>VAULTSECRET::)(?P<info>\{.*\})`
)

// SecretFormatError is a custom error type.
type SecretFormatError struct {
	Message string
}

func (e SecretFormatError) Error() string {
	return e.Message
}

// NewSecretFormatError creates a new `SecretFormatError`.
func NewSecretFormatError(message string) SecretFormatError {
	return SecretFormatError{Message: message}
}

// NoMatchError is a custom error type.
type NoMatchError struct {
	Message string
}

func (e NoMatchError) Error() string {
	return e.Message
}

// NewNoMatchError creates a new `NoMatchError`.
func NewNoMatchError(varName string, matcherVersion string) NoMatchError {
	message := fmt.Sprintf("'%s' does not match a v%s SecretMatcher secret", varName, matcherVersion)
	return NoMatchError{Message: message}
}

func SecretPrinter(s Secret) string {
	return fmt.Sprintf("<v%sSecret: %s:%s>", s.Version(), s.GetPath(), s.GetKey())
}

type Secret interface {
	GetPath() string
	GetKey() string
	VarName() string
	SetValue(string)
	GetValue() string
	Version() string
	// Including Stringer interface so developers MUST implement a method for
	// printing objects that *DOES NOT* print the secret value.
	fmt.Stringer
}

type SecretMatcher interface {
	Match(string) (Secret, error)
	ToFetch() int
	Version() string
}

type v1Secret struct {
	path    string
	key     string
	varName string
	value   string
	version string
}

func (s v1Secret) GetPath() string {
	return s.path
}

func (s v1Secret) GetKey() string {
	return s.key
}

func (s v1Secret) VarName() string {
	return s.varName
}

func (s *v1Secret) SetValue(value string) {
	s.value = value
}

func (s v1Secret) GetValue() string {
	return s.value
}

func (s v1Secret) Version() string {
	return s.version
}

func (s *v1Secret) String() string {
	return SecretPrinter(s)
}

func newV1Secret(varName, path string) *v1Secret {
	return &v1Secret{path: path, key: v1Key, varName: varName, version: SecretFormatV1}
}

type V1Matcher struct {
	checkRegex       *regexp.Regexp
	vaultSecretRegex *regexp.Regexp
	version          string
	toFetch          int
}

func NewV1Matcher() *V1Matcher {
	return &V1Matcher{
		version:          SecretFormatV1,
		checkRegex:       regexp.MustCompile(v1CheckPattern),
		vaultSecretRegex: regexp.MustCompile(v1VaultSecretPattern),
	}
}

func (m *V1Matcher) Match(str string) (Secret, error) {
	envVarLine := strings.Split(str, "=")
	if m.vaultSecretRegex.MatchString(envVarLine[1]) {
		m.toFetch++
		if m.checkRegex.MatchString(envVarLine[1]) {
			extracted := m.checkRegex.FindStringSubmatch(envVarLine[1])
			return newV1Secret(envVarLine[0], extracted[1]), nil
		}
		message := fmt.Sprintf(
			"'%s' does not follow the correct path definition format.",
			envVarLine[1],
		)
		return nil, NewSecretFormatError(message)
	}
	return nil, NewNoMatchError(envVarLine[0], m.version)
}

func (m *V1Matcher) ToFetch() int {
	return m.toFetch
}

func (m V1Matcher) Version() string {
	return m.version
}

type v2Secret struct {
	Path    string `json:"path"`
	Key     string `json:"key"`
	varName string
	value   string
	version string
}

func newV2Secret(varName string, data []byte) (*v2Secret, error) {
	secret := v2Secret{varName: varName, version: SecretFormatV2}
	if err := json.Unmarshal(data, &secret); err != nil {
		message := fmt.Sprintf(
			"failed to parse SecretFormatV2 secret from '%s': %s",
			string(data),
			err.Error(),
		)
		return nil, NewSecretFormatError(message)
	}
	return &secret, nil
}

func (s v2Secret) GetPath() string {
	return s.Path
}

func (s v2Secret) GetKey() string {
	return s.Key
}

func (s v2Secret) VarName() string {
	return s.varName
}

func (s *v2Secret) SetValue(value string) {
	s.value = value
}

func (s v2Secret) GetValue() string {
	return s.value
}

func (s v2Secret) Version() string {
	return s.version
}

func (s *v2Secret) String() string {
	return SecretPrinter(s)
}

type V2Matcher struct {
	secretRegex *regexp.Regexp
	version     string
	toFetch     int
}

func NewV2Matcher() *V2Matcher {
	return &V2Matcher{
		version:     SecretFormatV2,
		secretRegex: regexp.MustCompile(v2SecretPattern),
	}
}

func (m *V2Matcher) Match(str string) (Secret, error) {
	envVarLine := strings.Split(str, "=")
	if m.secretRegex.MatchString(envVarLine[1]) {
		m.toFetch++
		js := strings.Split(envVarLine[1], "::")[1]
		return newV2Secret(envVarLine[0], []byte(js))
	}
	return nil, NewNoMatchError(envVarLine[0], m.version)
}

func (m V2Matcher) ToFetch() int {
	return m.toFetch
}

func (m V2Matcher) Version() string {
	return m.version
}

func NewMatcher(version string) SecretMatcher {
	switch version {
	case SecretFormatV1:
		return NewV1Matcher()
	case SecretFormatV2:
		return NewV2Matcher()
	default:
		return NewV1Matcher()
	}
}
