Skip to content

Commit

Permalink
feat: allow annotations in the pod template too (#123)
Browse files Browse the repository at this point in the history
Several public charts only allow to edit the annotations of the pod template, not the deployment. Annotations will also be checked in the pod template if not present in the deployment.

fix: #122

Signed-off-by: Aurélien Lambert <[email protected]>
  • Loading branch information
aure-olli authored Feb 14, 2020
1 parent 7b19601 commit 2384d65
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 29 deletions.
70 changes: 59 additions & 11 deletions internal/pkg/callbacks/rolling_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,17 @@ type VolumesFunc func(interface{}) []v1.Volume
//UpdateFunc performs the resource update
type UpdateFunc func(kube.Clients, string, interface{}) error

//AnnotationsFunc is a generic func to return annotations
type AnnotationsFunc func(interface{}) map[string]string

//PodAnnotationsFunc is a generic func to return annotations
type PodAnnotationsFunc func(interface{}) map[string]string

//RollingUpgradeFuncs contains generic functions to perform rolling upgrade
type RollingUpgradeFuncs struct {
ItemsFunc ItemsFunc
AnnotationsFunc AnnotationsFunc
PodAnnotationsFunc PodAnnotationsFunc
ContainersFunc ContainersFunc
InitContainersFunc InitContainersFunc
UpdateFunc UpdateFunc
Expand Down Expand Up @@ -72,18 +80,58 @@ func GetDeploymentConfigItems(clients kube.Clients, namespace string) []interfac
return util.InterfaceSlice(deploymentConfigs.Items)
}

// GetDeploymentAnnotations returns the annotations of given deployment
func GetDeploymentAnnotations(item interface{}) map[string]string {
return item.(appsv1.Deployment).ObjectMeta.Annotations
}

// GetDaemonSetAnnotations returns the annotations of given daemonSet
func GetDaemonSetAnnotations(item interface{}) map[string]string {
return item.(appsv1.DaemonSet).ObjectMeta.Annotations
}

// GetStatefulSetAnnotations returns the annotations of given statefulSet
func GetStatefulSetAnnotations(item interface{}) map[string]string {
return item.(appsv1.StatefulSet).ObjectMeta.Annotations
}

// GetDeploymentConfigAnnotations returns the annotations of given deploymentConfig
func GetDeploymentConfigAnnotations(item interface{}) map[string]string {
return item.(openshiftv1.DeploymentConfig).ObjectMeta.Annotations
}

// GetDeploymentPodAnnotations returns the pod's annotations of given deployment
func GetDeploymentPodAnnotations(item interface{}) map[string]string {
return item.(appsv1.Deployment).Spec.Template.ObjectMeta.Annotations
}

// GetDaemonSetPodAnnotations returns the pod's annotations of given daemonSet
func GetDaemonSetPodAnnotations(item interface{}) map[string]string {
return item.(appsv1.DaemonSet).Spec.Template.ObjectMeta.Annotations
}

// GetStatefulSetPodAnnotations returns the pod's annotations of given statefulSet
func GetStatefulSetPodAnnotations(item interface{}) map[string]string {
return item.(appsv1.StatefulSet).Spec.Template.ObjectMeta.Annotations
}

// GetDeploymentConfigPodAnnotations returns the pod's annotations of given deploymentConfig
func GetDeploymentConfigPodAnnotations(item interface{}) map[string]string {
return item.(openshiftv1.DeploymentConfig).Spec.Template.ObjectMeta.Annotations
}

// GetDeploymentContainers returns the containers of given deployment
func GetDeploymentContainers(item interface{}) []v1.Container {
return item.(appsv1.Deployment).Spec.Template.Spec.Containers
}

// GetDaemonSetContainers returns the containers of given daemonset
// GetDaemonSetContainers returns the containers of given daemonSet
func GetDaemonSetContainers(item interface{}) []v1.Container {
return item.(appsv1.DaemonSet).Spec.Template.Spec.Containers
}

// GetStatefulsetContainers returns the containers of given statefulSet
func GetStatefulsetContainers(item interface{}) []v1.Container {
// GetStatefulSetContainers returns the containers of given statefulSet
func GetStatefulSetContainers(item interface{}) []v1.Container {
return item.(appsv1.StatefulSet).Spec.Template.Spec.Containers
}

Expand All @@ -97,13 +145,13 @@ func GetDeploymentInitContainers(item interface{}) []v1.Container {
return item.(appsv1.Deployment).Spec.Template.Spec.InitContainers
}

// GetDaemonSetInitContainers returns the containers of given daemonset
// GetDaemonSetInitContainers returns the containers of given daemonSet
func GetDaemonSetInitContainers(item interface{}) []v1.Container {
return item.(appsv1.DaemonSet).Spec.Template.Spec.InitContainers
}

// GetStatefulsetInitContainers returns the containers of given statefulSet
func GetStatefulsetInitContainers(item interface{}) []v1.Container {
// GetStatefulSetInitContainers returns the containers of given statefulSet
func GetStatefulSetInitContainers(item interface{}) []v1.Container {
return item.(appsv1.StatefulSet).Spec.Template.Spec.InitContainers
}

Expand All @@ -126,8 +174,8 @@ func UpdateDaemonSet(clients kube.Clients, namespace string, resource interface{
return err
}

// UpdateStatefulset performs rolling upgrade on statefulSet
func UpdateStatefulset(clients kube.Clients, namespace string, resource interface{}) error {
// UpdateStatefulSet performs rolling upgrade on statefulSet
func UpdateStatefulSet(clients kube.Clients, namespace string, resource interface{}) error {
statefulSet := resource.(appsv1.StatefulSet)
_, err := clients.KubernetesClient.AppsV1().StatefulSets(namespace).Update(&statefulSet)
return err
Expand All @@ -145,13 +193,13 @@ func GetDeploymentVolumes(item interface{}) []v1.Volume {
return item.(appsv1.Deployment).Spec.Template.Spec.Volumes
}

// GetDaemonSetVolumes returns the Volumes of given daemonset
// GetDaemonSetVolumes returns the Volumes of given daemonSet
func GetDaemonSetVolumes(item interface{}) []v1.Volume {
return item.(appsv1.DaemonSet).Spec.Template.Spec.Volumes
}

// GetStatefulsetVolumes returns the Volumes of given statefulSet
func GetStatefulsetVolumes(item interface{}) []v1.Volume {
// GetStatefulSetVolumes returns the Volumes of given statefulSet
func GetStatefulSetVolumes(item interface{}) []v1.Volume {
return item.(appsv1.StatefulSet).Spec.Template.Spec.Volumes
}

Expand Down
26 changes: 20 additions & 6 deletions internal/pkg/handler/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
func GetDeploymentRollingUpgradeFuncs() callbacks.RollingUpgradeFuncs {
return callbacks.RollingUpgradeFuncs{
ItemsFunc: callbacks.GetDeploymentItems,
AnnotationsFunc: callbacks.GetDeploymentAnnotations,
PodAnnotationsFunc: callbacks.GetDeploymentPodAnnotations,
ContainersFunc: callbacks.GetDeploymentContainers,
InitContainersFunc: callbacks.GetDeploymentInitContainers,
UpdateFunc: callbacks.UpdateDeployment,
Expand All @@ -29,6 +31,8 @@ func GetDeploymentRollingUpgradeFuncs() callbacks.RollingUpgradeFuncs {
func GetDaemonSetRollingUpgradeFuncs() callbacks.RollingUpgradeFuncs {
return callbacks.RollingUpgradeFuncs{
ItemsFunc: callbacks.GetDaemonSetItems,
AnnotationsFunc: callbacks.GetDaemonSetAnnotations,
PodAnnotationsFunc: callbacks.GetDaemonSetPodAnnotations,
ContainersFunc: callbacks.GetDaemonSetContainers,
InitContainersFunc: callbacks.GetDaemonSetInitContainers,
UpdateFunc: callbacks.UpdateDaemonSet,
Expand All @@ -41,10 +45,12 @@ func GetDaemonSetRollingUpgradeFuncs() callbacks.RollingUpgradeFuncs {
func GetStatefulSetRollingUpgradeFuncs() callbacks.RollingUpgradeFuncs {
return callbacks.RollingUpgradeFuncs{
ItemsFunc: callbacks.GetStatefulSetItems,
ContainersFunc: callbacks.GetStatefulsetContainers,
InitContainersFunc: callbacks.GetStatefulsetInitContainers,
UpdateFunc: callbacks.UpdateStatefulset,
VolumesFunc: callbacks.GetStatefulsetVolumes,
AnnotationsFunc: callbacks.GetStatefulSetAnnotations,
PodAnnotationsFunc: callbacks.GetStatefulSetPodAnnotations,
ContainersFunc: callbacks.GetStatefulSetContainers,
InitContainersFunc: callbacks.GetStatefulSetInitContainers,
UpdateFunc: callbacks.UpdateStatefulSet,
VolumesFunc: callbacks.GetStatefulSetVolumes,
ResourceType: "StatefulSet",
}
}
Expand All @@ -53,6 +59,8 @@ func GetStatefulSetRollingUpgradeFuncs() callbacks.RollingUpgradeFuncs {
func GetDeploymentConfigRollingUpgradeFuncs() callbacks.RollingUpgradeFuncs {
return callbacks.RollingUpgradeFuncs{
ItemsFunc: callbacks.GetDeploymentConfigItems,
AnnotationsFunc: callbacks.GetDeploymentConfigAnnotations,
PodAnnotationsFunc: callbacks.GetDeploymentConfigPodAnnotations,
ContainersFunc: callbacks.GetDeploymentConfigContainers,
InitContainersFunc: callbacks.GetDeploymentConfigInitContainers,
UpdateFunc: callbacks.UpdateDeploymentConfig,
Expand Down Expand Up @@ -87,8 +95,14 @@ func PerformRollingUpgrade(clients kube.Clients, config util.Config, upgradeFunc
var err error
for _, i := range items {
// find correct annotation and update the resource
annotationValue := util.ToObjectMeta(i).Annotations[config.Annotation]
reloaderEnabledValue := util.ToObjectMeta(i).Annotations[options.ReloaderAutoAnnotation]
annotations := upgradeFuncs.AnnotationsFunc(i)
annotationValue, found := annotations[config.Annotation]
reloaderEnabledValue, foundAuto := annotations[options.ReloaderAutoAnnotation]
if !found && !foundAuto {
annotations = upgradeFuncs.PodAnnotationsFunc(i)
annotationValue = annotations[config.Annotation]
reloaderEnabledValue = annotations[options.ReloaderAutoAnnotation]
}
result := constants.NotUpdated
reloaderEnabled, err := strconv.ParseBool(reloaderEnabledValue)
if err == nil && reloaderEnabled {
Expand Down
101 changes: 89 additions & 12 deletions internal/pkg/handler/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ import (
)

var (
clients = kube.Clients{KubernetesClient: testclient.NewSimpleClientset()}
namespace = "test-handler-" + testutil.RandSeq(5)
configmapName = "testconfigmap-handler-" + testutil.RandSeq(5)
secretName = "testsecret-handler-" + testutil.RandSeq(5)
configmapWithInitContainer = "testconfigmapInitContainerhandler-" + testutil.RandSeq(5)
secretWithInitContainer = "testsecretWithInitContainer-handler-" + testutil.RandSeq(5)
configmapWithInitEnv = "configmapWithInitEnv-" + testutil.RandSeq(5)
secretWithInitEnv = "secretWithInitEnv-handler-" + testutil.RandSeq(5)
configmapWithEnvName = "testconfigmapWithEnv-handler-" + testutil.RandSeq(5)
configmapWithEnvFromName = "testconfigmapWithEnvFrom-handler-" + testutil.RandSeq(5)
secretWithEnvName = "testsecretWithEnv-handler-" + testutil.RandSeq(5)
secretWithEnvFromName = "testsecretWithEnvFrom-handler-" + testutil.RandSeq(5)
clients = kube.Clients{KubernetesClient: testclient.NewSimpleClientset()}
namespace = "test-handler-" + testutil.RandSeq(5)
configmapName = "testconfigmap-handler-" + testutil.RandSeq(5)
secretName = "testsecret-handler-" + testutil.RandSeq(5)
configmapWithInitContainer = "testconfigmapInitContainerhandler-" + testutil.RandSeq(5)
secretWithInitContainer = "testsecretWithInitContainer-handler-" + testutil.RandSeq(5)
configmapWithInitEnv = "configmapWithInitEnv-" + testutil.RandSeq(5)
secretWithInitEnv = "secretWithInitEnv-handler-" + testutil.RandSeq(5)
configmapWithEnvName = "testconfigmapWithEnv-handler-" + testutil.RandSeq(5)
configmapWithEnvFromName = "testconfigmapWithEnvFrom-handler-" + testutil.RandSeq(5)
secretWithEnvName = "testsecretWithEnv-handler-" + testutil.RandSeq(5)
secretWithEnvFromName = "testsecretWithEnvFrom-handler-" + testutil.RandSeq(5)
configmapWithPodAnnotations = "testconfigmapPodAnnotations-handler-" + testutil.RandSeq(5)
configmapWithBothAnnotations = "testconfigmapBothAnnotations-handler-" + testutil.RandSeq(5)
)

func TestMain(m *testing.M) {
Expand Down Expand Up @@ -104,6 +106,11 @@ func setup() {
logrus.Errorf("Error in secret creation: %v", err)
}

_, err = testutil.CreateConfigMap(clients.KubernetesClient, namespace, configmapWithPodAnnotations, "www.google.com")
if err != nil {
logrus.Errorf("Error in configmap creation: %v", err)
}

// Creating Deployment with configmap
_, err = testutil.CreateDeployment(clients.KubernetesClient, configmapName, namespace, true)
if err != nil {
Expand Down Expand Up @@ -212,6 +219,17 @@ func setup() {
logrus.Errorf("Error in StatefulSet with secret configmap as env var source creation: %v", err)
}

// Creating Deployment with pod annotations
_, err = testutil.CreateDeploymentWithPodAnnotations(clients.KubernetesClient, configmapWithPodAnnotations, namespace, false)
if err != nil {
logrus.Errorf("Error in Deployment with pod annotations: %v", err)
}

// Creating Deployment with both annotations
_, err = testutil.CreateDeploymentWithPodAnnotations(clients.KubernetesClient, configmapWithBothAnnotations, namespace, true)
if err != nil {
logrus.Errorf("Error in Deployment with both annotations: %v", err)
}
}

func teardown() {
Expand Down Expand Up @@ -275,6 +293,18 @@ func teardown() {
logrus.Errorf("Error while deleting deployment with secret as envFrom source %v", deploymentError)
}

// Deleting Deployment with pod annotations
deploymentError = testutil.DeleteDeployment(clients.KubernetesClient, namespace, configmapWithPodAnnotations)
if deploymentError != nil {
logrus.Errorf("Error while deleting deployment with pod annotations %v", deploymentError)
}

// Deleting Deployment with both annotations
deploymentError = testutil.DeleteDeployment(clients.KubernetesClient, namespace, configmapWithBothAnnotations)
if deploymentError != nil {
logrus.Errorf("Error while deleting deployment with both annotations %v", deploymentError)
}

// Deleting DaemonSet with configmap
daemonSetError := testutil.DeleteDaemonSet(clients.KubernetesClient, namespace, configmapName)
if daemonSetError != nil {
Expand Down Expand Up @@ -383,6 +413,11 @@ func teardown() {
logrus.Errorf("Error while deleting the secret used as env var source in init container %v", err)
}

err = testutil.DeleteConfigMap(clients.KubernetesClient, namespace, configmapWithPodAnnotations)
if err != nil {
logrus.Errorf("Error while deleting the configmap used with pod annotations: %v", err)
}

// Deleting namespace
testutil.DeleteNamespace(namespace, clients.KubernetesClient)

Expand Down Expand Up @@ -667,3 +702,45 @@ func TestRollingUpgradeForStatefulSetWithSecret(t *testing.T) {
t.Errorf("StatefulSet was not updated")
}
}

func TestRollingUpgradeForDeploymentWithPodAnnotations(t *testing.T) {
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapWithPodAnnotations, "www.stakater.com")
config := getConfigWithAnnotations(constants.ConfigmapEnvVarPostfix, configmapWithPodAnnotations, shaData, options.ConfigmapUpdateOnChangeAnnotation)
deploymentFuncs := GetDeploymentRollingUpgradeFuncs()

err := PerformRollingUpgrade(clients, config, deploymentFuncs)
time.Sleep(5 * time.Second)
if err != nil {
t.Errorf("Rolling upgrade failed for Deployment with pod annotations")
}

logrus.Infof("Verifying deployment update")
envName := constants.EnvVarPrefix + util.ConvertToEnvVarName(config.ResourceName) + "_" + constants.ConfigmapEnvVarPostfix
items := deploymentFuncs.ItemsFunc(clients, config.Namespace)
var foundPod, foundBoth bool
for _, i := range items {
name := util.ToObjectMeta(i).Name
if name == configmapWithPodAnnotations {
containers := deploymentFuncs.ContainersFunc(i)
updated := testutil.GetResourceSHA(containers, envName)
if updated != config.SHAValue {
t.Errorf("Deployment was not updated")
}
foundPod = true
}
if name == configmapWithBothAnnotations {
containers := deploymentFuncs.ContainersFunc(i)
updated := testutil.GetResourceSHA(containers, envName)
if updated == config.SHAValue {
t.Errorf("Deployment was updated")
}
foundBoth = true
}
}
if !foundPod {
t.Errorf("Deployment with pod annotations was not found")
}
if !foundBoth {
t.Errorf("Deployment with both annotations was not found")
}
}
32 changes: 32 additions & 0 deletions internal/pkg/testutil/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,28 @@ func GetDeploymentWithEnvVarSources(namespace string, deploymentName string) *ap
}
}

func GetDeploymentWithPodAnnotations(namespace string, deploymentName string, both bool) *appsv1.Deployment {
replicaset := int32(1)
deployment := &appsv1.Deployment{
ObjectMeta: getObjectMeta(namespace, deploymentName, false),
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"secondLabel": "temp"},
},
Replicas: &replicaset,
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
Template: getPodTemplateSpecWithEnvVarSources(deploymentName),
},
}
if !both {
deployment.ObjectMeta.Annotations = nil
}
deployment.Spec.Template.ObjectMeta.Annotations = getAnnotations(deploymentName, true)
return deployment
}

// GetDaemonSet provides daemonset for testing
func GetDaemonSet(namespace string, daemonsetName string) *appsv1.DaemonSet {
return &appsv1.DaemonSet{
Expand Down Expand Up @@ -604,6 +626,16 @@ func CreateDeploymentWithEnvVarSource(client kubernetes.Interface, deploymentNam
return deployment, err
}

// CreateDeployment creates a deployment in given namespace and returns the Deployment
func CreateDeploymentWithPodAnnotations(client kubernetes.Interface, deploymentName string, namespace string, both bool) (*appsv1.Deployment, error) {
logrus.Infof("Creating Deployment")
deploymentClient := client.AppsV1().Deployments(namespace)
deploymentObj := GetDeploymentWithPodAnnotations(namespace, deploymentName, both)
deployment, err := deploymentClient.Create(deploymentObj)
time.Sleep(3 * time.Second)
return deployment, err
}

// CreateDaemonSet creates a deployment in given namespace and returns the DaemonSet
func CreateDaemonSet(client kubernetes.Interface, daemonsetName string, namespace string, volumeMount bool) (*appsv1.DaemonSet, error) {
logrus.Infof("Creating DaemonSet")
Expand Down

0 comments on commit 2384d65

Please sign in to comment.