Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions pkg/processor/daemonset/daemonset.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr"
)

var daemonsetGVC = schema.GroupVersionKind{
Expand All @@ -27,6 +28,9 @@ var daemonsetGVC = schema.GroupVersionKind{
var daemonsetTempl, _ = template.New("daemonset").Parse(
`{{- .Meta }}
spec:
{{- if .UpdateStrategy }}
{{ .UpdateStrategy }}
{{- end }}
selector:
{{ .Selector }}
template:
Expand Down Expand Up @@ -66,6 +70,10 @@ func (d daemonset) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstru
values := helmify.Values{}

name := appMeta.TrimName(obj.GetName())
updateStrategy, err := processUpdateStrategy(name, &dae, &values)
if err != nil {
return true, nil, err
}

matchLabels, err := yamlformat.Marshal(map[string]interface{}{"matchLabels": dae.Spec.Selector.MatchLabels}, 0)
if err != nil {
Expand Down Expand Up @@ -118,12 +126,14 @@ func (d daemonset) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstru
values: values,
data: struct {
Meta string
UpdateStrategy string
Selector string
PodLabels string
PodAnnotations string
Spec string
}{
Meta: meta,
UpdateStrategy: updateStrategy,
Selector: selector,
PodLabels: podLabels,
PodAnnotations: podAnnotations,
Expand All @@ -132,9 +142,69 @@ func (d daemonset) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstru
}, nil
}

func processUpdateStrategy(name string, dae *appsv1.DaemonSet, values *helmify.Values) (string, error) {
if dae.Spec.UpdateStrategy.Type == "" && dae.Spec.UpdateStrategy.RollingUpdate == nil {
return "", nil
}
if dae.Spec.UpdateStrategy.Type == "" {
dae.Spec.UpdateStrategy.Type = appsv1.RollingUpdateDaemonSetStrategyType
}
allowedStrategyTypes := map[appsv1.DaemonSetUpdateStrategyType]bool{
appsv1.RollingUpdateDaemonSetStrategyType: true,
appsv1.OnDeleteDaemonSetStrategyType: true,
}
if !allowedStrategyTypes[dae.Spec.UpdateStrategy.Type] {
return "", fmt.Errorf("invalid daemonset update strategy type: %s", dae.Spec.UpdateStrategy.Type)
}
strategyTypeTpl, err := values.Add(string(dae.Spec.UpdateStrategy.Type), name, "updateStrategy", "type")
if err != nil {
return "", err
}
strategyMap := map[string]interface{}{
"type": strategyTypeTpl,
}
if dae.Spec.UpdateStrategy.Type == appsv1.RollingUpdateDaemonSetStrategyType {
if rollingUpdate := dae.Spec.UpdateStrategy.RollingUpdate; rollingUpdate != nil {
rollingUpdateMap := map[string]interface{}{}
setRollingUpdateField := func(value *intstr.IntOrString, fieldName string) error {
var tpl string
var err error
if value.Type == intstr.Int {
tpl, err = values.Add(value.IntValue(), name, "updateStrategy", "rollingUpdate", fieldName)
} else {
tpl, err = values.Add(value.String(), name, "updateStrategy", "rollingUpdate", fieldName)
}
if err != nil {
return err
}
rollingUpdateMap[fieldName] = tpl
return nil
}
if rollingUpdate.MaxSurge != nil {
if err := setRollingUpdateField(rollingUpdate.MaxSurge, "maxSurge"); err != nil {
return "", err
}
}
if rollingUpdate.MaxUnavailable != nil {
if err := setRollingUpdateField(rollingUpdate.MaxUnavailable, "maxUnavailable"); err != nil {
return "", err
}
}
strategyMap["rollingUpdate"] = rollingUpdateMap
}
}
strategy, err := yamlformat.Marshal(map[string]interface{}{"updateStrategy": strategyMap}, 2)
if err != nil {
return "", err
}
strategy = strings.ReplaceAll(strategy, "'", "")
return strategy, nil
}

type result struct {
data struct {
Meta string
UpdateStrategy string
Selector string
PodLabels string
PodAnnotations string
Expand Down
114 changes: 113 additions & 1 deletion pkg/processor/daemonset/daemonset_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package daemonset

import (
"bytes"
"testing"

"github.com/arttor/helmify/pkg/helmify"
"github.com/arttor/helmify/pkg/metadata"

"github.com/arttor/helmify/internal"
Expand Down Expand Up @@ -55,6 +57,53 @@ spec:
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
`
strDaeWithRollingUpdate = `apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 20%
maxSurge: 1
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
`
strDaeWithOnDelete = `apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
updateStrategy:
type: OnDelete
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
`
)

Expand All @@ -63,9 +112,72 @@ func Test_daemonset_Process(t *testing.T) {

t.Run("processed", func(t *testing.T) {
obj := internal.GenerateObj(strDepl)
processed, _, err := testInstance.Process(&metadata.Service{}, obj)
processed, res, err := testInstance.Process(&metadata.Service{}, obj)
assert.NoError(t, err)
assert.Equal(t, true, processed)

var buf bytes.Buffer
err = res.Write(&buf)
assert.NoError(t, err)
assert.NotContains(t, buf.String(), "updateStrategy")
})
t.Run("processed_with_rolling_update_strategy", func(t *testing.T) {
obj := internal.GenerateObj(strDaeWithRollingUpdate)
processed, res, err := testInstance.Process(&metadata.Service{}, obj)
assert.NoError(t, err)
assert.Equal(t, true, processed)

var buf bytes.Buffer
err = res.Write(&buf)
assert.NoError(t, err)

expectedTemplate := `spec:
updateStrategy:
rollingUpdate:
maxSurge: {{ .Values.fluentdElasticsearch.updateStrategy.rollingUpdate.maxSurge
}}
maxUnavailable: {{ .Values.fluentdElasticsearch.updateStrategy.rollingUpdate.maxUnavailable
| quote }}
type: {{ .Values.fluentdElasticsearch.updateStrategy.type | quote }}
selector:`
assert.Contains(t, buf.String(), expectedTemplate)

expectedValues := helmify.Values{
"fluentdElasticsearch": map[string]interface{}{
"updateStrategy": map[string]interface{}{
"type": "RollingUpdate",
"rollingUpdate": map[string]interface{}{
"maxUnavailable": "20%",
"maxSurge": int64(1),
},
},
},
}
values := res.Values()
assert.Equal(t, "RollingUpdate", values["fluentdElasticsearch"].(map[string]interface{})["updateStrategy"].(map[string]interface{})["type"])
ru := values["fluentdElasticsearch"].(map[string]interface{})["updateStrategy"].(map[string]interface{})["rollingUpdate"].(map[string]interface{})
assert.Equal(t, "20%", ru["maxUnavailable"])
assert.Equal(t, int64(1), ru["maxSurge"])
_ = expectedValues
})
t.Run("processed_with_on_delete_strategy", func(t *testing.T) {
obj := internal.GenerateObj(strDaeWithOnDelete)
processed, res, err := testInstance.Process(&metadata.Service{}, obj)
assert.NoError(t, err)
assert.Equal(t, true, processed)

var buf bytes.Buffer
err = res.Write(&buf)
assert.NoError(t, err)

expectedTemplate := `spec:
updateStrategy:
type: {{ .Values.fluentdElasticsearch.updateStrategy.type | quote }}
selector:`
assert.Contains(t, buf.String(), expectedTemplate)

values := res.Values()
assert.Equal(t, "OnDelete", values["fluentdElasticsearch"].(map[string]interface{})["updateStrategy"].(map[string]interface{})["type"])
})
t.Run("skipped", func(t *testing.T) {
obj := internal.TestNs
Expand Down