mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-29 18:28:22 +00:00
Signed-off-by: RB <7775707+nitrocode@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: RB <7775707+nitrocode@users.noreply.github.com>
569 lines
22 KiB
Go
569 lines
22 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 (
|
||
"fmt"
|
||
"strconv"
|
||
|
||
"github.com/drmaxgit/go-azuredevops/azuredevops"
|
||
"github.com/google/go-github/v71/github"
|
||
"github.com/pkg/errors"
|
||
"github.com/runatlantis/atlantis/server/core/config/valid"
|
||
"github.com/runatlantis/atlantis/server/events/command"
|
||
"github.com/runatlantis/atlantis/server/events/models"
|
||
"github.com/runatlantis/atlantis/server/events/vcs"
|
||
"github.com/runatlantis/atlantis/server/events/vcs/gitea"
|
||
"github.com/runatlantis/atlantis/server/logging"
|
||
"github.com/runatlantis/atlantis/server/metrics"
|
||
"github.com/runatlantis/atlantis/server/recovery"
|
||
"github.com/runatlantis/atlantis/server/utils"
|
||
tally "github.com/uber-go/tally/v4"
|
||
gitlab "gitlab.com/gitlab-org/api/client-go"
|
||
)
|
||
|
||
const (
|
||
ShutdownComment = "Atlantis server is shutting down, please try again later."
|
||
)
|
||
|
||
//go:generate pegomock generate github.com/runatlantis/atlantis/server/events --package mocks -o mocks/mock_command_runner.go CommandRunner
|
||
|
||
// CommandRunner is the first step after a command request has been parsed.
|
||
type CommandRunner interface {
|
||
// RunCommentCommand is the first step after a command request has been parsed.
|
||
// It handles gathering additional information needed to execute the command
|
||
// and then calling the appropriate services to finish executing the command.
|
||
RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, maybePull *models.PullRequest, user models.User, pullNum int, cmd *CommentCommand)
|
||
RunAutoplanCommand(baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User)
|
||
}
|
||
|
||
//go:generate pegomock generate github.com/runatlantis/atlantis/server/events --package mocks -o mocks/mock_github_pull_getter.go GithubPullGetter
|
||
|
||
// GithubPullGetter makes API calls to get pull requests.
|
||
type GithubPullGetter interface {
|
||
// GetPullRequest gets the pull request with id pullNum for the repo.
|
||
GetPullRequest(logger logging.SimpleLogging, repo models.Repo, pullNum int) (*github.PullRequest, error)
|
||
}
|
||
|
||
//go:generate pegomock generate github.com/runatlantis/atlantis/server/events --package mocks -o mocks/mock_azuredevops_pull_getter.go AzureDevopsPullGetter
|
||
|
||
// AzureDevopsPullGetter makes API calls to get pull requests.
|
||
type AzureDevopsPullGetter interface {
|
||
// GetPullRequest gets the pull request with id pullNum for the repo.
|
||
GetPullRequest(logger logging.SimpleLogging, repo models.Repo, pullNum int) (*azuredevops.GitPullRequest, error)
|
||
}
|
||
|
||
//go:generate pegomock generate github.com/runatlantis/atlantis/server/events --package mocks -o mocks/mock_gitlab_merge_request_getter.go GitlabMergeRequestGetter
|
||
|
||
// GitlabMergeRequestGetter makes API calls to get merge requests.
|
||
type GitlabMergeRequestGetter interface {
|
||
// GetMergeRequest gets the pull request with the id pullNum for the repo.
|
||
GetMergeRequest(logger logging.SimpleLogging, repoFullName string, pullNum int) (*gitlab.MergeRequest, error)
|
||
}
|
||
|
||
// CommentCommandRunner runs individual command workflows.
|
||
type CommentCommandRunner interface {
|
||
Run(*command.Context, *CommentCommand)
|
||
}
|
||
|
||
func buildCommentCommandRunner(
|
||
cmdRunner *DefaultCommandRunner,
|
||
cmdName command.Name,
|
||
) CommentCommandRunner {
|
||
// panic here, we want to fail fast and hard since
|
||
// this would be an internal service configuration error.
|
||
runner, ok := cmdRunner.CommentCommandRunnerByCmd[cmdName]
|
||
|
||
if !ok {
|
||
panic(fmt.Sprintf("command runner not configured for command %s", cmdName.String()))
|
||
}
|
||
|
||
return runner
|
||
}
|
||
|
||
// DefaultCommandRunner is the first step when processing a comment command.
|
||
type DefaultCommandRunner struct {
|
||
VCSClient vcs.Client `validate:"required"`
|
||
GithubPullGetter GithubPullGetter
|
||
AzureDevopsPullGetter AzureDevopsPullGetter
|
||
GitlabMergeRequestGetter GitlabMergeRequestGetter
|
||
GiteaPullGetter *gitea.GiteaClient
|
||
// User config option: Disables autoplan when a pull request is opened or updated.
|
||
DisableAutoplan bool
|
||
DisableAutoplanLabel string
|
||
EventParser EventParsing
|
||
// User config option: Fail and do not run the Atlantis command request if any of the pre workflow hooks error
|
||
FailOnPreWorkflowHookError bool
|
||
Logger logging.SimpleLogging `validate:"required"`
|
||
GlobalCfg valid.GlobalCfg `validate:"required"`
|
||
StatsScope tally.Scope `validate:"required"`
|
||
// User config option: controls whether to operate on pull requests from forks.
|
||
AllowForkPRs bool
|
||
// ParallelPoolSize controls the size of the wait group used to run
|
||
// parallel plans and applies (if enabled).
|
||
ParallelPoolSize int
|
||
// AllowForkPRsFlag is the name of the flag that controls fork PR's. We use
|
||
// this in our error message back to the user on a forked PR so they know
|
||
// how to enable this functionality.
|
||
AllowForkPRsFlag string
|
||
// User config option: controls whether to comment on Fork PRs when AllowForkPRs = False
|
||
SilenceForkPRErrors bool
|
||
// SilenceForkPRErrorsFlag is the name of the flag that controls fork PR's. We use
|
||
// this in our error message back to the user on a forked PR so they know
|
||
// how to disable error comment
|
||
SilenceForkPRErrorsFlag string
|
||
CommentCommandRunnerByCmd map[command.Name]CommentCommandRunner `validate:"required"`
|
||
Drainer *Drainer `validate:"required"`
|
||
PreWorkflowHooksCommandRunner PreWorkflowHooksCommandRunner `validate:"required"`
|
||
PostWorkflowHooksCommandRunner PostWorkflowHooksCommandRunner `validate:"required"`
|
||
PullStatusFetcher PullStatusFetcher `validate:"required"`
|
||
TeamAllowlistChecker command.TeamAllowlistChecker `validate:"required"`
|
||
VarFileAllowlistChecker *VarFileAllowlistChecker `validate:"required"`
|
||
CommitStatusUpdater CommitStatusUpdater `validate:"required"`
|
||
}
|
||
|
||
// RunAutoplanCommand runs plan and policy_checks when a pull request is opened or updated.
|
||
func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User) {
|
||
if opStarted := c.Drainer.StartOp(); !opStarted {
|
||
if commentErr := c.VCSClient.CreateComment(c.Logger, baseRepo, pull.Num, ShutdownComment, command.Plan.String()); commentErr != nil {
|
||
c.Logger.Log(logging.Error, "unable to comment that Atlantis is shutting down: %s", commentErr)
|
||
}
|
||
return
|
||
}
|
||
defer c.Drainer.OpDone()
|
||
|
||
log := c.buildLogger(baseRepo.FullName, pull.Num)
|
||
defer c.logPanics(baseRepo, pull.Num, log)
|
||
status, err := c.PullStatusFetcher.GetPullStatus(pull)
|
||
|
||
if err != nil {
|
||
log.Err("Unable to fetch pull status, this is likely a bug.", err)
|
||
}
|
||
|
||
scope := c.StatsScope.SubScope("autoplan")
|
||
timer := scope.Timer(metrics.ExecutionTimeMetric).Start()
|
||
defer timer.Stop()
|
||
|
||
// Check if the user who triggered the autoplan has permissions to run 'plan'.
|
||
if c.TeamAllowlistChecker != nil && c.TeamAllowlistChecker.HasRules() {
|
||
err := c.fetchUserTeams(log, baseRepo, &user)
|
||
if err != nil {
|
||
log.Err("Unable to fetch user teams: %s", err)
|
||
return
|
||
}
|
||
|
||
ok, err := c.checkUserPermissions(baseRepo, user, "plan")
|
||
if err != nil {
|
||
log.Err("Unable to check user permissions: %s", err)
|
||
return
|
||
}
|
||
if !ok {
|
||
return
|
||
}
|
||
}
|
||
|
||
ctx := &command.Context{
|
||
User: user,
|
||
Log: log,
|
||
Scope: scope,
|
||
Pull: pull,
|
||
HeadRepo: headRepo,
|
||
PullStatus: status,
|
||
Trigger: command.AutoTrigger,
|
||
}
|
||
if !c.validateCtxAndComment(ctx, command.Autoplan) {
|
||
return
|
||
}
|
||
if c.DisableAutoplan {
|
||
return
|
||
}
|
||
if len(c.DisableAutoplanLabel) > 0 {
|
||
labels, err := c.VCSClient.GetPullLabels(ctx.Log, baseRepo, pull)
|
||
if err != nil {
|
||
ctx.Log.Err("Unable to get VCS pull/merge request labels: %s. Proceeding with autoplan.", err)
|
||
} else if utils.SlicesContains(labels, c.DisableAutoplanLabel) {
|
||
ctx.Log.Info("Pull/merge request has disable auto plan label '%s' so not running autoplan.", c.DisableAutoplanLabel)
|
||
return
|
||
}
|
||
}
|
||
|
||
ctx.Log.Info("Running autoplan...")
|
||
cmd := &CommentCommand{
|
||
Name: command.Autoplan,
|
||
}
|
||
|
||
// Update the combined plan commit status to pending
|
||
if err := c.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.PendingCommitStatus, command.Plan); err != nil {
|
||
ctx.Log.Warn("unable to update plan commit status: %s", err)
|
||
}
|
||
|
||
err = c.PreWorkflowHooksCommandRunner.RunPreHooks(ctx, cmd)
|
||
|
||
if err != nil {
|
||
if c.FailOnPreWorkflowHookError {
|
||
ctx.Log.Err("'fail-on-pre-workflow-hook-error' set, so not running %s command.", command.Plan)
|
||
|
||
// Update the plan or apply commit status to failed
|
||
switch cmd.Name {
|
||
case command.Plan:
|
||
if err := c.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.FailedCommitStatus, command.Plan); err != nil {
|
||
ctx.Log.Warn("Unable to update plan commit status: %s", err)
|
||
}
|
||
case command.Apply:
|
||
if err := c.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.FailedCommitStatus, command.Apply); err != nil {
|
||
ctx.Log.Warn("Unable to update apply commit status: %s", err)
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
ctx.Log.Err("'fail-on-pre-workflow-hook-error' not set so running %s command.", command.Plan)
|
||
}
|
||
|
||
autoPlanRunner := buildCommentCommandRunner(c, command.Plan)
|
||
|
||
autoPlanRunner.Run(ctx, nil)
|
||
|
||
c.PostWorkflowHooksCommandRunner.RunPostHooks(ctx, cmd) // nolint: errcheck
|
||
}
|
||
|
||
// commentUserDoesNotHavePermissions comments on the pull request that the user
|
||
// is not allowed to execute the command.
|
||
func (c *DefaultCommandRunner) commentUserDoesNotHavePermissions(baseRepo models.Repo, pullNum int, user models.User, cmd *CommentCommand) {
|
||
errMsg := fmt.Sprintf("```\nError: User @%s does not have permissions to execute '%s' command.\n```", user.Username, cmd.Name.String())
|
||
if err := c.VCSClient.CreateComment(c.Logger, baseRepo, pullNum, errMsg, ""); err != nil {
|
||
c.Logger.Err("unable to comment on pull request: %s", err)
|
||
}
|
||
}
|
||
|
||
// checkUserPermissions checks if the user has permissions to execute the command
|
||
func (c *DefaultCommandRunner) checkUserPermissions(repo models.Repo, user models.User, cmdName string) (bool, error) {
|
||
if c.TeamAllowlistChecker == nil || !c.TeamAllowlistChecker.HasRules() {
|
||
// allowlist restriction is not enabled
|
||
return true, nil
|
||
}
|
||
ctx := models.TeamAllowlistCheckerContext{
|
||
BaseRepo: repo,
|
||
CommandName: cmdName,
|
||
Log: c.Logger,
|
||
Pull: models.PullRequest{},
|
||
User: user,
|
||
Verbose: false,
|
||
API: false,
|
||
}
|
||
ok := c.TeamAllowlistChecker.IsCommandAllowedForAnyTeam(ctx, user.Teams, cmdName)
|
||
if !ok {
|
||
return false, nil
|
||
}
|
||
return true, nil
|
||
}
|
||
|
||
// checkVarFilesInPlanCommandAllowlisted checks if paths in a 'plan' command are allowlisted.
|
||
func (c *DefaultCommandRunner) checkVarFilesInPlanCommandAllowlisted(cmd *CommentCommand) error {
|
||
if cmd == nil || cmd.CommandName() != command.Plan {
|
||
return nil
|
||
}
|
||
|
||
return c.VarFileAllowlistChecker.Check(cmd.Flags)
|
||
}
|
||
|
||
// RunCommentCommand executes the command.
|
||
// We take in a pointer for maybeHeadRepo because for some events there isn't
|
||
// enough data to construct the Repo model and callers might want to wait until
|
||
// the event is further validated before making an additional (potentially
|
||
// wasteful) call to get the necessary data.
|
||
func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, maybePull *models.PullRequest, user models.User, pullNum int, cmd *CommentCommand) {
|
||
if opStarted := c.Drainer.StartOp(); !opStarted {
|
||
if commentErr := c.VCSClient.CreateComment(c.Logger, baseRepo, pullNum, ShutdownComment, ""); commentErr != nil {
|
||
c.Logger.Log(logging.Error, "unable to comment that Atlantis is shutting down: %s", commentErr)
|
||
}
|
||
return
|
||
}
|
||
defer c.Drainer.OpDone()
|
||
|
||
log := c.buildLogger(baseRepo.FullName, pullNum)
|
||
defer c.logPanics(baseRepo, pullNum, log)
|
||
|
||
scope := c.StatsScope.SubScope("comment")
|
||
|
||
if cmd != nil {
|
||
scope = scope.SubScope(cmd.Name.String())
|
||
}
|
||
timer := scope.Timer(metrics.ExecutionTimeMetric).Start()
|
||
defer timer.Stop()
|
||
|
||
// Check if the user who commented has the permissions to execute the 'plan' or 'apply' commands
|
||
if c.TeamAllowlistChecker != nil && c.TeamAllowlistChecker.HasRules() {
|
||
err := c.fetchUserTeams(log, baseRepo, &user)
|
||
if err != nil {
|
||
c.Logger.Err("Unable to fetch user teams: %s", err)
|
||
return
|
||
}
|
||
|
||
ok, err := c.checkUserPermissions(baseRepo, user, cmd.Name.String())
|
||
if err != nil {
|
||
c.Logger.Err("Unable to check user permissions: %s", err)
|
||
return
|
||
}
|
||
if !ok {
|
||
c.commentUserDoesNotHavePermissions(baseRepo, pullNum, user, cmd)
|
||
return
|
||
}
|
||
}
|
||
|
||
// Check if the provided var files in a 'plan' command are allowlisted
|
||
if err := c.checkVarFilesInPlanCommandAllowlisted(cmd); err != nil {
|
||
errMsg := fmt.Sprintf("```\n%s\n```", err.Error())
|
||
if commentErr := c.VCSClient.CreateComment(c.Logger, baseRepo, pullNum, errMsg, ""); commentErr != nil {
|
||
c.Logger.Err("unable to comment on pull request: %s", commentErr)
|
||
}
|
||
return
|
||
}
|
||
|
||
headRepo, pull, err := c.ensureValidRepoMetadata(baseRepo, maybeHeadRepo, maybePull, user, pullNum, log)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
status, err := c.PullStatusFetcher.GetPullStatus(pull)
|
||
|
||
if err != nil {
|
||
log.Err("Unable to fetch pull status, this is likely a bug.", err)
|
||
}
|
||
|
||
ctx := &command.Context{
|
||
User: user,
|
||
Log: log,
|
||
Pull: pull,
|
||
PullStatus: status,
|
||
HeadRepo: headRepo,
|
||
Scope: scope,
|
||
Trigger: command.CommentTrigger,
|
||
PolicySet: cmd.PolicySet,
|
||
ClearPolicyApproval: cmd.ClearPolicyApproval,
|
||
TeamAllowlistChecker: c.TeamAllowlistChecker,
|
||
}
|
||
|
||
if !c.validateCtxAndComment(ctx, cmd.Name) {
|
||
return
|
||
}
|
||
|
||
// Update the combined plan or apply commit status to pending
|
||
switch cmd.Name {
|
||
case command.Plan:
|
||
if err := c.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.PendingCommitStatus, command.Plan); err != nil {
|
||
ctx.Log.Warn("unable to update plan commit status: %s", err)
|
||
}
|
||
case command.Apply:
|
||
if err := c.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.PendingCommitStatus, command.Apply); err != nil {
|
||
ctx.Log.Warn("unable to update apply commit status: %s", err)
|
||
}
|
||
}
|
||
|
||
err = c.PreWorkflowHooksCommandRunner.RunPreHooks(ctx, cmd)
|
||
|
||
if err != nil {
|
||
if c.FailOnPreWorkflowHookError {
|
||
ctx.Log.Err("'fail-on-pre-workflow-hook-error' set, so not running %s command.", cmd.Name.String())
|
||
|
||
// Update the plan or apply commit status to failed
|
||
switch cmd.Name {
|
||
case command.Plan:
|
||
if err := c.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.FailedCommitStatus, command.Plan); err != nil {
|
||
ctx.Log.Warn("unable to update plan commit status: %s", err)
|
||
}
|
||
case command.Apply:
|
||
if err := c.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.FailedCommitStatus, command.Apply); err != nil {
|
||
ctx.Log.Warn("unable to update apply commit status: %s", err)
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
ctx.Log.Err("'fail-on-pre-workflow-hook-error' not set so running %s command.", cmd.Name.String())
|
||
}
|
||
|
||
cmdRunner := buildCommentCommandRunner(c, cmd.CommandName())
|
||
|
||
cmdRunner.Run(ctx, cmd)
|
||
|
||
c.PostWorkflowHooksCommandRunner.RunPostHooks(ctx, cmd) // nolint: errcheck
|
||
}
|
||
|
||
func (c *DefaultCommandRunner) getGithubData(logger logging.SimpleLogging, baseRepo models.Repo, pullNum int) (models.PullRequest, models.Repo, error) {
|
||
if c.GithubPullGetter == nil {
|
||
return models.PullRequest{}, models.Repo{}, errors.New("Atlantis not configured to support GitHub")
|
||
}
|
||
ghPull, err := c.GithubPullGetter.GetPullRequest(logger, baseRepo, pullNum)
|
||
if err != nil {
|
||
return models.PullRequest{}, models.Repo{}, errors.Wrap(err, "making pull request API call to GitHub")
|
||
}
|
||
pull, _, headRepo, err := c.EventParser.ParseGithubPull(logger, ghPull)
|
||
if err != nil {
|
||
return pull, headRepo, errors.Wrap(err, "extracting required fields from comment data")
|
||
}
|
||
return pull, headRepo, nil
|
||
}
|
||
|
||
func (c *DefaultCommandRunner) getGiteaData(logger logging.SimpleLogging, baseRepo models.Repo, pullNum int) (models.PullRequest, models.Repo, error) {
|
||
if c.GiteaPullGetter == nil {
|
||
return models.PullRequest{}, models.Repo{}, errors.New("Atlantis not configured to support Gitea")
|
||
}
|
||
giteaPull, err := c.GiteaPullGetter.GetPullRequest(logger, baseRepo, pullNum)
|
||
if err != nil {
|
||
return models.PullRequest{}, models.Repo{}, errors.Wrap(err, "making pull request API call to Gitea")
|
||
}
|
||
pull, _, headRepo, err := c.EventParser.ParseGiteaPull(giteaPull)
|
||
if err != nil {
|
||
return pull, headRepo, errors.Wrap(err, "extracting required fields from comment data")
|
||
}
|
||
return pull, headRepo, nil
|
||
}
|
||
|
||
func (c *DefaultCommandRunner) getGitlabData(logger logging.SimpleLogging, baseRepo models.Repo, pullNum int) (models.PullRequest, error) {
|
||
if c.GitlabMergeRequestGetter == nil {
|
||
return models.PullRequest{}, errors.New("Atlantis not configured to support GitLab")
|
||
}
|
||
mr, err := c.GitlabMergeRequestGetter.GetMergeRequest(logger, baseRepo.FullName, pullNum)
|
||
if err != nil {
|
||
return models.PullRequest{}, errors.Wrap(err, "making merge request API call to GitLab")
|
||
}
|
||
pull := c.EventParser.ParseGitlabMergeRequest(mr, baseRepo)
|
||
return pull, nil
|
||
}
|
||
|
||
func (c *DefaultCommandRunner) getAzureDevopsData(logger logging.SimpleLogging, baseRepo models.Repo, pullNum int) (models.PullRequest, models.Repo, error) {
|
||
if c.AzureDevopsPullGetter == nil {
|
||
return models.PullRequest{}, models.Repo{}, errors.New("atlantis not configured to support Azure DevOps")
|
||
}
|
||
adPull, err := c.AzureDevopsPullGetter.GetPullRequest(logger, baseRepo, pullNum)
|
||
if err != nil {
|
||
return models.PullRequest{}, models.Repo{}, errors.Wrap(err, "making pull request API call to Azure DevOps")
|
||
}
|
||
pull, _, headRepo, err := c.EventParser.ParseAzureDevopsPull(adPull)
|
||
if err != nil {
|
||
return pull, headRepo, errors.Wrap(err, "extracting required fields from comment data")
|
||
}
|
||
return pull, headRepo, nil
|
||
}
|
||
|
||
func (c *DefaultCommandRunner) buildLogger(repoFullName string, pullNum int) logging.SimpleLogging {
|
||
|
||
return c.Logger.WithHistory(
|
||
"repo", repoFullName,
|
||
"pull", strconv.Itoa(pullNum),
|
||
)
|
||
}
|
||
|
||
func (c *DefaultCommandRunner) ensureValidRepoMetadata(
|
||
baseRepo models.Repo,
|
||
maybeHeadRepo *models.Repo,
|
||
maybePull *models.PullRequest,
|
||
_ models.User,
|
||
pullNum int,
|
||
log logging.SimpleLogging,
|
||
) (headRepo models.Repo, pull models.PullRequest, err error) {
|
||
if maybeHeadRepo != nil {
|
||
headRepo = *maybeHeadRepo
|
||
}
|
||
|
||
switch baseRepo.VCSHost.Type {
|
||
case models.Github:
|
||
pull, headRepo, err = c.getGithubData(log, baseRepo, pullNum)
|
||
case models.Gitlab:
|
||
pull, err = c.getGitlabData(log, baseRepo, pullNum)
|
||
case models.BitbucketCloud, models.BitbucketServer:
|
||
if maybePull == nil {
|
||
err = errors.New("pull request should not be nil–this is a bug")
|
||
break
|
||
}
|
||
pull = *maybePull
|
||
case models.AzureDevops:
|
||
pull, headRepo, err = c.getAzureDevopsData(log, baseRepo, pullNum)
|
||
case models.Gitea:
|
||
pull, headRepo, err = c.getGiteaData(log, baseRepo, pullNum)
|
||
default:
|
||
err = errors.New("Unknown VCS type–this is a bug")
|
||
}
|
||
|
||
if err != nil {
|
||
log.Err(err.Error())
|
||
if commentErr := c.VCSClient.CreateComment(c.Logger, baseRepo, pullNum, fmt.Sprintf("`Error: %s`", err), ""); commentErr != nil {
|
||
log.Err("unable to comment: %s", commentErr)
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func (c *DefaultCommandRunner) fetchUserTeams(logger logging.SimpleLogging, repo models.Repo, user *models.User) error {
|
||
teams, err := c.VCSClient.GetTeamNamesForUser(logger, repo, *user)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
user.Teams = teams
|
||
return nil
|
||
}
|
||
|
||
func (c *DefaultCommandRunner) validateCtxAndComment(ctx *command.Context, commandName command.Name) bool {
|
||
if !c.AllowForkPRs && ctx.HeadRepo.Owner != ctx.Pull.BaseRepo.Owner {
|
||
if c.SilenceForkPRErrors {
|
||
return false
|
||
}
|
||
ctx.Log.Info("command was run on a fork pull request which is disallowed")
|
||
if err := c.VCSClient.CreateComment(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull.Num, fmt.Sprintf("Atlantis commands can't be run on fork pull requests. To enable, set --%s or, to disable this message, set --%s", c.AllowForkPRsFlag, c.SilenceForkPRErrorsFlag), ""); err != nil {
|
||
ctx.Log.Err("unable to comment: %s", err)
|
||
}
|
||
return false
|
||
}
|
||
|
||
if ctx.Pull.State != models.OpenPullState && commandName != command.Unlock {
|
||
ctx.Log.Info("command was run on closed pull request")
|
||
if err := c.VCSClient.CreateComment(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull.Num, "Atlantis commands can't be run on closed pull requests", ""); err != nil {
|
||
ctx.Log.Err("unable to comment: %s", err)
|
||
}
|
||
return false
|
||
}
|
||
|
||
repo := c.GlobalCfg.MatchingRepo(ctx.Pull.BaseRepo.ID())
|
||
if !repo.BranchMatches(ctx.Pull.BaseBranch) {
|
||
ctx.Log.Info("command was run on a pull request which doesn't match base branches")
|
||
// just ignore it to allow us to use any git workflows without malicious intentions.
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// logPanics logs and creates a comment on the pull request for panics.
|
||
func (c *DefaultCommandRunner) logPanics(baseRepo models.Repo, pullNum int, logger logging.SimpleLogging) {
|
||
if err := recover(); err != nil {
|
||
stack := recovery.Stack(3)
|
||
logger.Err("PANIC: %s\n%s", err, stack)
|
||
if commentErr := c.VCSClient.CreateComment(
|
||
logger,
|
||
baseRepo,
|
||
pullNum,
|
||
fmt.Sprintf("**Error: goroutine panic. This is a bug.**\n```\n%s\n%s```", err, stack),
|
||
"",
|
||
); commentErr != nil {
|
||
logger.Err("unable to comment: %s", commentErr)
|
||
}
|
||
}
|
||
}
|
||
|
||
var automergeComment = `Automatically merging because all plans have been successfully applied.`
|