Skip to content

Commit

Permalink
Allow users to configure webhook message content with a template file (
Browse files Browse the repository at this point in the history
…#253)

* added new config variable to customize webhook template from file

* added new webhookTemplateFile variable in helm chart

* fix gofmt & ineffassign errors

* set template file as configmap in helm chart

* check webhook template file in ValidateWebhookConfig function + set template content message as debug + typo nthConfig

Co-authored-by: Steven Bressey <steven.bressey@mediakeys.com>
  • Loading branch information
supasteev0 and Steven Bressey authored Sep 17, 2020
1 parent ae0676e commit dad7670
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 7 deletions.
2 changes: 2 additions & 0 deletions config/helm/aws-node-termination-handler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Parameter | Description | Default
`webhookProxy` | Uses the specified HTTP(S) proxy for sending webhooks | ``
`webhookHeaders` | Replaces the default webhook headers. | `{"Content-type":"application/json"}`
`webhookTemplate` | Replaces the default webhook message template. | `{"text":"[NTH][Instance Interruption] EventID: {{ .EventID }} - Kind: {{ .Kind }} - Description: {{ .Description }} - State: {{ .State }} - Start Time: {{ .StartTime }}"}`
`webhookTemplateConfigMapName` | Pass Webhook template file as configmap | None
`webhookTemplateConfigMapKey` | Name of the template file stored in the configmap| None
`dryRun` | If true, only log if a node would be drained | `false`
`enableScheduledEventDraining` | [EXPERIMENTAL] If true, drain nodes before the maintenance window starts for an EC2 instance scheduled event | `false`
`enableSpotInterruptionDraining` | If false, do not drain nodes when the spot interruption termination notice is received | `true`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ spec:
- name: "uptime"
hostPath:
path: {{ .Values.procUptimeFile | default "/proc/uptime" | quote }}
{{- if and .Values.webhookTemplateConfigMapName .Values.webhookTemplateConfigMapKey }}
- name: "webhookTemplate"
configMap:
name: {{ .Values.webhookTemplateConfigMapName }}
{{- end }}
priorityClassName: {{ .Values.priorityClassName | quote }}
affinity:
nodeAffinity:
Expand Down Expand Up @@ -85,6 +90,10 @@ spec:
- name: "uptime"
mountPath: {{ .Values.procUptimeFile | default "/proc/uptime" | quote }}
readOnly: true
{{- if and .Values.webhookTemplateConfigMapName .Values.webhookTemplateConfigMapKey }}
- name: "webhookTemplate"
mountPath: "/config/"
{{- end }}
env:
- name: NODE_NAME
valueFrom:
Expand Down Expand Up @@ -125,6 +134,10 @@ spec:
{{- end }}
- name: WEBHOOK_HEADERS
value: {{ .Values.webhookHeaders | quote }}
{{- if and .Values.webhookTemplateConfigMapName .Values.webhookTemplateConfigMapKey }}
- name: WEBHOOK_TEMPLATE_FILE
value: {{ print "/config/" .Values.webhookTemplateConfigMapKey | quote }}
{{- end }}
- name: WEBHOOK_TEMPLATE
value: {{ .Values.webhookTemplate | quote }}
- name: DRY_RUN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ spec:
{{ $key }}: {{ $value | quote }}
{{- end }}
spec:
{{- if and .Values.webhookTemplateConfigMapName .Values.webhookTemplateConfigMapKey }}
volumes:
- name: "webhookTemplate"
configMap:
name: {{ .Values.webhookTemplateConfigMapName }}
{{- end }}
priorityClassName: {{ .Values.priorityClassName | quote }}
affinity:
nodeAffinity:
Expand All @@ -64,6 +70,11 @@ spec:
- name: {{ include "aws-node-termination-handler.name" . }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if and .Values.webhookTemplateConfigMapName .Values.webhookTemplateConfigMapKey }}
volumeMounts:
- name: "webhookTemplate"
mountPath: "/config/"
{{- end }}
env:
- name: NODE_NAME
valueFrom:
Expand Down Expand Up @@ -97,6 +108,10 @@ spec:
value: {{ .Values.webhookURL | quote }}
- name: WEBHOOK_HEADERS
value: {{ .Values.webhookHeaders | quote }}
{{- if and .Values.webhookTemplateConfigMapName .Values.webhookTemplateConfigMapKey }}
- name: WEBHOOK_TEMPLATE_FILE
value: {{ print "/config/" .Values.webhookTemplateConfigMapKey | quote }}
{{- end }}
- name: WEBHOOK_TEMPLATE
value: {{ .Values.webhookTemplate | quote }}
- name: DRY_RUN
Expand Down
7 changes: 7 additions & 0 deletions config/helm/aws-node-termination-handler/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ webhookProxy: ""
# webhookHeaders if specified, replaces the default webhook headers.
webhookHeaders: ""

# webhook template file will be fetched from given config map name
# if specified, replaces the default webhook message with the content of the template file
webhookTemplateConfigMapName: ""

# template file name stored in configmap
webhookTemplateConfigMapKey: ""

# webhookTemplate if specified, replaces the default webhook message template.
webhookTemplate: ""

Expand Down
3 changes: 3 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
webhookHeadersConfigKey = "WEBHOOK_HEADERS"
webhookHeadersDefault = `{"Content-type":"application/json"}`
webhookTemplateConfigKey = "WEBHOOK_TEMPLATE"
webhookTemplateFileConfigKey = "WEBHOOK_TEMPLATE_FILE"
webhookTemplateDefault = `{"text":"[NTH][Instance Interruption] EventID: {{ .EventID }} - Kind: {{ .Kind }} - Description: {{ .Description }} - Start Time: {{ .StartTime }}"}`
enableScheduledEventDrainingConfigKey = "ENABLE_SCHEDULED_EVENT_DRAINING"
enableScheduledEventDrainingDefault = false
Expand Down Expand Up @@ -79,6 +80,7 @@ type Config struct {
WebhookURL string
WebhookHeaders string
WebhookTemplate string
WebhookTemplateFile string
WebhookProxy string
EnableScheduledEventDraining bool
EnableSpotInterruptionDraining bool
Expand Down Expand Up @@ -116,6 +118,7 @@ func ParseCliArgs() (config Config, err error) {
flag.StringVar(&config.WebhookProxy, "webhook-proxy", getEnv(webhookProxyConfigKey, webhookProxyDefault), "If specified, uses the HTTP(S) proxy to send webhooks. Example: --webhook-url='tcp://<ip-or-dns-to-proxy>:<port>'")
flag.StringVar(&config.WebhookHeaders, "webhook-headers", getEnv(webhookHeadersConfigKey, webhookHeadersDefault), "If specified, replaces the default webhook headers.")
flag.StringVar(&config.WebhookTemplate, "webhook-template", getEnv(webhookTemplateConfigKey, webhookTemplateDefault), "If specified, replaces the default webhook message template.")
flag.StringVar(&config.WebhookTemplateFile, "webhook-template-file", getEnv(webhookTemplateFileConfigKey, ""), "If specified, replaces the default webhook message template with content from template file.")
flag.BoolVar(&config.EnableScheduledEventDraining, "enable-scheduled-event-draining", getBoolEnv(enableScheduledEventDrainingConfigKey, enableScheduledEventDrainingDefault), "[EXPERIMENTAL] If true, drain nodes before the maintenance window starts for an EC2 instance scheduled event")
flag.BoolVar(&config.EnableSpotInterruptionDraining, "enable-spot-interruption-draining", getBoolEnv(enableSpotInterruptionDrainingConfigKey, enableSpotInterruptionDrainingDefault), "If false, do not drain nodes when the spot interruption termination notice is received")
flag.IntVar(&config.MetadataTries, "metadata-tries", getIntEnv(metadataTriesConfigKey, metadataTriesDefault), "The number of times to try requesting metadata. If you would like 2 retries, set metadata-tries to 3.")
Expand Down
41 changes: 34 additions & 7 deletions pkg/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"text/template"
Expand All @@ -34,9 +35,22 @@ type combinedDrainData struct {
}

// Post makes a http post to send drain event data to webhook url
func Post(additionalInfo ec2metadata.NodeMetadata, event *monitor.InterruptionEvent, nthconfig config.Config) {
func Post(additionalInfo ec2metadata.NodeMetadata, event *monitor.InterruptionEvent, nthConfig config.Config) {
var webhookTemplateContent string

if nthConfig.WebhookTemplateFile != "" {
content, err := ioutil.ReadFile(nthConfig.WebhookTemplateFile)
if err != nil {
log.Log().Msgf("Webhook Error: Could not read template file %s - %s", nthConfig.WebhookTemplateFile, err)
return
}
webhookTemplateContent = string(content)
log.Debug().Msgf("Template file content - %s", webhookTemplateContent)
} else {
webhookTemplateContent = nthConfig.WebhookTemplate
}

webhookTemplate, err := template.New("message").Parse(nthconfig.WebhookTemplate)
webhookTemplate, err := template.New("message").Parse(webhookTemplateContent)
if err != nil {
log.Log().Msgf("Webhook Error: Template parsing failed - %s", err)
return
Expand All @@ -51,14 +65,14 @@ func Post(additionalInfo ec2metadata.NodeMetadata, event *monitor.InterruptionEv
return
}

request, err := http.NewRequest("POST", nthconfig.WebhookURL, &byteBuffer)
request, err := http.NewRequest("POST", nthConfig.WebhookURL, &byteBuffer)
if err != nil {
log.Log().Msgf("Webhook Error: Http NewRequest failed - %s", err)
return
}

headerMap := make(map[string]interface{})
err = json.Unmarshal([]byte(nthconfig.WebhookHeaders), &headerMap)
err = json.Unmarshal([]byte(nthConfig.WebhookHeaders), &headerMap)
if err != nil {
log.Log().Msgf("Webhook Error: Header Unmarshal failed - %s", err)
return
Expand All @@ -72,10 +86,10 @@ func Post(additionalInfo ec2metadata.NodeMetadata, event *monitor.InterruptionEv
Transport: &http.Transport{
IdleConnTimeout: 1 * time.Second,
Proxy: func(req *http.Request) (*url.URL, error) {
if nthconfig.WebhookProxy == "" {
if nthConfig.WebhookProxy == "" {
return nil, nil
}
proxy, err := url.Parse(nthconfig.WebhookProxy)
proxy, err := url.Parse(nthConfig.WebhookProxy)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -104,7 +118,20 @@ func ValidateWebhookConfig(nthConfig config.Config) error {
if nthConfig.WebhookURL == "" {
return nil
}
webhookTemplate, err := template.New("message").Parse(nthConfig.WebhookTemplate)

var webhookTemplateContent string

if nthConfig.WebhookTemplateFile != "" {
content, err := ioutil.ReadFile(nthConfig.WebhookTemplateFile)
if err != nil {
return fmt.Errorf("Webhook Error: Could not read template file %w", err)
}
webhookTemplateContent = string(content)
} else {
webhookTemplateContent = nthConfig.WebhookTemplate
}

webhookTemplate, err := template.New("message").Parse(webhookTemplateContent)
if err != nil {
return fmt.Errorf("Unable to parse webhook template: %w", err)
}
Expand Down

0 comments on commit dad7670

Please sign in to comment.