Files
Reloader/internal/pkg/testutil/testutil.go
T

356 lines
13 KiB
Go

package testutil
import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"time"
"github.com/stakater/Reloader/internal/pkg/config"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
)
const (
// ConfigmapResourceType represents ConfigMap resource type
ConfigmapResourceType = "configmap"
// SecretResourceType represents Secret resource type
SecretResourceType = "secret"
)
// CreateNamespace creates a namespace with the given name.
func CreateNamespace(name string, client kubernetes.Interface) error {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
_, err := client.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{})
return err
}
// DeleteNamespace deletes the namespace with the given name.
func DeleteNamespace(name string, client kubernetes.Interface) error {
return client.CoreV1().Namespaces().Delete(context.Background(), name, metav1.DeleteOptions{})
}
// CreateConfigMap creates a ConfigMap with the given name and data.
func CreateConfigMap(client kubernetes.Interface, namespace, name, data string) (*corev1.ConfigMap, error) {
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Data: map[string]string{
"url": data,
},
}
return client.CoreV1().ConfigMaps(namespace).Create(context.Background(), cm, metav1.CreateOptions{})
}
// UpdateConfigMap updates the ConfigMap with new label and/or data.
func UpdateConfigMap(cm *corev1.ConfigMap, namespace, name, label, data string) error {
if label != "" {
if cm.Labels == nil {
cm.Labels = make(map[string]string)
}
cm.Labels["test-label"] = label
}
if data != "" {
cm.Data["url"] = data
}
// Note: caller must have a client to update
return nil
}
// UpdateConfigMapWithClient updates the ConfigMap with new label and/or data.
func UpdateConfigMapWithClient(client kubernetes.Interface, namespace, name, label, data string) error {
ctx := context.Background()
cm, err := client.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return err
}
if label != "" {
if cm.Labels == nil {
cm.Labels = make(map[string]string)
}
cm.Labels["test-label"] = label
}
if data != "" {
cm.Data["url"] = data
}
_, err = client.CoreV1().ConfigMaps(namespace).Update(ctx, cm, metav1.UpdateOptions{})
return err
}
// DeleteConfigMap deletes the ConfigMap with the given name.
func DeleteConfigMap(client kubernetes.Interface, namespace, name string) error {
return client.CoreV1().ConfigMaps(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
}
// CreateSecret creates a Secret with the given name and data.
func CreateSecret(client kubernetes.Interface, namespace, name, data string) (*corev1.Secret, error) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Data: map[string][]byte{
"password": []byte(data),
},
}
return client.CoreV1().Secrets(namespace).Create(context.Background(), secret, metav1.CreateOptions{})
}
// UpdateSecretWithClient updates the Secret with new label and/or data.
func UpdateSecretWithClient(client kubernetes.Interface, namespace, name, label, data string) error {
ctx := context.Background()
secret, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return err
}
if label != "" {
if secret.Labels == nil {
secret.Labels = make(map[string]string)
}
secret.Labels["test-label"] = label
}
if data != "" {
secret.Data["password"] = []byte(data)
}
_, err = client.CoreV1().Secrets(namespace).Update(ctx, secret, metav1.UpdateOptions{})
return err
}
// DeleteSecret deletes the Secret with the given name.
func DeleteSecret(client kubernetes.Interface, namespace, name string) error {
return client.CoreV1().Secrets(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
}
// CreateDeployment creates a Deployment that references a ConfigMap/Secret.
func CreateDeployment(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (*appsv1.Deployment, error) {
var deployment *appsv1.Deployment
if useConfigMap {
deployment = NewDeploymentWithEnvFrom(name, namespace, name, "")
} else {
deployment = NewDeploymentWithEnvFrom(name, namespace, "", name)
}
deployment.Annotations = annotations
// Override image for integration tests
deployment.Spec.Template.Spec.Containers[0].Image = "busybox:1.36"
deployment.Spec.Template.Spec.Containers[0].Command = []string{"sh", "-c", "while true; do sleep 3600; done"}
return client.AppsV1().Deployments(namespace).Create(context.Background(), deployment, metav1.CreateOptions{})
}
// DeleteDeployment deletes the Deployment with the given name.
func DeleteDeployment(client kubernetes.Interface, namespace, name string) error {
return client.AppsV1().Deployments(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
}
// CreateDaemonSet creates a DaemonSet that references a ConfigMap/Secret.
func CreateDaemonSet(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (*appsv1.DaemonSet, error) {
daemonset := NewDaemonSet(name, namespace, annotations)
// Override image for integration tests
daemonset.Spec.Template.Spec.Containers[0].Image = "busybox:1.36"
daemonset.Spec.Template.Spec.Containers[0].Command = []string{"sh", "-c", "while true; do sleep 3600; done"}
if useConfigMap {
daemonset.Spec.Template.Spec.Containers[0].EnvFrom = []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: name},
},
},
}
} else {
daemonset.Spec.Template.Spec.Containers[0].EnvFrom = []corev1.EnvFromSource{
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: name},
},
},
}
}
return client.AppsV1().DaemonSets(namespace).Create(context.Background(), daemonset, metav1.CreateOptions{})
}
// DeleteDaemonSet deletes the DaemonSet with the given name.
func DeleteDaemonSet(client kubernetes.Interface, namespace, name string) error {
return client.AppsV1().DaemonSets(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
}
// CreateStatefulSet creates a StatefulSet that references a ConfigMap/Secret.
func CreateStatefulSet(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (*appsv1.StatefulSet, error) {
statefulset := NewStatefulSet(name, namespace, annotations)
statefulset.Spec.ServiceName = name
// Override image for integration tests
statefulset.Spec.Template.Spec.Containers[0].Image = "busybox:1.36"
statefulset.Spec.Template.Spec.Containers[0].Command = []string{"sh", "-c", "while true; do sleep 3600; done"}
if useConfigMap {
statefulset.Spec.Template.Spec.Containers[0].EnvFrom = []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: name},
},
},
}
} else {
statefulset.Spec.Template.Spec.Containers[0].EnvFrom = []corev1.EnvFromSource{
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: name},
},
},
}
}
return client.AppsV1().StatefulSets(namespace).Create(context.Background(), statefulset, metav1.CreateOptions{})
}
// DeleteStatefulSet deletes the StatefulSet with the given name.
func DeleteStatefulSet(client kubernetes.Interface, namespace, name string) error {
return client.AppsV1().StatefulSets(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
}
// CreateCronJob creates a CronJob that references a ConfigMap/Secret.
func CreateCronJob(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (*batchv1.CronJob, error) {
cronjob := NewCronJob(name, namespace)
cronjob.Annotations = annotations
// Override image for integration tests
cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image = "busybox:1.36"
cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command = []string{"sh", "-c", "echo hello"}
cronjob.Spec.JobTemplate.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyOnFailure
if useConfigMap {
cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].EnvFrom = []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: name},
},
},
}
} else {
cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].EnvFrom = []corev1.EnvFromSource{
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: name},
},
},
}
}
return client.BatchV1().CronJobs(namespace).Create(context.Background(), cronjob, metav1.CreateOptions{})
}
// DeleteCronJob deletes the CronJob with the given name.
func DeleteCronJob(client kubernetes.Interface, namespace, name string) error {
return client.BatchV1().CronJobs(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
}
// ConvertResourceToSHA converts a resource data to SHA256 hash.
func ConvertResourceToSHA(resourceType, namespace, name, data string) string {
content := fmt.Sprintf("%s/%s/%s:%s", resourceType, namespace, name, data)
hash := sha256.Sum256([]byte(content))
return base64.StdEncoding.EncodeToString(hash[:])
}
// WaitForDeploymentAnnotation waits for a deployment to have the specified annotation value.
func WaitForDeploymentAnnotation(client kubernetes.Interface, namespace, name, annotation, expectedValue string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
deployment, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return false, nil // Keep waiting
}
value, ok := deployment.Spec.Template.Annotations[annotation]
if !ok {
return false, nil // Keep waiting
}
return value == expectedValue, nil
})
}
// WaitForDeploymentReloadedAnnotation waits for a deployment to have any reloaded annotation.
func WaitForDeploymentReloadedAnnotation(client kubernetes.Interface, namespace, name string, cfg *config.Config, timeout time.Duration) (bool, error) {
var found bool
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
deployment, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return false, nil // Keep waiting
}
// Check for the last-reloaded-from annotation in pod template
if deployment.Spec.Template.Annotations != nil {
if _, ok := deployment.Spec.Template.Annotations[cfg.Annotations.LastReloadedFrom]; ok {
found = true
return true, nil
}
}
return false, nil
})
if wait.Interrupted(err) {
return found, nil
}
return found, err
}
// WaitForDaemonSetReloadedAnnotation waits for a daemonset to have any reloaded annotation.
func WaitForDaemonSetReloadedAnnotation(client kubernetes.Interface, namespace, name string, cfg *config.Config, timeout time.Duration) (bool, error) {
var found bool
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
daemonset, err := client.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return false, nil // Keep waiting
}
// Check for the last-reloaded-from annotation in pod template
if daemonset.Spec.Template.Annotations != nil {
if _, ok := daemonset.Spec.Template.Annotations[cfg.Annotations.LastReloadedFrom]; ok {
found = true
return true, nil
}
}
return false, nil
})
if wait.Interrupted(err) {
return found, nil
}
return found, err
}
// WaitForStatefulSetReloadedAnnotation waits for a statefulset to have any reloaded annotation.
func WaitForStatefulSetReloadedAnnotation(client kubernetes.Interface, namespace, name string, cfg *config.Config, timeout time.Duration) (bool, error) {
var found bool
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
statefulset, err := client.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return false, nil // Keep waiting
}
// Check for the last-reloaded-from annotation in pod template
if statefulset.Spec.Template.Annotations != nil {
if _, ok := statefulset.Spec.Template.Annotations[cfg.Annotations.LastReloadedFrom]; ok {
found = true
return true, nil
}
}
return false, nil
})
if wait.Interrupted(err) {
return found, nil
}
return found, err
}