Merge pull request #1158 from runatlantis/skip-clone

Skip cloning PR repository in case of no projects were changed
This commit is contained in:
Luke Kysow
2020-08-18 15:56:17 -07:00
committed by GitHub
24 changed files with 423 additions and 103 deletions

View File

@@ -81,6 +81,7 @@ const (
SilenceAllowlistErrorsFlag = "silence-allowlist-errors"
// SilenceWhitelistErrorsFlag is deprecated for SilenceAllowlistErrorsFlag.
SilenceWhitelistErrorsFlag = "silence-whitelist-errors"
SkipCloneNoChanges = "skip-clone-no-changes"
SlackTokenFlag = "slack-token"
SSLCertFileFlag = "ssl-cert-file"
SSLKeyFileFlag = "ssl-key-file"
@@ -319,6 +320,10 @@ var boolFlags = map[string]boolFlag{
" This writes secrets to disk and should only be enabled in a secure environment.",
defaultValue: false,
},
SkipCloneNoChanges: {
description: "Skips cloning the PR repo if there are no projects were changed in the PR.",
defaultValue: false,
},
}
var intFlags = map[string]intFlag{
PortFlag: {

View File

@@ -86,6 +86,7 @@ var testFlags = map[string]interface{}{
SilenceForkPRErrorsFlag: true,
SilenceAllowlistErrorsFlag: true,
SilenceVCSStatusNoPlans: true,
SkipCloneNoChanges: true,
SlackTokenFlag: "slack-token",
SSLCertFileFlag: "cert-file",
SSLKeyFileFlag: "key-file",

View File

@@ -3,6 +3,7 @@ package matchers
import (
"reflect"
"github.com/petergtz/pegomock"
models "github.com/runatlantis/atlantis/server/events/models"
)

View File

@@ -3,6 +3,7 @@ package matchers
import (
"reflect"
"github.com/petergtz/pegomock"
models "github.com/runatlantis/atlantis/server/events/models"
)

View File

@@ -3,6 +3,7 @@ package matchers
import (
"reflect"
"github.com/petergtz/pegomock"
logging "github.com/runatlantis/atlantis/server/logging"
)

View File

@@ -4,11 +4,12 @@
package events
import (
"reflect"
"time"
pegomock "github.com/petergtz/pegomock"
models "github.com/runatlantis/atlantis/server/events/models"
logging "github.com/runatlantis/atlantis/server/logging"
"reflect"
"time"
)
type MockWorkingDir struct {

View File

@@ -53,14 +53,15 @@ type ProjectCommandBuilder interface {
// This class combines the data from the comment and any atlantis.yaml file or
// Atlantis server config and then generates a set of contexts.
type DefaultProjectCommandBuilder struct {
ParserValidator *yaml.ParserValidator
ProjectFinder ProjectFinder
VCSClient vcs.Client
WorkingDir WorkingDir
WorkingDirLocker WorkingDirLocker
GlobalCfg valid.GlobalCfg
PendingPlanFinder *DefaultPendingPlanFinder
CommentBuilder CommentBuilder
ParserValidator *yaml.ParserValidator
ProjectFinder ProjectFinder
VCSClient vcs.Client
WorkingDir WorkingDir
WorkingDirLocker WorkingDirLocker
GlobalCfg valid.GlobalCfg
PendingPlanFinder *DefaultPendingPlanFinder
CommentBuilder CommentBuilder
SkipCloneNoChanges bool
}
// See ProjectCommandBuilder.BuildAutoplanCommands.
@@ -101,8 +102,43 @@ func (p *DefaultProjectCommandBuilder) BuildApplyCommands(ctx *CommandContext, c
// buildPlanAllCommands builds plan contexts for all projects we determine were
// modified in this ctx.
func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext, commentFlags []string, verbose bool) ([]models.ProjectCommandContext, error) {
// We'll need the list of modified files.
modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.BaseRepo, ctx.Pull)
if err != nil {
return nil, err
}
ctx.Log.Debug("%d files were modified in this pull request", len(modifiedFiles))
if p.SkipCloneNoChanges && p.VCSClient.SupportsSingleFileDownload(ctx.BaseRepo) {
hasRepoCfg, repoCfgData, err := p.VCSClient.DownloadRepoConfigFile(ctx.Pull)
if err != nil {
return nil, errors.Wrapf(err, "downloading %s", yaml.AtlantisYAMLFilename)
}
if hasRepoCfg {
repoCfg, err := p.ParserValidator.ParseRepoCfgData(repoCfgData, p.GlobalCfg, ctx.BaseRepo.ID())
if err != nil {
return nil, errors.Wrapf(err, "parsing %s", yaml.AtlantisYAMLFilename)
}
ctx.Log.Info("successfully parsed remote %s file", yaml.AtlantisYAMLFilename)
matchingProjects, err := p.ProjectFinder.DetermineProjectsViaConfig(ctx.Log, modifiedFiles, repoCfg, "")
if err != nil {
return nil, err
}
ctx.Log.Info("%d projects are changed on MR %q based on their when_modified config", len(matchingProjects), ctx.Pull.Num)
if len(matchingProjects) == 0 {
ctx.Log.Info("skipping repo clone since no project was modified")
return []models.ProjectCommandContext{}, nil
}
// NOTE: We discard this work here and end up doing it again after
// cloning to ensure all the return values are set properly with
// the actual clone directory.
}
}
// Need to lock the workspace we're about to clone to.
workspace := DefaultWorkspace
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, ctx.Pull.Num, workspace)
if err != nil {
ctx.Log.Warn("workspace was locked")
@@ -111,18 +147,10 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
ctx.Log.Debug("got workspace lock")
defer unlockFn()
// We'll need the list of modified files.
modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.BaseRepo, ctx.Pull)
if err != nil {
return nil, err
}
ctx.Log.Debug("%d files were modified in this pull request", len(modifiedFiles))
repoDir, _, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
if err != nil {
return nil, err
}
// Parse config file if it exists.
hasRepoCfg, err := p.ParserValidator.HasRepoCfg(repoDir)
if err != nil {

View File

@@ -563,14 +563,15 @@ projects:
}
builder := &DefaultProjectCommandBuilder{
WorkingDirLocker: NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: parser,
VCSClient: vcsClient,
ProjectFinder: &DefaultProjectFinder{},
PendingPlanFinder: &DefaultPendingPlanFinder{},
CommentBuilder: &CommentParser{},
GlobalCfg: globalCfg,
WorkingDirLocker: NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: parser,
VCSClient: vcsClient,
ProjectFinder: &DefaultProjectFinder{},
PendingPlanFinder: &DefaultPendingPlanFinder{},
CommentBuilder: &CommentParser{},
GlobalCfg: globalCfg,
SkipCloneNoChanges: false,
}
// We run a test for each type of command.

View File

@@ -135,14 +135,15 @@ projects:
}
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(false, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(false, false, false),
SkipCloneNoChanges: false,
}
ctxs, err := builder.BuildAutoplanCommands(&events.CommandContext{
@@ -358,13 +359,14 @@ projects:
}
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}
var actCtxs []models.ProjectCommandContext
@@ -491,13 +493,14 @@ projects:
}
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}
ctxs, err := builder.BuildPlanCommands(
@@ -562,14 +565,15 @@ func TestDefaultProjectCommandBuilder_BuildMultiApply(t *testing.T) {
ThenReturn(tmpDir, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(false, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(false, false, false),
SkipCloneNoChanges: false,
}
ctxs, err := builder.BuildApplyCommands(
@@ -630,13 +634,14 @@ projects:
AnyString())).ThenReturn(repoDir, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}
ctx := &events.CommandContext{
@@ -692,13 +697,14 @@ func TestDefaultProjectCommandBuilder_EscapeArgs(t *testing.T) {
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"main.tf"}, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}
var actCtxs []models.ProjectCommandContext
@@ -856,13 +862,14 @@ projects:
AnyString())).ThenReturn(tmpDir, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
VCSClient: vcsClient,
ParserValidator: &yaml.ParserValidator{},
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
VCSClient: vcsClient,
ParserValidator: &yaml.ParserValidator{},
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}
actCtxs, err := builder.BuildPlanCommands(
@@ -887,3 +894,43 @@ projects:
})
}
}
// Test that we don't clone the repo if there were no changes based on the atlantis.yaml file.
func TestDefaultProjectCommandBuilder_SkipCloneNoChanges(t *testing.T) {
atlantisYAML := `
version: 3
projects:
- dir: dir1`
RegisterMockTestingT(t)
vcsClient := vcsmocks.NewMockClient()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"main.tf"}, nil)
When(vcsClient.SupportsSingleFileDownload(matchers.AnyModelsRepo())).ThenReturn(true)
When(vcsClient.DownloadRepoConfigFile(matchers.AnyModelsPullRequest())).ThenReturn(true, []byte(atlantisYAML), nil)
workingDir := mocks.NewMockWorkingDir()
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: true,
}
var actCtxs []models.ProjectCommandContext
var err error
actCtxs, err = builder.BuildAutoplanCommands(&events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: nil,
PullMergeable: true,
})
Ok(t, err)
Equals(t, 0, len(actCtxs))
workingDir.VerifyWasCalled(Never()).Clone(matchers.AnyPtrToLoggingSimpleLogger(), matchers.AnyModelsRepo(), matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), AnyString())
}

View File

@@ -120,11 +120,22 @@ func (p *DefaultProjectFinder) DetermineProjectsViaConfig(log *logging.SimpleLog
}
if match {
log.Debug("file %q matched pattern", file)
_, err := os.Stat(filepath.Join(absRepoDir, project.Dir))
if err == nil {
projects = append(projects, project)
// If we're checking using an atlantis.yaml file we downloaded
// directly from the repo (when doing a no-clone check) then
// absRepoDir will be empty. Since we didn't clone the repo
// yet we can't do this check. If there was a file modified
// in a deleted directory then when we finally do clone the repo
// we'll call this function again and then we'll detect the
// directory was deleted.
if absRepoDir != "" {
_, err := os.Stat(filepath.Join(absRepoDir, project.Dir))
if err == nil {
projects = append(projects, project)
} else {
log.Debug("project at dir %q not included because dir does not exist", project.Dir)
}
} else {
log.Debug("project at dir %q not included because dir does not exist", project.Dir)
projects = append(projects, project)
}
break
}

View File

@@ -364,3 +364,11 @@ func SplitAzureDevopsRepoFullName(repoFullName string) (owner string, project st
}
return repoFullName[:lastSlashIdx], "", repoFullName[lastSlashIdx+1:]
}
func (g *AzureDevopsClient) SupportsSingleFileDownload(repo models.Repo) bool {
return false
}
func (g *AzureDevopsClient) DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error) {
return false, []byte{}, fmt.Errorf("Not Implemented")
}

View File

@@ -243,3 +243,14 @@ func (b *Client) makeRequest(method string, path string, reqBody io.Reader) ([]b
}
return respBody, nil
}
func (b *Client) SupportsSingleFileDownload(models.Repo) bool {
return false
}
// DownloadRepoConfigFile return `atlantis.yaml` content from VCS (which support fetch a single file from repository)
// The first return value indicate that repo contain atlantis.yaml or not
// if BaseRepo had one repo config file, its content will placed on the second return value
func (b *Client) DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error) {
return false, []byte{}, fmt.Errorf("Not Implemented")
}

View File

@@ -311,3 +311,14 @@ func (b *Client) makeRequest(method string, path string, reqBody io.Reader) ([]b
}
return respBody, nil
}
func (b *Client) SupportsSingleFileDownload(repo models.Repo) bool {
return false
}
// DownloadRepoConfigFile return `atlantis.yaml` content from VCS (which support fetch a single file from repository)
// The first return value indicate that repo contain atlantis.yaml or not
// if BaseRepo had one repo config file, its content will placed on the second return value
func (b *Client) DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error) {
return false, []byte{}, fmt.Errorf("not implemented")
}

View File

@@ -38,4 +38,10 @@ type Client interface {
UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, src string, description string, url string) error
MergePull(pull models.PullRequest) error
MarkdownPullLink(pull models.PullRequest) (string, error)
// DownloadRepoConfigFile return `atlantis.yaml` content from VCS (which support fetch a single file from repository)
// The first return value indicate that repo contain atlantis.yaml or not
// if BaseRepo had one repo config file, its content will placed on the second return value
DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error)
SupportsSingleFileDownload(repo models.Repo) bool
}

View File

@@ -15,17 +15,19 @@ package vcs
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"strings"
"time"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs/common"
"github.com/runatlantis/atlantis/server/logging"
"github.com/Laisky/graphql"
"github.com/google/go-github/v31/github"
"github.com/pkg/errors"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs/common"
"github.com/runatlantis/atlantis/server/events/yaml"
"github.com/runatlantis/atlantis/server/logging"
"github.com/shurcooL/githubv4"
)
@@ -384,3 +386,29 @@ func (g *GithubClient) ExchangeCode(code string) (*GithubAppTemporarySecrets, er
return data, err
}
// DownloadRepoConfigFile return `atlantis.yaml` content from VCS (which support fetch a single file from repository)
// The first return value indicate that repo contain atlantis.yaml or not
// if BaseRepo had one repo config file, its content will placed on the second return value
func (g *GithubClient) DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error) {
opt := github.RepositoryContentGetOptions{Ref: pull.HeadBranch}
fileContent, _, resp, err := g.client.Repositories.GetContents(g.ctx, pull.BaseRepo.Owner, pull.BaseRepo.Name, yaml.AtlantisYAMLFilename, &opt)
if resp.StatusCode == http.StatusNotFound {
return false, []byte{}, nil
}
if err != nil {
return true, []byte{}, err
}
decodedData, err := base64.StdEncoding.DecodeString(*fileContent.Content)
if err != nil {
return true, []byte{}, err
}
return true, decodedData, nil
}
func (g *GithubClient) SupportsSingleFileDownload(repo models.Repo) bool {
return true
}

View File

@@ -16,9 +16,12 @@ package vcs
import (
"fmt"
"net"
"net/http"
"net/url"
"strings"
"github.com/runatlantis/atlantis/server/events/yaml"
"github.com/runatlantis/atlantis/server/events/vcs/common"
version "github.com/hashicorp/go-version"
@@ -269,3 +272,25 @@ func MustConstraint(constraint string) version.Constraints {
}
return c
}
// DownloadRepoConfigFile return `atlantis.yaml` content from VCS (which support fetch a single file from repository)
// The first return value indicate that repo contain atlantis.yaml or not
// if BaseRepo had one repo config file, its content will placed on the second return value
func (g *GitlabClient) DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error) {
opt := gitlab.GetRawFileOptions{Ref: gitlab.String(pull.HeadBranch)}
bytes, resp, err := g.Client.RepositoryFiles.GetRawFile(pull.BaseRepo.FullName, yaml.AtlantisYAMLFilename, &opt)
if resp.StatusCode == http.StatusNotFound {
return false, []byte{}, nil
}
if err != nil {
return true, []byte{}, err
}
return true, bytes, nil
}
func (g *GitlabClient) SupportsSingleFileDownload(repo models.Repo) bool {
return true
}

View File

@@ -0,0 +1,20 @@
// Code generated by pegomock. DO NOT EDIT.
package matchers
import (
"reflect"
"github.com/petergtz/pegomock"
)
func AnySliceOfByte() []byte {
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*([]byte))(nil)).Elem()))
var nullValue []byte
return nullValue
}
func EqSliceOfByte(value []byte) []byte {
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
var nullValue []byte
return nullValue
}

View File

@@ -161,6 +161,44 @@ func (mock *MockClient) MarkdownPullLink(pull models.PullRequest) (string, error
return ret0, ret1
}
func (mock *MockClient) DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClient().")
}
params := []pegomock.Param{pull}
result := pegomock.GetGenericMockFrom(mock).Invoke("DownloadRepoConfigFile", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*[]byte)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 bool
var ret1 []byte
var ret2 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(bool)
}
if result[1] != nil {
ret1 = result[1].([]byte)
}
if result[2] != nil {
ret2 = result[2].(error)
}
}
return ret0, ret1, ret2
}
func (mock *MockClient) SupportsSingleFileDownload(repo models.Repo) bool {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClient().")
}
params := []pegomock.Param{repo}
result := pegomock.GetGenericMockFrom(mock).Invoke("SupportsSingleFileDownload", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
var ret0 bool
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(bool)
}
}
return ret0
}
func (mock *MockClient) VerifyWasCalledOnce() *VerifierMockClient {
return &VerifierMockClient{
mock: mock,
@@ -461,3 +499,57 @@ func (c *MockClient_MarkdownPullLink_OngoingVerification) GetAllCapturedArgument
}
return
}
func (verifier *VerifierMockClient) DownloadRepoConfigFile(pull models.PullRequest) *MockClient_DownloadRepoConfigFile_OngoingVerification {
params := []pegomock.Param{pull}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DownloadRepoConfigFile", params, verifier.timeout)
return &MockClient_DownloadRepoConfigFile_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type MockClient_DownloadRepoConfigFile_OngoingVerification struct {
mock *MockClient
methodInvocations []pegomock.MethodInvocation
}
func (c *MockClient_DownloadRepoConfigFile_OngoingVerification) GetCapturedArguments() models.PullRequest {
pull := c.GetAllCapturedArguments()
return pull[len(pull)-1]
}
func (c *MockClient_DownloadRepoConfigFile_OngoingVerification) GetAllCapturedArguments() (_param0 []models.PullRequest) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.PullRequest, len(c.methodInvocations))
for u, param := range params[0] {
_param0[u] = param.(models.PullRequest)
}
}
return
}
func (verifier *VerifierMockClient) SupportsSingleFileDownload(repo models.Repo) *MockClient_SupportsSingleFileDownload_OngoingVerification {
params := []pegomock.Param{repo}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SupportsSingleFileDownload", params, verifier.timeout)
return &MockClient_SupportsSingleFileDownload_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type MockClient_SupportsSingleFileDownload_OngoingVerification struct {
mock *MockClient
methodInvocations []pegomock.MethodInvocation
}
func (c *MockClient_SupportsSingleFileDownload_OngoingVerification) GetCapturedArguments() models.Repo {
repo := c.GetAllCapturedArguments()
return repo[len(repo)-1]
}
func (c *MockClient_SupportsSingleFileDownload_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(c.methodInvocations))
for u, param := range params[0] {
_param0[u] = param.(models.Repo)
}
}
return
}

View File

@@ -53,3 +53,11 @@ func (a *NotConfiguredVCSClient) MarkdownPullLink(pull models.PullRequest) (stri
func (a *NotConfiguredVCSClient) err() error {
return fmt.Errorf("atlantis was not configured to support repos from %s", a.Host.String())
}
func (a *NotConfiguredVCSClient) SupportsSingleFileDownload(repo models.Repo) bool {
return false
}
func (a *NotConfiguredVCSClient) DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error) {
return true, []byte{}, a.err()
}

View File

@@ -83,3 +83,11 @@ func (d *ClientProxy) MergePull(pull models.PullRequest) error {
func (d *ClientProxy) MarkdownPullLink(pull models.PullRequest) (string, error) {
return d.clients[pull.BaseRepo.VCSHost.Type].MarkdownPullLink(pull)
}
func (d *ClientProxy) DownloadRepoConfigFile(pull models.PullRequest) (bool, []byte, error) {
return d.clients[pull.BaseRepo.VCSHost.Type].DownloadRepoConfigFile(pull)
}
func (d *ClientProxy) SupportsSingleFileDownload(repo models.Repo) bool {
return d.clients[repo.VCSHost.Type].SupportsSingleFileDownload(repo)
}

View File

@@ -56,9 +56,12 @@ func (p *ParserValidator) ParseRepoCfg(absRepoDir string, globalCfg valid.Global
// able to detect if it's a NotExist err.
return valid.RepoCfg{}, err
}
return p.ParseRepoCfgData(configData, globalCfg, repoID)
}
func (p *ParserValidator) ParseRepoCfgData(repoCfgData []byte, globalCfg valid.GlobalCfg, repoID string) (valid.RepoCfg, error) {
var rawConfig raw.RepoCfg
if err := yaml.UnmarshalStrict(configData, &rawConfig); err != nil {
if err := yaml.UnmarshalStrict(repoCfgData, &rawConfig); err != nil {
return valid.RepoCfg{}, err
}
@@ -83,7 +86,7 @@ func (p *ParserValidator) ParseRepoCfg(absRepoDir string, globalCfg valid.Global
}
}
err = globalCfg.ValidateRepoCfg(validConfig, repoID)
err := globalCfg.ValidateRepoCfg(validConfig, repoID)
return validConfig, err
}

View File

@@ -476,14 +476,15 @@ func setupE2E(t *testing.T, repoDir string) (server.EventsController, *vcsmocks.
AllowForkPRs: allowForkPRs,
AllowForkPRsFlag: "allow-fork-prs",
ProjectCommandBuilder: &events.DefaultProjectCommandBuilder{
ParserValidator: parser,
ProjectFinder: &events.DefaultProjectFinder{},
VCSClient: e2eVCSClient,
WorkingDir: workingDir,
WorkingDirLocker: locker,
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: commentParser,
GlobalCfg: globalCfg,
ParserValidator: parser,
ProjectFinder: &events.DefaultProjectFinder{},
VCSClient: e2eVCSClient,
WorkingDir: workingDir,
WorkingDirLocker: locker,
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: commentParser,
GlobalCfg: globalCfg,
SkipCloneNoChanges: false,
},
DB: boltdb,
PendingPlanFinder: &events.DefaultPendingPlanFinder{},

View File

@@ -370,14 +370,15 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
SilenceVCSStatusNoPlans: userConfig.SilenceVCSStatusNoPlans,
DisableApplyAll: userConfig.DisableApplyAll,
ProjectCommandBuilder: &events.DefaultProjectCommandBuilder{
ParserValidator: validator,
ProjectFinder: &events.DefaultProjectFinder{},
VCSClient: vcsClient,
WorkingDir: workingDir,
WorkingDirLocker: workingDirLocker,
GlobalCfg: globalCfg,
PendingPlanFinder: pendingPlanFinder,
CommentBuilder: commentParser,
ParserValidator: validator,
ProjectFinder: &events.DefaultProjectFinder{},
VCSClient: vcsClient,
WorkingDir: workingDir,
WorkingDirLocker: workingDirLocker,
GlobalCfg: globalCfg,
PendingPlanFinder: pendingPlanFinder,
CommentBuilder: commentParser,
SkipCloneNoChanges: userConfig.SkipCloneNoChanges,
},
ProjectCommandRunner: &events.DefaultProjectCommandRunner{
Locker: projectLocker,

View File

@@ -58,6 +58,7 @@ type UserConfig struct {
SilenceAllowlistErrors bool `mapstructure:"silence-allowlist-errors"`
// SilenceWhitelistErrors is deprecated in favour of SilenceAllowlistErrors
SilenceWhitelistErrors bool `mapstructure:"silence-whitelist-errors"`
SkipCloneNoChanges bool `mapstructure:"skip-clone-no-changes"`
SlackToken string `mapstructure:"slack-token"`
SSLCertFile string `mapstructure:"ssl-cert-file"`
SSLKeyFile string `mapstructure:"ssl-key-file"`