diff --git a/server/controllers/api_controller.go b/server/controllers/api_controller.go index 1103802f1..30a330837 100644 --- a/server/controllers/api_controller.go +++ b/server/controllers/api_controller.go @@ -22,21 +22,21 @@ const atlantisTokenHeader = "X-Atlantis-Token" type APIController struct { APISecret []byte - Locker locking.Locker - Logger logging.SimpleLogging - Parser events.EventParsing - ProjectCommandBuilder events.ProjectCommandBuilder - ProjectPlanCommandRunner events.ProjectPlanCommandRunner - ProjectApplyCommandRunner events.ProjectApplyCommandRunner + Locker locking.Locker `validate:"required"` + Logger logging.SimpleLogging `validate:"required"` + Parser events.EventParsing `validate:"required"` + ProjectCommandBuilder events.ProjectCommandBuilder `validate:"required"` + ProjectPlanCommandRunner events.ProjectPlanCommandRunner `validate:"required"` + ProjectApplyCommandRunner events.ProjectApplyCommandRunner `validate:"required"` FailOnPreWorkflowHookError bool - PreWorkflowHooksCommandRunner events.PreWorkflowHooksCommandRunner - PostWorkflowHooksCommandRunner events.PostWorkflowHooksCommandRunner - RepoAllowlistChecker *events.RepoAllowlistChecker - Scope tally.Scope - VCSClient vcs.Client - WorkingDir events.WorkingDir - WorkingDirLocker events.WorkingDirLocker - CommitStatusUpdater events.CommitStatusUpdater + PreWorkflowHooksCommandRunner events.PreWorkflowHooksCommandRunner `validate:"required"` + PostWorkflowHooksCommandRunner events.PostWorkflowHooksCommandRunner `validate:"required"` + RepoAllowlistChecker *events.RepoAllowlistChecker `validate:"required"` + Scope tally.Scope `validate:"required"` + VCSClient vcs.Client `validate:"required"` + WorkingDir events.WorkingDir `validate:"required"` + WorkingDirLocker events.WorkingDirLocker `validate:"required"` + CommitStatusUpdater events.CommitStatusUpdater `validate:"required"` } type APIRequest struct { diff --git a/server/controllers/events/events_controller.go b/server/controllers/events/events_controller.go index 1836795dd..bc0295a28 100644 --- a/server/controllers/events/events_controller.go +++ b/server/controllers/events/events_controller.go @@ -58,12 +58,12 @@ const azuredevopsTestURL = "https://fabrikam.visualstudio.com/DefaultCollection/ // VCSEventsController handles all webhook requests which signify 'events' in the // VCS host, ex. GitHub. type VCSEventsController struct { - CommandRunner events.CommandRunner - PullCleaner events.PullCleaner - Logger logging.SimpleLogging - Scope tally.Scope - Parser events.EventParsing - CommentParser events.CommentParsing + CommandRunner events.CommandRunner `validate:"required"` + PullCleaner events.PullCleaner `validate:"required"` + Logger logging.SimpleLogging `validate:"required"` + Scope tally.Scope `validate:"required"` + Parser events.EventParsing `validate:"required"` + CommentParser events.CommentParsing `validate:"required"` ApplyDisabled bool EmojiReaction string ExecutableName string @@ -71,20 +71,20 @@ type VCSEventsController struct { // UI that identifies this call as coming from GitHub. If empty, no // request validation is done. GithubWebhookSecret []byte - GithubRequestValidator GithubRequestValidator - GitlabRequestParserValidator GitlabRequestParserValidator + GithubRequestValidator GithubRequestValidator `validate:"required"` + GitlabRequestParserValidator GitlabRequestParserValidator `validate:"required"` // GitlabWebhookSecret is the secret added to this webhook via the GitLab // UI that identifies this call as coming from GitLab. If empty, no // request validation is done. GitlabWebhookSecret []byte - RepoAllowlistChecker *events.RepoAllowlistChecker + RepoAllowlistChecker *events.RepoAllowlistChecker `validate:"required"` // SilenceAllowlistErrors controls whether we write an error comment on // pull requests from non-allowlisted repos. SilenceAllowlistErrors bool // SupportedVCSHosts is which VCS hosts Atlantis was configured upon // startup to support. - SupportedVCSHosts []models.VCSHostType - VCSClient vcs.Client + SupportedVCSHosts []models.VCSHostType `validate:"required"` + VCSClient vcs.Client `validate:"required"` TestingMode bool // BitbucketWebhookSecret is the secret added to this webhook via the Bitbucket // UI that identifies this call as coming from Bitbucket. If empty, no @@ -99,7 +99,7 @@ type VCSEventsController struct { // webhook via the Azure DevOps UI that identifies this call as coming from your // Azure DevOps Team Project. If empty, no request validation is done. AzureDevopsWebhookBasicPassword []byte - AzureDevopsRequestValidator AzureDevopsRequestValidator + AzureDevopsRequestValidator AzureDevopsRequestValidator `validate:"required"` GiteaWebhookSecret []byte } diff --git a/server/controllers/github_app_controller.go b/server/controllers/github_app_controller.go index f6c72dc70..e30147672 100644 --- a/server/controllers/github_app_controller.go +++ b/server/controllers/github_app_controller.go @@ -13,10 +13,10 @@ import ( // GithubAppController handles the creation and setup of a new GitHub app type GithubAppController struct { - AtlantisURL *url.URL - Logger logging.SimpleLogging + AtlantisURL *url.URL `validate:"required"` + Logger logging.SimpleLogging `validate:"required"` GithubSetupComplete bool - GithubHostname string + GithubHostname string `validate:"required"` GithubOrg string } diff --git a/server/controllers/jobs_controller.go b/server/controllers/jobs_controller.go index 2f27e32b9..100fbd073 100644 --- a/server/controllers/jobs_controller.go +++ b/server/controllers/jobs_controller.go @@ -26,15 +26,15 @@ func (g JobIDKeyGenerator) Generate(r *http.Request) (string, error) { } type JobsController struct { - AtlantisVersion string - AtlantisURL *url.URL - Logger logging.SimpleLogging - ProjectJobsTemplate web_templates.TemplateWriter - ProjectJobsErrorTemplate web_templates.TemplateWriter - Backend locking.Backend - WsMux *websocket.Multiplexor + AtlantisVersion string `validate:"required"` + AtlantisURL *url.URL `validate:"required"` + Logger logging.SimpleLogging `validate:"required"` + ProjectJobsTemplate web_templates.TemplateWriter `validate:"required"` + ProjectJobsErrorTemplate web_templates.TemplateWriter `validate:"required"` + Backend locking.Backend `validate:"required"` + WsMux *websocket.Multiplexor `validate:"required"` KeyGenerator JobIDKeyGenerator - StatsScope tally.Scope + StatsScope tally.Scope `validate:"required"` } func (j *JobsController) getProjectJobs(w http.ResponseWriter, r *http.Request) error { diff --git a/server/controllers/locks_controller.go b/server/controllers/locks_controller.go index 27b330c8b..b8415720d 100644 --- a/server/controllers/locks_controller.go +++ b/server/controllers/locks_controller.go @@ -17,17 +17,17 @@ import ( // LocksController handles all requests relating to Atlantis locks. type LocksController struct { - AtlantisVersion string - AtlantisURL *url.URL - Locker locking.Locker - Logger logging.SimpleLogging - ApplyLocker locking.ApplyLocker - VCSClient vcs.Client - LockDetailTemplate web_templates.TemplateWriter - WorkingDir events.WorkingDir - WorkingDirLocker events.WorkingDirLocker - Backend locking.Backend - DeleteLockCommand events.DeleteLockCommand + AtlantisVersion string `validate:"required"` + AtlantisURL *url.URL `validate:"required"` + Locker locking.Locker `validate:"required"` + Logger logging.SimpleLogging `validate:"required"` + ApplyLocker locking.ApplyLocker `validate:"required"` + VCSClient vcs.Client `validate:"required"` + LockDetailTemplate web_templates.TemplateWriter `validate:"required"` + WorkingDir events.WorkingDir `validate:"required"` + WorkingDirLocker events.WorkingDirLocker `validate:"required"` + Backend locking.Backend `validate:"required"` + DeleteLockCommand events.DeleteLockCommand `validate:"required"` } // LockApply handles creating a global apply lock. diff --git a/server/controllers/status_controller.go b/server/controllers/status_controller.go index e35f3bc7b..487ac9791 100644 --- a/server/controllers/status_controller.go +++ b/server/controllers/status_controller.go @@ -11,9 +11,9 @@ import ( // StatusController handles the status of Atlantis. type StatusController struct { - Logger logging.SimpleLogging - Drainer *events.Drainer - AtlantisVersion string + Logger logging.SimpleLogging `validate:"required"` + Drainer *events.Drainer `validate:"required"` + AtlantisVersion string `validate:"required"` } type StatusResponse struct { diff --git a/server/core/runtime/apply_step_runner.go b/server/core/runtime/apply_step_runner.go index 35a864cfc..b14666403 100644 --- a/server/core/runtime/apply_step_runner.go +++ b/server/core/runtime/apply_step_runner.go @@ -18,11 +18,11 @@ import ( // ApplyStepRunner runs `terraform apply`. type ApplyStepRunner struct { - TerraformExecutor TerraformExec - DefaultTFDistribution terraform.Distribution - DefaultTFVersion *version.Version - CommitStatusUpdater StatusUpdater - AsyncTFExec AsyncTFExec + TerraformExecutor TerraformExec `validate:"required"` + DefaultTFDistribution terraform.Distribution `validate:"required"` + DefaultTFVersion *version.Version `validate:"required"` + CommitStatusUpdater StatusUpdater `validate:"required"` + AsyncTFExec AsyncTFExec `validate:"required"` } func (a *ApplyStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) { diff --git a/server/events/command_runner.go b/server/events/command_runner.go index 90870a467..88291c82a 100644 --- a/server/events/command_runner.go +++ b/server/events/command_runner.go @@ -94,7 +94,7 @@ func buildCommentCommandRunner( // DefaultCommandRunner is the first step when processing a comment command. type DefaultCommandRunner struct { - VCSClient vcs.Client + VCSClient vcs.Client `validate:"required"` GithubPullGetter GithubPullGetter AzureDevopsPullGetter AzureDevopsPullGetter GitlabMergeRequestGetter GitlabMergeRequestGetter @@ -105,9 +105,9 @@ type DefaultCommandRunner struct { 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 - GlobalCfg valid.GlobalCfg - StatsScope tally.Scope + 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 @@ -123,14 +123,14 @@ type DefaultCommandRunner struct { // 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 - Drainer *Drainer - PreWorkflowHooksCommandRunner PreWorkflowHooksCommandRunner - PostWorkflowHooksCommandRunner PostWorkflowHooksCommandRunner - PullStatusFetcher PullStatusFetcher - TeamAllowlistChecker command.TeamAllowlistChecker - VarFileAllowlistChecker *VarFileAllowlistChecker - CommitStatusUpdater CommitStatusUpdater + 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. diff --git a/server/events/drainer.go b/server/events/drainer.go index 3ac4238fa..4258b9c76 100644 --- a/server/events/drainer.go +++ b/server/events/drainer.go @@ -7,9 +7,9 @@ import ( // Drainer is used to gracefully shut down atlantis by waiting for in-progress // operations to complete. type Drainer struct { - status DrainStatus - mutex sync.Mutex - wg sync.WaitGroup + status DrainStatus `validate:"required"` + mutex sync.Mutex `validate:"required"` + wg sync.WaitGroup `validate:"required"` } type DrainStatus struct { diff --git a/server/events/post_workflow_hooks_command_runner.go b/server/events/post_workflow_hooks_command_runner.go index 0e83b4792..2739587b3 100644 --- a/server/events/post_workflow_hooks_command_runner.go +++ b/server/events/post_workflow_hooks_command_runner.go @@ -27,13 +27,13 @@ type PostWorkflowHooksCommandRunner interface { // DefaultPostWorkflowHooksCommandRunner is the first step when processing a workflow hook commands. type DefaultPostWorkflowHooksCommandRunner struct { - VCSClient vcs.Client - WorkingDirLocker WorkingDirLocker - WorkingDir WorkingDir - GlobalCfg valid.GlobalCfg - PostWorkflowHookRunner runtime.PostWorkflowHookRunner - CommitStatusUpdater CommitStatusUpdater - Router PostWorkflowHookURLGenerator + VCSClient vcs.Client `validate:"required"` + WorkingDirLocker WorkingDirLocker `validate:"required"` + WorkingDir WorkingDir `validate:"required"` + GlobalCfg valid.GlobalCfg `validate:"required"` + PostWorkflowHookRunner runtime.PostWorkflowHookRunner `validate:"required"` + CommitStatusUpdater CommitStatusUpdater `validate:"required"` + Router PostWorkflowHookURLGenerator `validate:"required"` } // RunPostHooks runs post_workflow_hooks after a plan/apply has completed diff --git a/server/events/pre_workflow_hooks_command_runner.go b/server/events/pre_workflow_hooks_command_runner.go index be175d0b7..684c3c5e5 100644 --- a/server/events/pre_workflow_hooks_command_runner.go +++ b/server/events/pre_workflow_hooks_command_runner.go @@ -27,13 +27,13 @@ type PreWorkflowHooksCommandRunner interface { // DefaultPreWorkflowHooksCommandRunner is the first step when processing a workflow hook commands. type DefaultPreWorkflowHooksCommandRunner struct { - VCSClient vcs.Client - WorkingDirLocker WorkingDirLocker - WorkingDir WorkingDir - GlobalCfg valid.GlobalCfg - PreWorkflowHookRunner runtime.PreWorkflowHookRunner - CommitStatusUpdater CommitStatusUpdater - Router PreWorkflowHookURLGenerator + VCSClient vcs.Client `validate:"required"` + WorkingDirLocker WorkingDirLocker `validate:"required"` + WorkingDir WorkingDir `validate:"required"` + GlobalCfg valid.GlobalCfg `validate:"required"` + PreWorkflowHookRunner runtime.PreWorkflowHookRunner `validate:"required"` + CommitStatusUpdater CommitStatusUpdater `validate:"required"` + Router PreWorkflowHookURLGenerator `validate:"required"` } // RunPreHooks runs pre_workflow_hooks when PR is opened or updated. diff --git a/server/server.go b/server/server.go index 893d1c487..3028c5152 100644 --- a/server/server.go +++ b/server/server.go @@ -34,6 +34,7 @@ import ( "syscall" "time" + "github.com/go-playground/validator/v10" "github.com/mitchellh/go-homedir" tally "github.com/uber-go/tally/v4" prometheus "github.com/uber-go/tally/v4/prometheus" @@ -203,19 +204,19 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { } } - validator := &cfg.ParserValidator{} + parserValidator := &cfg.ParserValidator{} globalCfg := valid.NewGlobalCfgFromArgs( valid.GlobalCfgArgs{ PolicyCheckEnabled: userConfig.EnablePolicyChecksFlag, }) if userConfig.RepoConfig != "" { - globalCfg, err = validator.ParseGlobalCfg(userConfig.RepoConfig, globalCfg) + globalCfg, err = parserValidator.ParseGlobalCfg(userConfig.RepoConfig, globalCfg) if err != nil { return nil, errors.Wrapf(err, "parsing %s file", userConfig.RepoConfig) } } else if userConfig.RepoConfigJSON != "" { - globalCfg, err = validator.ParseGlobalCfgJSON(userConfig.RepoConfigJSON, globalCfg) + globalCfg, err = parserValidator.ParseGlobalCfgJSON(userConfig.RepoConfigJSON, globalCfg) if err != nil { return nil, errors.Wrapf(err, "parsing --%s", config.RepoConfigJSONFlag) } @@ -649,7 +650,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { projectCommandBuilder := events.NewInstrumentedProjectCommandBuilder( logger, policyChecksEnabled, - validator, + parserValidator, &events.DefaultProjectFinder{}, vcsClient, workingDir, @@ -993,7 +994,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { GithubOrg: userConfig.GithubOrg, } - return &Server{ + server := &Server{ AtlantisVersion: config.AtlantisVersion, AtlantisURL: parsedURL, Router: underlyingRouter, @@ -1026,7 +1027,16 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { WebUsername: userConfig.WebUsername, WebPassword: userConfig.WebPassword, ScheduledExecutorService: scheduledExecutorService, - }, nil + } + + validate := validator.New(validator.WithRequiredStructEnabled()) + + err = validate.Struct(server) + if err != nil { + return nil, err + } else { + return server, nil + } } // Start creates the routes and starts serving traffic. diff --git a/server/server_test.go b/server/server_test.go index 4da358301..521cb48e4 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -26,6 +26,7 @@ import ( "github.com/gorilla/mux" . "github.com/petergtz/pegomock/v4" + "github.com/runatlantis/atlantis/cmd" "github.com/runatlantis/atlantis/server" "github.com/runatlantis/atlantis/server/controllers/web_templates" tMocks "github.com/runatlantis/atlantis/server/controllers/web_templates/mocks" @@ -36,13 +37,28 @@ import ( . "github.com/runatlantis/atlantis/testing" ) -func TestNewServer(t *testing.T) { +const ( + testAtlantisVersion = "1.0.0" + testAtlantisUrl = "http://example.com" + testLockingDBType = cmd.DefaultLockingDBType + testGitHubHostName = cmd.DefaultGHHostname + testGitHubUser = "user" +) + +func TestNewServer_GitHubUser(t *testing.T) { t.Log("Run through NewServer constructor") tmpDir := t.TempDir() - _, err := server.NewServer(server.UserConfig{ - DataDir: tmpDir, - AtlantisURL: "http://example.com", - }, server.Config{}) + _, err := server.NewServer( + server.UserConfig{ + DataDir: tmpDir, + AtlantisURL: testAtlantisUrl, + LockingDBType: testLockingDBType, + GithubHostname: testGitHubHostName, + GithubUser: testGitHubUser, + }, server.Config{ + AtlantisVersion: testAtlantisVersion, + }, + ) Ok(t, err) } @@ -273,12 +289,3 @@ func TestParseAtlantisURL(t *testing.T) { }) } } - -func TestCommandRunnerVCSClientInitialized(t *testing.T) { - s, _ := server.NewServer(server.UserConfig{ - AtlantisURL: "http://example.com", - }, - server.Config{}, - ) - Assert(t, s.CommandRunner.VCSClient != nil, "VCSClient must not be nil.") -}