mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-23 22:16:45 +00:00
Add helper class and complete integration test
This commit is contained in:
@@ -2,10 +2,12 @@ package controller
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
helper "github.com/stakater/Reloader/internal/pkg/helper"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
@@ -14,9 +16,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
configmapNamePrefix = "testconfigmap-reloader"
|
||||
secretNamePrefix = "testsecret-reloader"
|
||||
letters = []rune("abcdefghijklmnopqrstuvwxyz")
|
||||
configmapNamePrefix = "testconfigmap-reloader"
|
||||
secretNamePrefix = "testsecret-reloader"
|
||||
letters = []rune("abcdefghijklmnopqrstuvwxyz")
|
||||
configmapUpdateOnChangeAnnotation = "reloader.stakater.com/configmap.update-on-change"
|
||||
secretUpdateOnChangeAnnotation = "reloader.stakater.com/secret.update-on-change"
|
||||
)
|
||||
|
||||
func randSeq(n int) string {
|
||||
@@ -36,9 +40,11 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) {
|
||||
return
|
||||
}
|
||||
namespace := "test-reloader"
|
||||
logrus.Infof("Step 1: Create namespace")
|
||||
createNamespace(t, namespace, client)
|
||||
defer deleteNamespace(t, namespace, client)
|
||||
|
||||
logrus.Infof("Step 2: Create controller")
|
||||
controller, err := NewController(client, "configMaps", namespace)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to create NewController error = %v", err)
|
||||
@@ -46,41 +52,68 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) {
|
||||
}
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
logrus.Infof("Step 3: Start controller")
|
||||
go controller.Run(1, stop)
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
configmapName := configmapNamePrefix + "-update-" + randSeq(5)
|
||||
configmapClient := client.CoreV1().ConfigMaps(namespace)
|
||||
|
||||
logrus.Infof("Step 4: Create configmap")
|
||||
_, err = configmapClient.Create(initConfigmap(namespace, configmapName))
|
||||
if err != nil {
|
||||
logrus.Fatalf("Fatal error in configmap creation: %v", err)
|
||||
}
|
||||
logrus.Infof("Created Configmap %q.\n", configmapName)
|
||||
time.Sleep(10 * time.Second)
|
||||
deployment := createDeployement(configmapName, namespace, client)
|
||||
|
||||
logrus.Infof("Step 5: Create Deployment")
|
||||
deployment := createDeployment(configmapName, namespace, client)
|
||||
|
||||
logrus.Infof("Step 6: Update configmap for first time")
|
||||
logrus.Infof("Updating Configmap %q.\n", configmapName)
|
||||
_, err = configmapClient.Get(configmapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while getting configmap %v", err)
|
||||
}
|
||||
_, updateErr := configmapClient.Update(updateConfigmap(namespace, configmapName))
|
||||
|
||||
// TODO: Add functionality to verify reloader functionality here
|
||||
_, updateErr := configmapClient.Update(updateConfigmap(namespace, configmapName, "www.stakater.com"))
|
||||
|
||||
if updateErr != nil {
|
||||
err = controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while deleting the configmap %v", err)
|
||||
}
|
||||
logrus.Fatalf("Fatal error in configmap update: %v", updateErr)
|
||||
t.Errorf("Configmap was not updated")
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
logrus.Infof("Step 7: Verify deployment update for first time")
|
||||
|
||||
updated := verifyDeploymentUpdate(client, namespace, configmapName, "_CONFIGMAP", "www.stakater.com")
|
||||
if !updated {
|
||||
t.Errorf("Deployment was not updated")
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
logrus.Infof("Step 8: Update configmap for Second time")
|
||||
_, updateErr = configmapClient.Update(updateConfigmap(namespace, configmapName, "aurorasolutions.io"))
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
logrus.Infof("Step 9: Verify deployment update for second time")
|
||||
updated = verifyDeploymentUpdate(client, namespace, configmapName, "_CONFIGMAP", "aurorasolutions.io")
|
||||
if !updated {
|
||||
t.Errorf("Deployment was not updated")
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
logrus.Infof("Step 10: Delete Deployment")
|
||||
logrus.Infof("Deleting Deployment %q.\n", deployment.GetObjectMeta().GetName())
|
||||
deploymentError := controller.client.ExtensionsV1beta1().Deployments(namespace).Delete(configmapName, &metav1.DeleteOptions{})
|
||||
if deploymentError != nil {
|
||||
logrus.Fatalf("Error while deleting the configmap %v", deploymentError)
|
||||
}
|
||||
|
||||
logrus.Infof("Step 11: Delete Configmap")
|
||||
logrus.Infof("Deleting Configmap %q.\n", configmapName)
|
||||
err = controller.client.CoreV1().ConfigMaps(namespace).Delete(configmapName, &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
@@ -89,7 +122,55 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeployment(t *testing.T) {
|
||||
time.Sleep(15 * time.Second)
|
||||
}
|
||||
|
||||
func createDeployement(deploymentName string, namespace string, client kubernetes.Interface) *v1beta1.Deployment {
|
||||
func verifyDeploymentUpdate(client kubernetes.Interface, namespace string, name string, resourceType string, change string) bool {
|
||||
deployments, err := client.ExtensionsV1beta1().Deployments(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to list deployments %v", err)
|
||||
}
|
||||
for _, d := range deployments.Items {
|
||||
containers := d.Spec.Template.Spec.Containers
|
||||
// match deployments with the correct annotation
|
||||
annotationValue := d.ObjectMeta.Annotations[configmapUpdateOnChangeAnnotation]
|
||||
if annotationValue != "" {
|
||||
values := strings.Split(annotationValue, ",")
|
||||
matches := false
|
||||
for _, value := range values {
|
||||
if value == name {
|
||||
matches = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if matches {
|
||||
sshData := helper.ConvertConfigmapToSHA(updateConfigmap(namespace, name, change))
|
||||
envName := "STAKATER_" + helper.ConvertToEnvVarName(annotationValue) + resourceType
|
||||
updated := getResourceSsh(containers, envName)
|
||||
logrus.Infof("sshData %s", sshData)
|
||||
logrus.Infof("updated %s", updated)
|
||||
|
||||
if updated != sshData {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getResourceSsh(containers []v1.Container, envar string) string {
|
||||
for i := range containers {
|
||||
envs := containers[i].Env
|
||||
for j := range envs {
|
||||
if envs[j].Name == envar {
|
||||
return envs[j].Value
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func createDeployment(deploymentName string, namespace string, client kubernetes.Interface) *v1beta1.Deployment {
|
||||
deploymentClient := client.ExtensionsV1beta1().Deployments(namespace)
|
||||
deployment := initDeployment(namespace, deploymentName)
|
||||
deployment, err := deploymentClient.Create(deployment)
|
||||
@@ -223,6 +304,7 @@ func createNamespace(t *testing.T, namespace string, client kubernetes.Interface
|
||||
}
|
||||
|
||||
func deleteNamespace(t *testing.T, namespace string, client kubernetes.Interface) {
|
||||
logrus.Infof("Step 12: Delete Namespace")
|
||||
err := client.CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Error("Failed to delete namespace that was created for testing", err)
|
||||
@@ -231,14 +313,14 @@ func deleteNamespace(t *testing.T, namespace string, client kubernetes.Interface
|
||||
}
|
||||
}
|
||||
|
||||
func updateConfigmap(namespace string, configmapName string) *v1.ConfigMap {
|
||||
func updateConfigmap(namespace string, configmapName string, testData string) *v1.ConfigMap {
|
||||
return &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: configmapName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{"firstLabel": "temp"},
|
||||
},
|
||||
Data: map[string]string{"test.url": "www.stakater.com"},
|
||||
Data: map[string]string{"test.url": testData},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
helper "github.com/stakater/Reloader/internal/pkg/helper"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
"k8s.io/api/core/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -16,7 +13,7 @@ import (
|
||||
|
||||
const (
|
||||
configmapUpdateOnChangeAnnotation = "reloader.stakater.com/configmap.update-on-change"
|
||||
// Adding seperate annotation to differentiate between configmap and secret
|
||||
// Adding separate annotation to differentiate between configmap and secret
|
||||
secretUpdateOnChangeAnnotation = "reloader.stakater.com/secret.update-on-change"
|
||||
)
|
||||
|
||||
@@ -59,12 +56,12 @@ func rollingUpgrade(r ResourceUpdatedHandler, resourceType string, rollingUpgrad
|
||||
if resourceType == "configmaps" {
|
||||
namespace = r.Resource.(*v1.ConfigMap).Namespace
|
||||
name = r.Resource.(*v1.ConfigMap).Name
|
||||
sshData = convertConfigmapToSHA(r.Resource.(*v1.ConfigMap))
|
||||
sshData = helper.ConvertConfigmapToSHA(r.Resource.(*v1.ConfigMap))
|
||||
envName = "_CONFIGMAP"
|
||||
} else if resourceType == "secrets" {
|
||||
namespace = r.Resource.(*v1.Secret).Namespace
|
||||
name = r.Resource.(*v1.Secret).Name
|
||||
sshData = convertSecretToSHA(r.Resource.(*v1.Secret))
|
||||
sshData = helper.ConvertSecretToSHA(r.Resource.(*v1.Secret))
|
||||
envName = "_SECRET"
|
||||
}
|
||||
|
||||
@@ -211,7 +208,7 @@ func rollingUpgradeForStatefulSets(client kubernetes.Interface, r ResourceUpdate
|
||||
|
||||
func updateContainers(containers []v1.Container, annotationValue string, sshData string, resourceType string) bool {
|
||||
updated := false
|
||||
envar := "STAKATER_" + convertToEnvVarName(annotationValue) + resourceType
|
||||
envar := "STAKATER_" + helper.ConvertToEnvVarName(annotationValue) + resourceType
|
||||
logrus.Infof("Generated environment variable: %s", envar)
|
||||
|
||||
for i := range containers {
|
||||
@@ -220,7 +217,7 @@ func updateContainers(containers []v1.Container, annotationValue string, sshData
|
||||
for j := range envs {
|
||||
if envs[j].Name == envar {
|
||||
matched = true
|
||||
logrus.Infof("%s environment variable found")
|
||||
logrus.Infof("%s environment variable found", envar)
|
||||
if envs[j].Value != sshData {
|
||||
logrus.Infof("Updating %s to %s", envar, sshData)
|
||||
envs[j].Value = sshData
|
||||
@@ -236,60 +233,8 @@ func updateContainers(containers []v1.Container, annotationValue string, sshData
|
||||
}
|
||||
containers[i].Env = append(containers[i].Env, e)
|
||||
updated = true
|
||||
logrus.Infof("%s environment variable does not found so creating a new one")
|
||||
logrus.Infof("%s environment variable does not found, creating a new env with value %s", envar, sshData)
|
||||
}
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
// convertToEnvVarName converts the given text into a usable env var
|
||||
// removing any special chars with '_' and transforming text to upper case
|
||||
func convertToEnvVarName(text string) string {
|
||||
var buffer bytes.Buffer
|
||||
upper := strings.ToUpper(text)
|
||||
lastCharValid := false
|
||||
for i := 0; i < len(upper); i++ {
|
||||
ch := upper[i]
|
||||
if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
|
||||
buffer.WriteString(string(ch))
|
||||
lastCharValid = true
|
||||
} else {
|
||||
if lastCharValid {
|
||||
buffer.WriteString("_")
|
||||
}
|
||||
lastCharValid = false
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func convertConfigmapToSHA(cm *v1.ConfigMap) string {
|
||||
logrus.Infof("Generating SHA for configmap data")
|
||||
values := []string{}
|
||||
for k, v := range cm.Data {
|
||||
values = append(values, k+"="+v)
|
||||
}
|
||||
sort.Strings(values)
|
||||
sha := generateSHA(strings.Join(values, ";"))
|
||||
logrus.Infof("SHA for configmap data: %x", sha)
|
||||
return sha
|
||||
}
|
||||
|
||||
func convertSecretToSHA(se *v1.Secret) string {
|
||||
logrus.Infof("Generating SHA for secret data")
|
||||
values := []string{}
|
||||
for k, v := range se.Data {
|
||||
values = append(values, k+"="+string(v[:]))
|
||||
}
|
||||
sort.Strings(values)
|
||||
sha := generateSHA(strings.Join(values, ";"))
|
||||
logrus.Infof("SHA for secret data: %x", sha)
|
||||
return sha
|
||||
}
|
||||
|
||||
func generateSHA(data string) string {
|
||||
hasher := sha1.New()
|
||||
io.WriteString(hasher, data)
|
||||
sha := hasher.Sum(nil)
|
||||
return string(sha[:])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ConvertToEnvVarName converts the given text into a usable env var
|
||||
// removing any special chars with '_' and transforming text to upper case
|
||||
func ConvertToEnvVarName(text string) string {
|
||||
var buffer bytes.Buffer
|
||||
upper := strings.ToUpper(text)
|
||||
lastCharValid := false
|
||||
for i := 0; i < len(upper); i++ {
|
||||
ch := upper[i]
|
||||
if (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
|
||||
buffer.WriteString(string(ch))
|
||||
lastCharValid = true
|
||||
} else {
|
||||
if lastCharValid {
|
||||
buffer.WriteString("_")
|
||||
}
|
||||
lastCharValid = false
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// ConvertConfigmapToSHA generates SHA for configmap data
|
||||
func ConvertConfigmapToSHA(cm *v1.ConfigMap) string {
|
||||
logrus.Infof("Generating SHA for configmap data")
|
||||
values := []string{}
|
||||
for k, v := range cm.Data {
|
||||
values = append(values, k+"="+v)
|
||||
}
|
||||
sort.Strings(values)
|
||||
sha := GenerateSHA(strings.Join(values, ";"))
|
||||
logrus.Infof("SHA for configmap data: %s", sha)
|
||||
return sha
|
||||
}
|
||||
|
||||
// ConvertSecretToSHA generates SHA for secret data
|
||||
func ConvertSecretToSHA(se *v1.Secret) string {
|
||||
logrus.Infof("Generating SHA for secret data")
|
||||
values := []string{}
|
||||
for k, v := range se.Data {
|
||||
values = append(values, k+"="+string(v[:]))
|
||||
}
|
||||
sort.Strings(values)
|
||||
sha := GenerateSHA(strings.Join(values, ";"))
|
||||
logrus.Infof("SHA for secret data: %s", sha)
|
||||
return sha
|
||||
}
|
||||
|
||||
// GenerateSHA generates SHA from string
|
||||
func GenerateSHA(data string) string {
|
||||
hasher := sha1.New()
|
||||
io.WriteString(hasher, data)
|
||||
sha := hasher.Sum(nil)
|
||||
return fmt.Sprintf("%x", sha)
|
||||
}
|
||||
Reference in New Issue
Block a user