mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-31 03:28:39 +00:00
* feat: atlantis import * feat: atlantis import * regenerate mock comment builder * remove duplicate err check * instrumented import command runner/builder * atlantis import subcommand accept args before hyphen * fix link checker * docs: review feedback * fix atlantis import options order * Update runatlantis.io/docs/using-atlantis.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/using-atlantis.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/using-atlantis.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/using-atlantis.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/using-atlantis.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update server/events/comment_parser.go Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update server/events/comment_parser.go Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/server-side-repo-config.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update runatlantis.io/docs/command-requirements.md Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update server/events/command_requirement_handler.go Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Update server/events/command_requirement_handler_test.go Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * fix test import usage * fix e2e expected txt * fix doc link * docs: workflow import stage/step * docs fixup Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com>
299 lines
11 KiB
Go
299 lines
11 KiB
Go
// Copyright 2017 HootSuite Media Inc.
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the License);
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an AS IS BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
// Modified hereafter by contributors to runatlantis/atlantis.
|
||
|
||
package events
|
||
|
||
import (
|
||
"bytes"
|
||
"embed"
|
||
"fmt"
|
||
"strings"
|
||
"text/template"
|
||
|
||
"github.com/Masterminds/sprig/v3"
|
||
"github.com/runatlantis/atlantis/server/events/command"
|
||
"github.com/runatlantis/atlantis/server/events/models"
|
||
"golang.org/x/text/cases"
|
||
"golang.org/x/text/language"
|
||
)
|
||
|
||
var (
|
||
planCommandTitle = command.Plan.TitleString()
|
||
applyCommandTitle = command.Apply.TitleString()
|
||
policyCheckCommandTitle = command.PolicyCheck.TitleString()
|
||
approvePoliciesCommandTitle = command.ApprovePolicies.TitleString()
|
||
versionCommandTitle = command.Version.TitleString()
|
||
importCommandTitle = command.Import.TitleString()
|
||
// maxUnwrappedLines is the maximum number of lines the Terraform output
|
||
// can be before we wrap it in an expandable template.
|
||
maxUnwrappedLines = 12
|
||
|
||
//go:embed templates/*
|
||
templatesFS embed.FS
|
||
)
|
||
|
||
// MarkdownRenderer renders responses as markdown.
|
||
type MarkdownRenderer struct {
|
||
// GitlabSupportsCommonMark is true if the version of GitLab we're
|
||
// using supports the CommonMark markdown format.
|
||
// If we're not configured with a GitLab client, this will be false.
|
||
GitlabSupportsCommonMark bool
|
||
DisableApplyAll bool
|
||
DisableApply bool
|
||
DisableMarkdownFolding bool
|
||
DisableRepoLocking bool
|
||
EnableDiffMarkdownFormat bool
|
||
MarkdownTemplates *template.Template
|
||
}
|
||
|
||
// commonData is data that all responses have.
|
||
type commonData struct {
|
||
Command string
|
||
Verbose bool
|
||
Log string
|
||
PlansDeleted bool
|
||
DisableApplyAll bool
|
||
DisableApply bool
|
||
DisableRepoLocking bool
|
||
EnableDiffMarkdownFormat bool
|
||
}
|
||
|
||
// errData is data about an error response.
|
||
type errData struct {
|
||
Error string
|
||
commonData
|
||
}
|
||
|
||
// failureData is data about a failure response.
|
||
type failureData struct {
|
||
Failure string
|
||
commonData
|
||
}
|
||
|
||
// resultData is data about a successful response.
|
||
type resultData struct {
|
||
Results []projectResultTmplData
|
||
commonData
|
||
}
|
||
|
||
type planSuccessData struct {
|
||
models.PlanSuccess
|
||
PlanSummary string
|
||
PlanWasDeleted bool
|
||
DisableApply bool
|
||
DisableRepoLocking bool
|
||
EnableDiffMarkdownFormat bool
|
||
}
|
||
|
||
type policyCheckSuccessData struct {
|
||
models.PolicyCheckSuccess
|
||
PolicyCheckSummary string
|
||
}
|
||
|
||
type projectResultTmplData struct {
|
||
Workspace string
|
||
RepoRelDir string
|
||
ProjectName string
|
||
Rendered string
|
||
}
|
||
|
||
// Initialize templates
|
||
func GetMarkdownRenderer(
|
||
GitlabSupportsCommonMark bool,
|
||
DisableApplyAll bool,
|
||
DisableApply bool,
|
||
DisableMarkdownFolding bool,
|
||
DisableRepoLocking bool,
|
||
EnableDiffMarkdownFormat bool,
|
||
MarkdownTemplateOverridesDir string,
|
||
) *MarkdownRenderer {
|
||
var templates *template.Template
|
||
templates, _ = template.New("").Funcs(sprig.TxtFuncMap()).ParseFS(templatesFS, "templates/*.tmpl")
|
||
if overrides, err := templates.ParseGlob(fmt.Sprintf("%s/*.tmpl", MarkdownTemplateOverridesDir)); err == nil {
|
||
// doesn't override if templates directory doesn't exist
|
||
templates = overrides
|
||
}
|
||
return &MarkdownRenderer{
|
||
GitlabSupportsCommonMark: GitlabSupportsCommonMark,
|
||
DisableApplyAll: DisableApplyAll,
|
||
DisableMarkdownFolding: DisableMarkdownFolding,
|
||
DisableApply: DisableApply,
|
||
DisableRepoLocking: DisableRepoLocking,
|
||
EnableDiffMarkdownFormat: EnableDiffMarkdownFormat,
|
||
MarkdownTemplates: templates,
|
||
}
|
||
}
|
||
|
||
// Render formats the data into a markdown string.
|
||
// nolint: interfacer
|
||
func (m *MarkdownRenderer) Render(res command.Result, cmdName command.Name, log string, verbose bool, vcsHost models.VCSHostType) string {
|
||
commandStr := cases.Title(language.English).String(strings.Replace(cmdName.String(), "_", " ", -1))
|
||
common := commonData{
|
||
Command: commandStr,
|
||
Verbose: verbose,
|
||
Log: log,
|
||
PlansDeleted: res.PlansDeleted,
|
||
DisableApplyAll: m.DisableApplyAll || m.DisableApply,
|
||
DisableApply: m.DisableApply,
|
||
DisableRepoLocking: m.DisableRepoLocking,
|
||
EnableDiffMarkdownFormat: m.EnableDiffMarkdownFormat,
|
||
}
|
||
|
||
templates := m.MarkdownTemplates
|
||
|
||
if res.Error != nil {
|
||
return m.renderTemplate(templates.Lookup("unwrappedErrWithLog"), errData{res.Error.Error(), common})
|
||
}
|
||
if res.Failure != "" {
|
||
return m.renderTemplate(templates.Lookup("failureWithLog"), failureData{res.Failure, common})
|
||
}
|
||
return m.renderProjectResults(res.ProjectResults, common, vcsHost)
|
||
}
|
||
|
||
func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult, common commonData, vcsHost models.VCSHostType) string {
|
||
var resultsTmplData []projectResultTmplData
|
||
numPlanSuccesses := 0
|
||
numPolicyCheckSuccesses := 0
|
||
numVersionSuccesses := 0
|
||
|
||
templates := m.MarkdownTemplates
|
||
|
||
for _, result := range results {
|
||
resultData := projectResultTmplData{
|
||
Workspace: result.Workspace,
|
||
RepoRelDir: result.RepoRelDir,
|
||
ProjectName: result.ProjectName,
|
||
}
|
||
if result.Error != nil {
|
||
tmpl := templates.Lookup("unwrappedErr")
|
||
if m.shouldUseWrappedTmpl(vcsHost, result.Error.Error()) {
|
||
tmpl = templates.Lookup("wrappedErr")
|
||
}
|
||
resultData.Rendered = m.renderTemplate(tmpl, struct {
|
||
Command string
|
||
Error string
|
||
}{
|
||
Command: common.Command,
|
||
Error: result.Error.Error(),
|
||
})
|
||
} else if result.Failure != "" {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("failure"), struct {
|
||
Command string
|
||
Failure string
|
||
}{
|
||
Command: common.Command,
|
||
Failure: result.Failure,
|
||
})
|
||
} else if result.PlanSuccess != nil {
|
||
if m.shouldUseWrappedTmpl(vcsHost, result.PlanSuccess.TerraformOutput) {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("planSuccessWrapped"), planSuccessData{PlanSuccess: *result.PlanSuccess, PlanSummary: result.PlanSuccess.Summary(), PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply, DisableRepoLocking: common.DisableRepoLocking, EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat})
|
||
} else {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("planSuccessUnwrapped"), planSuccessData{PlanSuccess: *result.PlanSuccess, PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply, DisableRepoLocking: common.DisableRepoLocking, EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat})
|
||
}
|
||
numPlanSuccesses++
|
||
} else if result.PolicyCheckSuccess != nil {
|
||
if m.shouldUseWrappedTmpl(vcsHost, result.PolicyCheckSuccess.PolicyCheckOutput) {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("policyCheckSuccessWrapped"), policyCheckSuccessData{PolicyCheckSuccess: *result.PolicyCheckSuccess, PolicyCheckSummary: result.PolicyCheckSuccess.Summary()})
|
||
} else {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("policyCheckSuccessUnwrapped"), policyCheckSuccessData{PolicyCheckSuccess: *result.PolicyCheckSuccess})
|
||
}
|
||
numPolicyCheckSuccesses++
|
||
} else if result.ApplySuccess != "" {
|
||
if m.shouldUseWrappedTmpl(vcsHost, result.ApplySuccess) {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("applyWrappedSuccess"), struct{ Output string }{result.ApplySuccess})
|
||
} else {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("applyUnwrappedSuccess"), struct{ Output string }{result.ApplySuccess})
|
||
}
|
||
} else if result.VersionSuccess != "" {
|
||
if m.shouldUseWrappedTmpl(vcsHost, result.VersionSuccess) {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("versionWrappedSuccess"), struct{ Output string }{result.VersionSuccess})
|
||
} else {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("versionUnwrappedSuccess"), struct{ Output string }{result.VersionSuccess})
|
||
}
|
||
numVersionSuccesses++
|
||
} else if result.ImportSuccess != nil {
|
||
if m.shouldUseWrappedTmpl(vcsHost, result.ImportSuccess.Output) {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("importSuccessWrapped"), result.ImportSuccess)
|
||
} else {
|
||
resultData.Rendered = m.renderTemplate(templates.Lookup("importSuccessUnwrapped"), result.ImportSuccess)
|
||
}
|
||
} else {
|
||
resultData.Rendered = "Found no template. This is a bug!"
|
||
}
|
||
resultsTmplData = append(resultsTmplData, resultData)
|
||
}
|
||
|
||
var tmpl *template.Template
|
||
switch {
|
||
case len(resultsTmplData) == 1 && common.Command == planCommandTitle && numPlanSuccesses > 0:
|
||
tmpl = templates.Lookup("singleProjectPlanSuccess")
|
||
case len(resultsTmplData) == 1 && common.Command == planCommandTitle && numPlanSuccesses == 0:
|
||
tmpl = templates.Lookup("singleProjectPlanUnsuccessful")
|
||
case len(resultsTmplData) == 1 && common.Command == policyCheckCommandTitle && numPolicyCheckSuccesses > 0:
|
||
tmpl = templates.Lookup("singleProjectPlanSuccess")
|
||
case len(resultsTmplData) == 1 && common.Command == policyCheckCommandTitle && numPolicyCheckSuccesses == 0:
|
||
tmpl = templates.Lookup("singleProjectPlanUnsuccessful")
|
||
case len(resultsTmplData) == 1 && common.Command == versionCommandTitle && numVersionSuccesses > 0:
|
||
tmpl = templates.Lookup("singleProjectVersionSuccess")
|
||
case len(resultsTmplData) == 1 && common.Command == versionCommandTitle && numVersionSuccesses == 0:
|
||
tmpl = templates.Lookup("singleProjectVersionUnsuccessful")
|
||
case len(resultsTmplData) == 1 && common.Command == applyCommandTitle:
|
||
tmpl = templates.Lookup("singleProjectApply")
|
||
case len(resultsTmplData) == 1 && common.Command == importCommandTitle:
|
||
tmpl = templates.Lookup("singleProjectImport")
|
||
case common.Command == planCommandTitle,
|
||
common.Command == policyCheckCommandTitle:
|
||
tmpl = templates.Lookup("multiProjectPlan")
|
||
case common.Command == approvePoliciesCommandTitle:
|
||
tmpl = templates.Lookup("approveAllProjects")
|
||
case common.Command == applyCommandTitle:
|
||
tmpl = templates.Lookup("multiProjectApply")
|
||
case common.Command == versionCommandTitle:
|
||
tmpl = templates.Lookup("multiProjectVersion")
|
||
case common.Command == importCommandTitle:
|
||
tmpl = templates.Lookup("multiProjectImport")
|
||
default:
|
||
return "no template matched–this is a bug"
|
||
}
|
||
return m.renderTemplate(tmpl, resultData{resultsTmplData, common})
|
||
}
|
||
|
||
// shouldUseWrappedTmpl returns true if we should use the wrapped markdown
|
||
// templates that collapse the output to make the comment smaller on initial
|
||
// load. Some VCS providers or versions of VCS providers don't support this
|
||
// syntax.
|
||
func (m *MarkdownRenderer) shouldUseWrappedTmpl(vcsHost models.VCSHostType, output string) bool {
|
||
if m.DisableMarkdownFolding {
|
||
return false
|
||
}
|
||
|
||
// Bitbucket Cloud and Server don't support the folding markdown syntax.
|
||
if vcsHost == models.BitbucketServer || vcsHost == models.BitbucketCloud {
|
||
return false
|
||
}
|
||
|
||
if vcsHost == models.Gitlab && !m.GitlabSupportsCommonMark {
|
||
return false
|
||
}
|
||
|
||
return strings.Count(output, "\n") > maxUnwrappedLines
|
||
}
|
||
|
||
func (m *MarkdownRenderer) 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()
|
||
}
|