Files
atlantis/server/github_comment_renderer.go
Luke Kysow d1bda5ff28 Use cobra and viper for cli/config (#20)
* #15 Use cobra and viper for cli/config

* Run gofmt
2017-06-07 21:20:37 -07:00

192 lines
5.7 KiB
Go

package server
import (
"bytes"
"fmt"
"text/template"
)
// GithubCommentRenderer renders responses as GitHub comments
type GithubCommentRenderer struct{}
// CompiledTemplate represents a single template with both its source text and its compiled
// template. We compile the templates in init()
type CompiledTemplate struct {
text string
Template *template.Template
}
// PathResultRendered is used as an intermediary data container. We render the individual path results
// into Render and then pass around this struct to be rendered into the main templates
type PathResultRendered struct {
PathResult
Rendered string
}
// todo: once this is in its own package don't need to append Tmpl
var (
// If you add a template here, be sure to add it to init() so it gets compiled
SetupFailure *CompiledTemplate = &CompiledTemplate{
text: "**{{.Command}} Failed**:\n{{.Output}}\n" + logTmpl,
}
SinglePath *CompiledTemplate = &CompiledTemplate{
text: "{{ range $result := .Results }}{{$result.Rendered}}{{end}}\n" + logTmpl,
}
MultiPath *CompiledTemplate = &CompiledTemplate{
text: "Ran {{.Command}} in {{ len .Results }} directories:\n" +
"{{ range $path, $result := .Results }}" +
" * `{{$path}}`\n" + //todo: add result status
"{{end}}\n" +
"{{ range $path, $result := .Results }}" +
"Terraform {{$.Command}} for `{{$path}}`:\n" +
"{{$result.Rendered}}\n\n" +
"---\n{{end}}" +
logTmpl,
}
PlanSuccessTmpl *CompiledTemplate = &CompiledTemplate{
text: "```diff\n" +
"{{.TerraformOutput}}\n" +
"```\n\n" +
"* To **discard** this plan click [here]({{.LockURL}}).",
}
RunLockedFailureTmpl *CompiledTemplate = &CompiledTemplate{
text: "This plan is currently locked by #{{.LockingPullNum}}\n" +
"The locking plan must be applied or discarded before future plans can execute.",
}
TerraformFailureTmpl *CompiledTemplate = &CompiledTemplate{
text: "**Atlantis encountered an error while running...**\n" +
"```\n" +
"$ {{.Command}}\n" +
"{{.Output}}\n" +
"```",
}
EnvironmentFileNotFoundFailureTmpl *CompiledTemplate = &CompiledTemplate{
text: "Environment file did not exist {{.Filename}}",
}
EnvironmentErrorTmpl *CompiledTemplate = &CompiledTemplate{
text: "Please specify environment variable while running plan\n" +
"For example: `atlantis plan {environment_name}`\n" +
"*Environments that are available can be found under the `env/` folder of the terraform stack.*",
}
ApplySuccessTmpl *CompiledTemplate = &CompiledTemplate{
text: "```diff\n" +
"{{.Output}}\n" +
"```",
}
ApplyFailureTmpl *CompiledTemplate = &CompiledTemplate{
text: "**Apply Failed**:\n" +
"```bash\n" +
"$ {{.Command}}\n" +
"{{.Output}}\n" +
"{{.ErrorMessage}}\n" +
"```",
}
PullNotApprovedFailureTmpl *CompiledTemplate = &CompiledTemplate{
text: "Pull Request must be **Approved** before running apply.",
}
NoPlansFailureTmpl *CompiledTemplate = &CompiledTemplate{
text: "0 plans found",
}
ErrorTmpl *CompiledTemplate = &CompiledTemplate{
text: "**Atlantis encountered an error:**\n" +
"```\n" +
"{{.Error}}\n" +
"```\n" +
"Log:\n" +
"```\n" +
"{{.Log}}```",
}
GeneralErrorTmpl *CompiledTemplate = &CompiledTemplate{
text: "{{.Error}}",
}
)
var logTmpl = "{{if .Verbose}}\nAtlantis Log:\n```\n{{.Log}}```{{end}}\n"
func init() {
// compile the templates
for _, t := range []*CompiledTemplate{
SetupFailure,
SinglePath,
MultiPath,
PlanSuccessTmpl,
RunLockedFailureTmpl,
TerraformFailureTmpl,
EnvironmentFileNotFoundFailureTmpl,
EnvironmentErrorTmpl,
ApplySuccessTmpl,
ApplyFailureTmpl,
PullNotApprovedFailureTmpl,
NoPlansFailureTmpl,
ErrorTmpl,
GeneralErrorTmpl,
} {
t.Template = template.Must(template.New("").Parse(t.text))
}
}
func (g *GithubCommentRenderer) render(res ExecutionResult, log string, verbose bool) string {
commandStr := g.commandToString(res.Command)
if res.SetupError != nil {
renderedError := g.renderTemplate(res.SetupError.Template().Template, res.SetupError)
return g.renderTemplate(ErrorTmpl.Template, struct {
Error string
Log string
}{renderedError, log})
} else if res.SetupFailure != nil {
renderedFailure := g.renderTemplate(res.SetupFailure.Template().Template, res.SetupFailure)
return g.renderTemplate(SetupFailure.Template, struct {
Command string
Output string
Log string
Verbose bool
}{commandStr, renderedFailure, log, verbose})
} else {
hasErrors := false
for _, res := range res.PathResults {
if res.Status == "error" {
hasErrors = true
}
}
return g.renderPathOutputs(res.PathResults, commandStr, log, hasErrors || verbose)
}
}
func (g *GithubCommentRenderer) renderPathOutputs(pathResults []PathResult, command string, log string, verbose bool) string {
renderedOutputs := map[string]PathResultRendered{}
for _, result := range pathResults {
renderedOutputs[result.Path] = PathResultRendered{result, g.renderTemplate(result.Result.Template().Template, result.Result)}
}
var tmpl *template.Template
if len(renderedOutputs) == 1 {
tmpl = SinglePath.Template
} else {
tmpl = MultiPath.Template
}
return g.renderTemplate(tmpl, struct {
Results map[string]PathResultRendered
Log string
Verbose bool
Command string
}{renderedOutputs, log, verbose, command})
}
func (g *GithubCommentRenderer) renderTemplate(tmpl *template.Template, data interface{}) string {
buf := &bytes.Buffer{}
if err := tmpl.Execute(buf, data); err != nil {
return fmt.Sprintf("Failed to render template, this is a bug: %v", err)
}
return buf.String()
}
func (g *GithubCommentRenderer) commandToString(command CommandType) string {
switch command {
case Plan:
return "Plan"
case Apply:
return "Apply"
}
return "Unknown Command"
}