mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-20 20:46:30 +00:00
356 lines
13 KiB
Go
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
|
|
}
|