Files
Reloader/internal/pkg/handler/update.go
2018-07-26 12:59:10 +05:00

195 lines
6.1 KiB
Go

package handler
import (
"sort"
"strings"
"github.com/sirupsen/logrus"
"github.com/stakater/Reloader/internal/pkg/callbacks"
"github.com/stakater/Reloader/internal/pkg/constants"
"github.com/stakater/Reloader/internal/pkg/crypto"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/kube"
"k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
)
// ResourceUpdatedHandler contains updated objects
type ResourceUpdatedHandler struct {
Resource interface{}
OldResource interface{}
}
// Handle processes the updated resource
func (r ResourceUpdatedHandler) Handle() error {
if r.Resource == nil || r.OldResource == nil {
logrus.Errorf("Error in Handler")
} else {
logrus.Infof("Detected changes in object %s", r.Resource)
// process resource based on its type
rollingUpgrade(r, callbacks.RollingUpgradeFuncs{
ItemsFunc: callbacks.GetDeploymentItems,
ContainersFunc: callbacks.GetDeploymentContainers,
UpdateFunc: callbacks.UpdateDeployment,
ResourceType: "Deployment",
})
rollingUpgrade(r, callbacks.RollingUpgradeFuncs{
ItemsFunc: callbacks.GetDaemonSetItems,
ContainersFunc: callbacks.GetDaemonSetContainers,
UpdateFunc: callbacks.UpdateDaemonSet,
ResourceType: "DaemonSet",
})
rollingUpgrade(r, callbacks.RollingUpgradeFuncs{
ItemsFunc: callbacks.GetStatefulSetItems,
ContainersFunc: callbacks.GetStatefulsetContainers,
UpdateFunc: callbacks.UpdateStatefulset,
ResourceType: "StatefulSet",
})
}
return nil
}
func rollingUpgrade(r ResourceUpdatedHandler, upgradeFuncs callbacks.RollingUpgradeFuncs) {
client, err := kube.GetClient()
if err != nil {
logrus.Fatalf("Unable to create Kubernetes client error = %v", err)
}
config, envVarPostfix, oldSHAData := getConfig(r)
if config.SHAValue != oldSHAData {
err = PerformRollingUpgrade(client, config, envVarPostfix, upgradeFuncs)
if err != nil {
logrus.Fatalf("Rolling upgrade failed with error = %v", err)
}
} else {
logrus.Infof("Rolling upgrade will not happend because no actual change in data has been detected")
}
}
func getConfig(r ResourceUpdatedHandler) (util.Config, string, string) {
var oldSHAData, envVarPostfix string
var config util.Config
if _, ok := r.Resource.(*v1.ConfigMap); ok {
logrus.Infof("Performing 'Updated' action for resource of type 'configmap'")
oldSHAData = getSHAfromConfigmap(r.OldResource.(*v1.ConfigMap).Data)
config = getConfigmapConfig(r)
envVarPostfix = constants.ConfigmapEnvVarPostfix
} else if _, ok := r.Resource.(*v1.Secret); ok {
logrus.Infof("Performing 'Updated' action for resource of type 'secret'")
oldSHAData = getSHAfromSecret(r.OldResource.(*v1.Secret).Data)
config = getSecretConfig(r)
envVarPostfix = constants.SecretEnvVarPostfix
} else {
logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found, %v", r.Resource)
}
return config, envVarPostfix, oldSHAData
}
func getConfigmapConfig(r ResourceUpdatedHandler) util.Config {
configmap := r.Resource.(*v1.ConfigMap)
return util.Config{
Namespace: configmap.Namespace,
ResourceName: configmap.Name,
Annotation: constants.ConfigmapUpdateOnChangeAnnotation,
SHAValue: getSHAfromConfigmap(configmap.Data),
}
}
func getSecretConfig(r ResourceUpdatedHandler) util.Config {
secret := r.Resource.(*v1.Secret)
return util.Config{
Namespace: secret.Namespace,
ResourceName: secret.Name,
Annotation: constants.SecretUpdateOnChangeAnnotation,
SHAValue: getSHAfromSecret(secret.Data),
}
}
// PerformRollingUpgrade upgrades the deployment if there is any change in configmap or secret data
func PerformRollingUpgrade(client kubernetes.Interface, config util.Config, envarPostfix string, upgradeFuncs callbacks.RollingUpgradeFuncs) error {
items := upgradeFuncs.ItemsFunc(client, config.Namespace)
var err error
for _, i := range items {
containers := upgradeFuncs.ContainersFunc(i)
// find correct annotation and update the resource
annotationValue := util.ToObjectMeta(i).Annotations[config.Annotation]
if annotationValue != "" {
values := strings.Split(annotationValue, ",")
for _, value := range values {
if value == config.ResourceName {
updated := updateContainers(containers, value, config.SHAValue, envarPostfix)
if !updated {
logrus.Warnf("Rolling upgrade did not happen")
} else {
err = upgradeFuncs.UpdateFunc(client, config.Namespace, i)
if err != nil {
logrus.Errorf("Update %s failed %v", upgradeFuncs.ResourceType, err)
} else {
logrus.Infof("Updated %s of type %s", config.ResourceName, upgradeFuncs.ResourceType)
}
break
}
}
}
}
}
return err
}
func updateContainers(containers []v1.Container, annotationValue string, shaData string, envarPostfix string) bool {
updated := false
envar := constants.EnvVarPrefix + util.ConvertToEnvVarName(annotationValue) + envarPostfix
logrus.Infof("Generated environment variable: %s", envar)
for i := range containers {
envs := containers[i].Env
//update if env var exists
updated = updateEnvVar(envs, envar, shaData)
// if no existing env var exists lets create one
if !updated {
e := v1.EnvVar{
Name: envar,
Value: shaData,
}
containers[i].Env = append(containers[i].Env, e)
updated = true
logrus.Infof("%s environment variable does not exist, creating a new envVar", envar)
}
}
return updated
}
func updateEnvVar(envs []v1.EnvVar, envar string, shaData string) bool {
for j := range envs {
if envs[j].Name == envar {
logrus.Infof("%s environment variable found", envar)
if envs[j].Value != shaData {
logrus.Infof("Updating %s", envar)
envs[j].Value = shaData
return true
}
}
}
return false
}
func getSHAfromConfigmap(data map[string]string) string {
values := []string{}
for k, v := range data {
values = append(values, k+"="+v)
}
sort.Strings(values)
return crypto.GenerateSHA(strings.Join(values, ";"))
}
func getSHAfromSecret(data map[string][]byte) string {
values := []string{}
for k, v := range data {
values = append(values, k+"="+string(v[:]))
}
sort.Strings(values)
return crypto.GenerateSHA(strings.Join(values, ";"))
}