diff --git a/config/helm/aws-node-termination-handler/README.md b/config/helm/aws-node-termination-handler/README.md index d4c9f886..a78ea8ab 100644 --- a/config/helm/aws-node-termination-handler/README.md +++ b/config/helm/aws-node-termination-handler/README.md @@ -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` diff --git a/config/helm/aws-node-termination-handler/templates/daemonset.linux.yaml b/config/helm/aws-node-termination-handler/templates/daemonset.linux.yaml index de4e5b58..d4b7f387 100644 --- a/config/helm/aws-node-termination-handler/templates/daemonset.linux.yaml +++ b/config/helm/aws-node-termination-handler/templates/daemonset.linux.yaml @@ -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: @@ -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: @@ -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 diff --git a/config/helm/aws-node-termination-handler/templates/daemonset.windows.yaml b/config/helm/aws-node-termination-handler/templates/daemonset.windows.yaml index e8b26d8a..2f834909 100644 --- a/config/helm/aws-node-termination-handler/templates/daemonset.windows.yaml +++ b/config/helm/aws-node-termination-handler/templates/daemonset.windows.yaml @@ -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: @@ -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: @@ -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 diff --git a/config/helm/aws-node-termination-handler/values.yaml b/config/helm/aws-node-termination-handler/values.yaml index 9ce201fa..301b70a2 100644 --- a/config/helm/aws-node-termination-handler/values.yaml +++ b/config/helm/aws-node-termination-handler/values.yaml @@ -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: "" diff --git a/pkg/config/config.go b/pkg/config/config.go index 0ecf02cc..1da23c4e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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 @@ -79,6 +80,7 @@ type Config struct { WebhookURL string WebhookHeaders string WebhookTemplate string + WebhookTemplateFile string WebhookProxy string EnableScheduledEventDraining bool EnableSpotInterruptionDraining bool @@ -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://:'") 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.") diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index 247d34f9..c8742dc0 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -17,6 +17,7 @@ import ( "bytes" "encoding/json" "fmt" + "io/ioutil" "net/http" "net/url" "text/template" @@ -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 @@ -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 @@ -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 } @@ -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) }