diff --git a/internal/pkg/controller/controller_test.go b/internal/pkg/controller/controller_test.go index 82d65fb7..2c1e1c8e 100644 --- a/internal/pkg/controller/controller_test.go +++ b/internal/pkg/controller/controller_test.go @@ -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}, } } diff --git a/internal/pkg/handler/updated-handler.go b/internal/pkg/handler/updated-handler.go index d589383e..5d5ce225 100644 --- a/internal/pkg/handler/updated-handler.go +++ b/internal/pkg/handler/updated-handler.go @@ -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[:]) -} diff --git a/internal/pkg/helper/helper.go b/internal/pkg/helper/helper.go new file mode 100644 index 00000000..333ca4e6 --- /dev/null +++ b/internal/pkg/helper/helper.go @@ -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) +}