Skip to content

Commit

Permalink
Update functionalities in k8s module to take in options
Browse files Browse the repository at this point in the history
  • Loading branch information
yorinasub17 committed Dec 12, 2018
1 parent c7853c2 commit 4f285bf
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 22 deletions.
22 changes: 17 additions & 5 deletions modules/k8s/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,26 @@ func GetKubernetesClientE(t *testing.T) (*kubernetes.Clientset, error) {
return nil, err
}

return GetKubernetesClientFromFileE(t, kubeConfigPath, "")
options := NewKubectlOptions("", kubeConfigPath)
return GetKubernetesClientFromOptionsE(t, options)
}

// GetKubernetesClientFromFileE returns a Kubernetes API client given the kubernetes config file path.
func GetKubernetesClientFromFileE(t *testing.T, kubeConfigPath string, contextName string) (*kubernetes.Clientset, error) {
logger.Logf(t, "Configuring kubectl using config file %s with context %s", kubeConfigPath, contextName)
// GetKubernetesClientFromOptionsE returns a Kubernetes API client given a configured KubectlOptions object.
func GetKubernetesClientFromOptionsE(t *testing.T, options *KubectlOptions) (*kubernetes.Clientset, error) {
var err error

// We have to give an actual configpath for LoadApiClientConfigE to work
kubeConfigPath := options.ConfigPath
if kubeConfigPath == "" {
kubeConfigPath, err = GetKubeConfigPathE(t)
if err != nil {
return nil, err
}
}

logger.Logf(t, "Configuring kubectl using config file %s with context %s", kubeConfigPath, options.ContextName)
// Load API config (instead of more low level ClientConfig)
config, err := LoadApiClientConfigE(kubeConfigPath, contextName)
config, err := LoadApiClientConfigE(kubeConfigPath, options.ContextName)
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions modules/k8s/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ func LoadConfigFromPath(path string) clientcmd.ClientConfig {

// LoadApiClientConfig will load a ClientConfig object from a file path that points to a location on disk containing a
// kubectl config, with the requested context loaded.
func LoadApiClientConfigE(path string, context string) (*restclient.Config, error) {
func LoadApiClientConfigE(configPath string, contextName string) (*restclient.Config, error) {
overrides := clientcmd.ConfigOverrides{}
if context != "" {
overrides.CurrentContext = context
if contextName != "" {
overrides.CurrentContext = contextName
}
config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: path},
&clientcmd.ClientConfigLoadingRules{ExplicitPath: configPath},
&overrides)
return config.ClientConfig()
}
Expand Down
29 changes: 27 additions & 2 deletions modules/k8s/kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func RunKubectlAndGetOutputE(t *testing.T, options *KubectlOptions, args ...stri
if options.ConfigPath != "" {
cmdArgs = append(cmdArgs, "--kubeconfig", options.ConfigPath)
}
if options.Namespace != "" {
cmdArgs = append(cmdArgs, "--namespace", options.Namespace)
}
cmdArgs = append(cmdArgs, args...)
command := shell.Command{
Command: "kubectl",
Expand All @@ -41,6 +44,17 @@ func RunKubectlAndGetOutputE(t *testing.T, options *KubectlOptions, args ...stri
return shell.RunCommandAndGetOutputE(t, command)
}

// KubectlDelete will take in a file path and delete it from the cluster targeted by KubectlOptions. If there are any
// errors, fail the test immediately.
func KubectlDelete(t *testing.T, options *KubectlOptions, configPath string) {
require.NoError(t, KubectlDeleteE(t, options, configPath))
}

// KubectlDeleteE will take in a file path and delete it from the cluster targeted by KubectlOptions.
func KubectlDeleteE(t *testing.T, options *KubectlOptions, configPath string) error {
return RunKubectlE(t, options, "delete", "-f", configPath)
}

// KubectlDeleteFromString will take in a kubernetes resource config as a string and delete it on the cluster specified
// by the provided kubectl options.
func KubectlDeleteFromString(t *testing.T, options *KubectlOptions, configData string) {
Expand All @@ -55,7 +69,18 @@ func KubectlDeleteFromStringE(t *testing.T, options *KubectlOptions, configData
return err
}
defer os.Remove(tmpfile)
return RunKubectlE(t, options, "delete", "-f", tmpfile)
return KubectlDeleteE(t, options, tmpfile)
}

// KubectlApply will take in a file path and apply it to the cluster targeted by KubectlOptions. If there are any
// errors, fail the test immediately.
func KubectlApply(t *testing.T, options *KubectlOptions, configPath string) {
require.NoError(t, KubectlApplyE(t, options, configPath))
}

// KubectlApplyE will take in a file path and apply it to the cluster targeted by KubectlOptions.
func KubectlApplyE(t *testing.T, options *KubectlOptions, configPath string) error {
return RunKubectlE(t, options, "apply", "-f", configPath)
}

// KubectlApplyFromString will take in a kubernetes resource config as a string and apply it on the cluster specified
Expand All @@ -72,7 +97,7 @@ func KubectlApplyFromStringE(t *testing.T, options *KubectlOptions, configData s
return err
}
defer os.Remove(tmpfile)
return RunKubectlE(t, options, "apply", "-f", tmpfile)
return KubectlApplyE(t, options, tmpfile)
}

// StoreConfigToTempFile will store the provided config data to a temporary file created on the os and return the
Expand Down
1 change: 1 addition & 0 deletions modules/k8s/kubectl_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package k8s
type KubectlOptions struct {
ContextName string
ConfigPath string
Namespace string
Env map[string]string
}

Expand Down
66 changes: 66 additions & 0 deletions modules/k8s/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package k8s

import (
"testing"

"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// CreateNamespace will create a new Kubernetes namespace on the cluster targeted by the provided options. This will
// fail the test if there is an error in creating the namespace.
func CreateNamespace(t *testing.T, options *KubectlOptions, namespaceName string) {
require.NoError(t, CreateNamespaceE(t, options, namespaceName))
}

// CreateNamespaceE will create a new Kubernetes namespace on the cluster targeted by the provided options.
func CreateNamespaceE(t *testing.T, options *KubectlOptions, namespaceName string) error {
clientset, err := GetKubernetesClientFromOptionsE(t, options)
if err != nil {
return err
}

namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespaceName,
},
}
_, err = clientset.CoreV1().Namespaces().Create(&namespace)
return err
}

// GetNamespace will query the Kubernetes cluster targeted by the provided options for the requested namespace. This will
// fail the test if there is an error in creating the namespace.
func GetNamespace(t *testing.T, options *KubectlOptions, namespaceName string) corev1.Namespace {
namespace, err := GetNamespaceE(t, options, namespaceName)
require.NoError(t, err)
return namespace
}

// GetNamespaceE will query the Kubernetes cluster targeted by the provided options for the requested namespace.
func GetNamespaceE(t *testing.T, options *KubectlOptions, namespaceName string) (corev1.Namespace, error) {
clientset, err := GetKubernetesClientFromOptionsE(t, options)
if err != nil {
return corev1.Namespace{}, err
}

namespace, err := clientset.CoreV1().Namespaces().Get(namespaceName, metav1.GetOptions{})
return *namespace, err
}

// DeleteNamespace will delete the requested namespace from the Kubernetes cluster targeted by the provided options. This will
// fail the test if there is an error in creating the namespace.
func DeleteNamespace(t *testing.T, options *KubectlOptions, namespaceName string) {
require.NoError(t, DeleteNamespaceE(t, options, namespaceName))
}

// DeleteNamespaceE will delete the requested namespace from the Kubernetes cluster targeted by the provided options.
func DeleteNamespaceE(t *testing.T, options *KubectlOptions, namespaceName string) error {
clientset, err := GetKubernetesClientFromOptionsE(t, options)
if err != nil {
return err
}

return clientset.CoreV1().Namespaces().Delete(namespaceName, &metav1.DeleteOptions{})
}
28 changes: 28 additions & 0 deletions modules/k8s/namespace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package k8s

import (
"strings"
"testing"

"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"

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

func TestNamespaces(t *testing.T) {
t.Parallel()

uniqueId := random.UniqueId()
namespaceName := strings.ToLower(uniqueId)
options := NewKubectlOptions("", "")
CreateNamespace(t, options, namespaceName)
defer func() {
DeleteNamespace(t, options, namespaceName)
namespace := GetNamespace(t, options, namespaceName)
require.Equal(t, namespace.Status.Phase, corev1.NamespaceTerminating)
}()

namespace := GetNamespace(t, options, namespaceName)
require.Equal(t, namespace.Name, namespaceName)
}
14 changes: 7 additions & 7 deletions modules/k8s/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,31 @@ import (

// GetService returns a Kubernetes service resource in the provided namespace with the given name. This will
// fail the test if there is an error.
func GetService(t *testing.T, namespace string, serviceName string) *corev1.Service {
service, err := GetServiceE(t, namespace, serviceName)
func GetService(t *testing.T, options *KubectlOptions, serviceName string) *corev1.Service {
service, err := GetServiceE(t, options, serviceName)
require.NoError(t, err)
return service
}

// GetServiceE returns a Kubernetes service resource in the provided namespace with the given name.
func GetServiceE(t *testing.T, namespace string, serviceName string) (*corev1.Service, error) {
clientset, err := GetKubernetesClientE(t)
func GetServiceE(t *testing.T, options *KubectlOptions, serviceName string) (*corev1.Service, error) {
clientset, err := GetKubernetesClientFromOptionsE(t, options)
if err != nil {
return nil, err
}
return clientset.CoreV1().Services(namespace).Get(serviceName, metav1.GetOptions{})
return clientset.CoreV1().Services(options.Namespace).Get(serviceName, metav1.GetOptions{})
}

// WaitUntilServiceAvailable waits until the service endpoint is ready to accept traffic.
func WaitUntilServiceAvailable(t *testing.T, namespace string, serviceName string, retries int, sleepBetweenRetries time.Duration) {
func WaitUntilServiceAvailable(t *testing.T, options *KubectlOptions, serviceName string, retries int, sleepBetweenRetries time.Duration) {
statusMsg := fmt.Sprintf("Wait for service %s to be provisioned.", serviceName)
message := retry.DoWithRetry(
t,
statusMsg,
retries,
sleepBetweenRetries,
func() (string, error) {
service, err := GetServiceE(t, namespace, serviceName)
service, err := GetServiceE(t, options, serviceName)
if err != nil {
return "", err
}
Expand Down
12 changes: 8 additions & 4 deletions modules/k8s/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import (
func TestGetServiceEReturnsErrorForNonExistantService(t *testing.T) {
t.Parallel()

_, err := GetServiceE(t, "default", "nginx-service")
options := NewKubectlOptions("", "")
_, err := GetServiceE(t, options, "nginx-service")
require.Error(t, err)
}

Expand All @@ -24,11 +25,12 @@ func TestGetServiceEReturnsCorrectServiceInCorrectNamespace(t *testing.T) {

uniqueID := strings.ToLower(random.UniqueId())
options := NewKubectlOptions("", "")
options.Namespace = uniqueID
configData := fmt.Sprintf(EXAMPLE_DEPLOYMENT_YAML_TEMPLATE, uniqueID, uniqueID, uniqueID)
KubectlApplyFromString(t, options, configData)
defer KubectlDeleteFromString(t, options, configData)

service := GetService(t, uniqueID, "nginx-service")
service := GetService(t, options, "nginx-service")
require.Equal(t, service.Name, "nginx-service")
require.Equal(t, service.Namespace, uniqueID)
}
Expand All @@ -38,23 +40,25 @@ func TestWaitUntilServiceAvailableReturnsSuccessfullyOnNodePortType(t *testing.T

uniqueID := strings.ToLower(random.UniqueId())
options := NewKubectlOptions("", "")
options.Namespace = uniqueID
configData := fmt.Sprintf(EXAMPLE_DEPLOYMENT_YAML_TEMPLATE, uniqueID, uniqueID, uniqueID)
KubectlApplyFromString(t, options, configData)
defer KubectlDeleteFromString(t, options, configData)

WaitUntilServiceAvailable(t, uniqueID, "nginx-service", 10, 1*time.Second)
WaitUntilServiceAvailable(t, options, "nginx-service", 10, 1*time.Second)
}

func TestGetServiceEndpointEReturnsAccessibleEndpointForNodePort(t *testing.T) {
t.Parallel()

uniqueID := strings.ToLower(random.UniqueId())
options := NewKubectlOptions("", "")
options.Namespace = uniqueID
configData := fmt.Sprintf(EXAMPLE_DEPLOYMENT_YAML_TEMPLATE, uniqueID, uniqueID, uniqueID)
KubectlApplyFromString(t, options, configData)
defer KubectlDeleteFromString(t, options, configData)

service := GetService(t, uniqueID, "nginx-service")
service := GetService(t, options, "nginx-service")
endpoint := GetServiceEndpoint(t, service, 80)
// Test up to 5 minutes
http_helper.HttpGetWithRetryWithCustomValidation(
Expand Down

0 comments on commit 4f285bf

Please sign in to comment.