Skip to content

Commit

Permalink
Merge pull request #58 from mercari/dtan4/optional-disable-html-escape
Browse files Browse the repository at this point in the history
Add a option to disable HTML escape for Terraform command output in GitHub
  • Loading branch information
dtan4 authored Jan 6, 2020
2 parents b557fb0 + 2c2c1ff commit 8396848
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 86 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,35 @@ terraform:
# ...
```

Sometimes you may want not to HTML-escape Terraform command outputs.
For example, when you use code block to print command output, it's better to use raw characters instead of character references (e.g. `-/+` -> `-/+`, `"` -> `"`).

You can disable HTML escape by adding `use_raw_output: true` configuration.
With this configuration, Terraform doesn't HTML-escape any Terraform output.

~~~yaml
---
# ...
terraform:
use_raw_output: true
# ...
plan:
template: |
{{ .Title }} <sup>[CI link]( {{ .Link }} )</sup>
{{ .Message }}
{{if .Result}}
```
{{ .Result }}
```
{{end}}
<details><summary>Details (Click me)</summary>
```
{{ .Body }}
```
# ...
~~~

</details>

<details>
Expand Down
9 changes: 5 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ type TypetalkNotifier struct {

// Terraform represents terraform configurations
type Terraform struct {
Default Default `yaml:"default"`
Fmt Fmt `yaml:"fmt"`
Plan Plan `yaml:"plan"`
Apply Apply `yaml:"apply"`
Default Default `yaml:"default"`
Fmt Fmt `yaml:"fmt"`
Plan Plan `yaml:"plan"`
Apply Apply `yaml:"apply"`
UseRawOutput bool `yaml:"use_raw_output,omitempty"`
}

// Default is a default setting for terraform commands
Expand Down
2 changes: 2 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func TestLoadFile(t *testing.T) {
Apply: Apply{
Template: "",
},
UseRawOutput: false,
},
path: "../example.tfnotify.yaml",
},
Expand Down Expand Up @@ -99,6 +100,7 @@ func TestLoadFile(t *testing.T) {
Apply: Apply{
Template: "",
},
UseRawOutput: false,
},
path: "../example-with-destroy.tfnotify.yaml",
},
Expand Down
21 changes: 21 additions & 0 deletions example-use-raw-output.tfnotify.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
ci: circleci
notifier:
github:
token: $GITHUB_TOKEN
repository:
owner: "mercari"
name: "tfnotify"
terraform:
use_raw_output: true
plan:
template: |
{{ .Title }}
{{ .Message }}
{{if .Result}}
<pre><code>{{ .Result }}
</pre></code>
{{end}}
<details><summary>Details (Click me)</summary>
<pre><code>{{ .Body }}
</pre></code></details>
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func (t *tfnotify) Run() error {
},
CI: ci.URL,
Parser: t.parser,
UseRawOutput: t.config.Terraform.UseRawOutput,
Template: t.template,
DestroyWarningTemplate: t.destroyWarningTemplate,
WarnDestroy: t.warnDestroy,
Expand Down
17 changes: 9 additions & 8 deletions notifier/github/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ type Client struct {

// Config is a configuration for GitHub client
type Config struct {
Token string
BaseURL string
Owner string
Repo string
PR PullRequest
CI string
Parser terraform.Parser
WarnDestroy bool
Token string
BaseURL string
Owner string
Repo string
PR PullRequest
CI string
Parser terraform.Parser
UseRawOutput bool
WarnDestroy bool
// Template is used for all Terraform command output
Template terraform.Template
// DestroyWarningTemplate is used only for additional warning
Expand Down
22 changes: 12 additions & 10 deletions notifier/github/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ func (g *NotifyService) Notify(body string) (exit int, err error) {
}

template.SetValue(terraform.CommonTemplate{
Title: cfg.PR.Title,
Message: cfg.PR.Message,
Result: result.Result,
Body: body,
Link: cfg.CI,
Title: cfg.PR.Title,
Message: cfg.PR.Message,
Result: result.Result,
Body: body,
Link: cfg.CI,
UseRawOutput: cfg.UseRawOutput,
})
body, err = template.Execute()
if err != nil {
Expand Down Expand Up @@ -70,11 +71,12 @@ func (g *NotifyService) notifyDestoryWarning(body string, result terraform.Parse
cfg := g.client.Config
destroyWarningTemplate := g.client.Config.DestroyWarningTemplate
destroyWarningTemplate.SetValue(terraform.CommonTemplate{
Title: cfg.PR.DestroyWarningTitle,
Message: cfg.PR.DestroyWarningMessage,
Result: result.Result,
Body: body,
Link: cfg.CI,
Title: cfg.PR.DestroyWarningTitle,
Message: cfg.PR.DestroyWarningMessage,
Result: result.Result,
Body: body,
Link: cfg.CI,
UseRawOutput: cfg.UseRawOutput,
})
body, err := destroyWarningTemplate.Execute()
if err != nil {
Expand Down
138 changes: 77 additions & 61 deletions terraform/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package terraform

import (
"bytes"
"html/template"
htmltemplate "html/template"
texttemplate "text/template"
)

const (
Expand Down Expand Up @@ -101,11 +102,12 @@ type Template interface {

// CommonTemplate represents template entities
type CommonTemplate struct {
Title string
Message string
Result string
Body string
Link string
Title string
Message string
Result string
Body string
Link string
UseRawOutput bool
}

// DefaultTemplate is a default template for terraform commands
Expand Down Expand Up @@ -193,104 +195,118 @@ func NewApplyTemplate(template string) *ApplyTemplate {
}
}

// Execute binds the execution result of terraform command into tepmlate
func (t *DefaultTemplate) Execute() (resp string, err error) {
tpl, err := template.New("default").Parse(t.Template)
if err != nil {
return resp, err
}
func generateOutput(kind, template string, data map[string]interface{}, useRawOutput bool) (string, error) {
var b bytes.Buffer
if err := tpl.Execute(&b, map[string]interface{}{

if useRawOutput {
tpl, err := texttemplate.New(kind).Parse(template)
if err != nil {
return "", err
}
if err := tpl.Execute(&b, data); err != nil {
return "", err
}
} else {
tpl, err := htmltemplate.New(kind).Parse(template)
if err != nil {
return "", err
}
if err := tpl.Execute(&b, data); err != nil {
return "", err
}
}

return b.String(), nil
}

// Execute binds the execution result of terraform command into tepmlate
func (t *DefaultTemplate) Execute() (string, error) {
data := map[string]interface{}{
"Title": t.Title,
"Message": t.Message,
"Result": "",
"Body": t.Result,
"Link": t.Link,
}); err != nil {
return resp, err
}
resp = b.String()
return resp, err
}

// Execute binds the execution result of terraform fmt into tepmlate
func (t *FmtTemplate) Execute() (resp string, err error) {
tpl, err := template.New("fmt").Parse(t.Template)
resp, err := generateOutput("default", t.Template, data, t.UseRawOutput)
if err != nil {
return resp, err
return "", err
}
var b bytes.Buffer
if err := tpl.Execute(&b, map[string]interface{}{

return resp, nil
}

// Execute binds the execution result of terraform fmt into tepmlate
func (t *FmtTemplate) Execute() (string, error) {
data := map[string]interface{}{
"Title": t.Title,
"Message": t.Message,
"Result": "",
"Body": t.Result,
"Link": t.Link,
}); err != nil {
return resp, err
}
resp = b.String()
return resp, err
}

// Execute binds the execution result of terraform plan into tepmlate
func (t *PlanTemplate) Execute() (resp string, err error) {
tpl, err := template.New("plan").Parse(t.Template)
resp, err := generateOutput("fmt", t.Template, data, t.UseRawOutput)
if err != nil {
return resp, err
return "", err
}
var b bytes.Buffer
if err := tpl.Execute(&b, map[string]interface{}{

return resp, nil
}

// Execute binds the execution result of terraform plan into tepmlate
func (t *PlanTemplate) Execute() (string, error) {
data := map[string]interface{}{
"Title": t.Title,
"Message": t.Message,
"Result": t.Result,
"Body": t.Body,
"Link": t.Link,
}); err != nil {
return resp, err
}
resp = b.String()
return resp, err
}

// Execute binds the execution result of terraform plan into template
func (t *DestroyWarningTemplate) Execute() (resp string, err error) {
tpl, err := template.New("destroy_warning").Parse(t.Template)
resp, err := generateOutput("plan", t.Template, data, t.UseRawOutput)
if err != nil {
return resp, err
return "", err
}
var b bytes.Buffer
if err := tpl.Execute(&b, map[string]interface{}{

return resp, nil
}

// Execute binds the execution result of terraform plan into template
func (t *DestroyWarningTemplate) Execute() (string, error) {
data := map[string]interface{}{
"Title": t.Title,
"Message": t.Message,
"Result": t.Result,
"Body": t.Body,
"Link": t.Link,
}); err != nil {
return resp, err
}
resp = b.String()
return resp, err
}

// Execute binds the execution result of terraform apply into tepmlate
func (t *ApplyTemplate) Execute() (resp string, err error) {
tpl, err := template.New("apply").Parse(t.Template)
resp, err := generateOutput("destroy_warning", t.Template, data, t.UseRawOutput)
if err != nil {
return resp, err
return "", err
}
var b bytes.Buffer
if err := tpl.Execute(&b, map[string]interface{}{

return resp, nil
}

// Execute binds the execution result of terraform apply into tepmlate
func (t *ApplyTemplate) Execute() (string, error) {
data := map[string]interface{}{
"Title": t.Title,
"Message": t.Message,
"Result": t.Result,
"Body": t.Body,
"Link": t.Link,
}); err != nil {
return resp, err
}
resp = b.String()
return resp, err

resp, err := generateOutput("apply", t.Template, data, t.UseRawOutput)
if err != nil {
return "", err
}

return resp, nil
}

// SetValue sets template entities to CommonTemplate
Expand Down
Loading

0 comments on commit 8396848

Please sign in to comment.