feat(4157): adding option to hide UI button to disable apply commands (#4158)

This commit is contained in:
Isaac Gaskin
2024-01-23 21:10:44 -08:00
committed by GitHub
parent 9a14f5d9a9
commit fe32f8645c
7 changed files with 54 additions and 28 deletions

View File

@@ -1295,6 +1295,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
allowCommands = opt.allowCommands
}
disableApply := true
disableGlobalApplyLock := false
for _, allowCommand := range allowCommands {
if allowCommand == command.Apply {
disableApply = false
@@ -1314,7 +1315,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
backend := boltdb
lockingClient := locking.NewClient(boltdb)
noOpLocker := locking.NewNoOpLocker()
applyLocker = locking.NewApplyClient(boltdb, disableApply)
applyLocker = locking.NewApplyClient(boltdb, disableApply, disableGlobalApplyLock)
projectLocker := &events.DefaultProjectLocker{
Locker: lockingClient,
NoOpLocker: noOpLocker,

View File

@@ -45,9 +45,10 @@ type LockIndexData struct {
// ApplyLockData holds the fields to display in the index view
type ApplyLockData struct {
Locked bool
Time time.Time
TimeFormatted string
Locked bool
GlobalApplyLockEnabled bool
Time time.Time
TimeFormatted string
}
// IndexData holds the data for rendering the index page
@@ -98,6 +99,7 @@ var IndexTemplate = template.Must(template.New("index.html.tmpl").Parse(`
<p class="js-discard-success"><strong>Plan discarded and unlocked!</strong></p>
</section>
<section>
{{ if .ApplyLock.GlobalApplyLockEnabled }}
{{ if .ApplyLock.Locked }}
<div class="twelve center columns">
<h6><strong>Apply commands are disabled globally</strong></h6>
@@ -111,6 +113,7 @@ var IndexTemplate = template.Must(template.New("index.html.tmpl").Parse(`
<a class="button button-primary" id="applyLockPrompt">Disable Apply Commands</a>
</div>
{{ end }}
{{ end }}
</section>
<br>
<br>

View File

@@ -33,20 +33,23 @@ type ApplyCommandLock struct {
// Locked is true is when apply commands are locked
// Either by using omitting apply from AllowCommands or creating a global ApplyCommandLock
// DisableApply lock take precedence when set
Locked bool
Time time.Time
Failure string
Locked bool
GlobalApplyLockEnabled bool
Time time.Time
Failure string
}
type ApplyClient struct {
backend Backend
disableApply bool
backend Backend
disableApply bool
disableGlobalApplyLock bool
}
func NewApplyClient(backend Backend, disableApply bool) ApplyLocker {
func NewApplyClient(backend Backend, disableApply bool, disableGlobalApplyLock bool) ApplyLocker {
return &ApplyClient{
backend: backend,
disableApply: disableApply,
backend: backend,
disableApply: disableApply,
disableGlobalApplyLock: disableGlobalApplyLock,
}
}
@@ -91,7 +94,9 @@ func (c *ApplyClient) UnlockApply() error {
// CheckApplyLock retrieves an apply command lock if present.
// If DisableApply is set it will always return a lock.
func (c *ApplyClient) CheckApplyLock() (ApplyCommandLock, error) {
response := ApplyCommandLock{}
response := ApplyCommandLock{
GlobalApplyLockEnabled: true,
}
if c.disableApply {
return ApplyCommandLock{
@@ -108,6 +113,9 @@ func (c *ApplyClient) CheckApplyLock() (ApplyCommandLock, error) {
response.Locked = true
response.Time = applyCmdLock.LockTime()
}
if c.disableGlobalApplyLock {
response.GlobalApplyLockEnabled = false
}
return response, nil
}

View File

@@ -196,7 +196,7 @@ func TestApplyLocker(t *testing.T) {
backend := mocks.NewMockBackend()
When(backend.LockCommand(Any[command.Name](), Any[time.Time]())).ThenReturn(nil, errExpected)
l := locking.NewApplyClient(backend, false)
l := locking.NewApplyClient(backend, false, false)
lock, err := l.LockApply()
Equals(t, errExpected, err)
Assert(t, !lock.Locked, "exp false")
@@ -205,7 +205,7 @@ func TestApplyLocker(t *testing.T) {
t.Run("can't lock if apply is omitted from userConfig.AllowCommands", func(t *testing.T) {
backend := mocks.NewMockBackend()
l := locking.NewApplyClient(backend, true)
l := locking.NewApplyClient(backend, true, false)
_, err := l.LockApply()
ErrEquals(t, "apply is omitted from AllowCommands; Apply commands are locked globally until flag is updated", err)
@@ -216,7 +216,7 @@ func TestApplyLocker(t *testing.T) {
backend := mocks.NewMockBackend()
When(backend.LockCommand(Any[command.Name](), Any[time.Time]())).ThenReturn(applyLock, nil)
l := locking.NewApplyClient(backend, false)
l := locking.NewApplyClient(backend, false, false)
lock, _ := l.LockApply()
Assert(t, lock.Locked, "exp lock present")
})
@@ -227,7 +227,7 @@ func TestApplyLocker(t *testing.T) {
backend := mocks.NewMockBackend()
When(backend.UnlockCommand(Any[command.Name]())).ThenReturn(errExpected)
l := locking.NewApplyClient(backend, false)
l := locking.NewApplyClient(backend, false, false)
err := l.UnlockApply()
Equals(t, errExpected, err)
})
@@ -235,7 +235,7 @@ func TestApplyLocker(t *testing.T) {
t.Run("can't lock if apply is omitted from userConfig.AllowCommands", func(t *testing.T) {
backend := mocks.NewMockBackend()
l := locking.NewApplyClient(backend, true)
l := locking.NewApplyClient(backend, true, false)
err := l.UnlockApply()
ErrEquals(t, "apply commands are disabled until AllowCommands flag is updated", err)
@@ -246,7 +246,7 @@ func TestApplyLocker(t *testing.T) {
backend := mocks.NewMockBackend()
When(backend.UnlockCommand(Any[command.Name]())).ThenReturn(nil)
l := locking.NewApplyClient(backend, false)
l := locking.NewApplyClient(backend, false, false)
err := l.UnlockApply()
Equals(t, nil, err)
})
@@ -258,7 +258,7 @@ func TestApplyLocker(t *testing.T) {
backend := mocks.NewMockBackend()
When(backend.CheckCommandLock(Any[command.Name]())).ThenReturn(nil, errExpected)
l := locking.NewApplyClient(backend, false)
l := locking.NewApplyClient(backend, false, false)
lock, err := l.CheckApplyLock()
Equals(t, errExpected, err)
Equals(t, lock.Locked, false)
@@ -267,7 +267,7 @@ func TestApplyLocker(t *testing.T) {
t.Run("when apply is not in AllowCommands always return a lock", func(t *testing.T) {
backend := mocks.NewMockBackend()
l := locking.NewApplyClient(backend, true)
l := locking.NewApplyClient(backend, true, false)
lock, err := l.CheckApplyLock()
Ok(t, err)
Equals(t, lock.Locked, true)
@@ -278,7 +278,7 @@ func TestApplyLocker(t *testing.T) {
backend := mocks.NewMockBackend()
When(backend.CheckCommandLock(Any[command.Name]())).ThenReturn(applyLock, nil)
l := locking.NewApplyClient(backend, false)
l := locking.NewApplyClient(backend, false, false)
lock, err := l.CheckApplyLock()
Equals(t, nil, err)
Assert(t, lock.Locked, "exp lock present")

View File

@@ -121,6 +121,7 @@ type Server struct {
WebPassword string
ProjectCmdOutputHandler jobs.ProjectCommandOutputHandler
ScheduledExecutorService *scheduled.ExecutorService
DisableGlobalApplyLock bool
}
// Config holds config for server that isn't passed in by the user.
@@ -455,8 +456,12 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
} else {
lockingClient = locking.NewClient(backend)
}
disableGlobalApplyLock := false
if userConfig.DisableGlobalApplyLock {
disableGlobalApplyLock = true
}
applyLockingClient = locking.NewApplyClient(backend, disableApply)
applyLockingClient = locking.NewApplyClient(backend, disableApply, disableGlobalApplyLock)
workingDirLocker := events.NewDefaultWorkingDirLocker()
var workingDir events.WorkingDir = &events.FileWorkspace{
@@ -919,6 +924,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
ProjectJobsErrorTemplate: templates.ProjectJobsErrorTemplate,
SSLKeyFile: userConfig.SSLKeyFile,
SSLCertFile: userConfig.SSLCertFile,
DisableGlobalApplyLock: userConfig.DisableGlobalApplyLock,
Drainer: drainer,
ProjectCmdOutputHandler: projectCmdOutputHandler,
WebAuthentication: userConfig.WebBasicAuth,
@@ -941,8 +947,6 @@ func (s *Server) Start() error {
s.Router.HandleFunc("/api/apply", s.APIController.Apply).Methods("POST")
s.Router.HandleFunc("/github-app/exchange-code", s.GithubAppController.ExchangeCode).Methods("GET")
s.Router.HandleFunc("/github-app/setup", s.GithubAppController.New).Methods("GET")
s.Router.HandleFunc("/apply/lock", s.LocksController.LockApply).Methods("POST").Queries()
s.Router.HandleFunc("/apply/unlock", s.LocksController.UnlockApply).Methods("DELETE").Queries()
s.Router.HandleFunc("/locks", s.LocksController.DeleteLock).Methods("DELETE").Queries("id", "{id:.*}")
s.Router.HandleFunc("/lock", s.LocksController.GetLock).Methods("GET").
Queries(LockViewRouteIDQueryParam, fmt.Sprintf("{%s}", LockViewRouteIDQueryParam)).Name(LockViewRouteName)
@@ -953,6 +957,10 @@ func (s *Server) Start() error {
if ok {
s.Router.Handle(s.CommandRunner.GlobalCfg.Metrics.Prometheus.Endpoint, r.HTTPHandler())
}
if !s.DisableGlobalApplyLock {
s.Router.HandleFunc("/apply/lock", s.LocksController.LockApply).Methods("POST").Queries()
s.Router.HandleFunc("/apply/unlock", s.LocksController.UnlockApply).Methods("DELETE").Queries()
}
n := negroni.New(&negroni.Recovery{
Logger: log.New(os.Stdout, "", log.LstdFlags),
@@ -1064,9 +1072,10 @@ func (s *Server) Index(w http.ResponseWriter, _ *http.Request) {
}
applyLockData := templates.ApplyLockData{
Time: applyCmdLock.Time,
Locked: applyCmdLock.Locked,
TimeFormatted: applyCmdLock.Time.Format("02-01-2006 15:04:05"),
Time: applyCmdLock.Time,
Locked: applyCmdLock.Locked,
GlobalApplyLockEnabled: applyCmdLock.GlobalApplyLockEnabled,
TimeFormatted: applyCmdLock.Time.Format("02-01-2006 15:04:05"),
}
//Sort by date - newest to oldest.
sort.SliceStable(lockResults, func(i, j int) bool { return lockResults[i].Time.After(lockResults[j].Time) })

View File

@@ -36,6 +36,7 @@ type UserConfig struct {
DisableAutoplanLabel string `mapstructure:"disable-autoplan-label"`
DisableMarkdownFolding bool `mapstructure:"disable-markdown-folding"`
DisableRepoLocking bool `mapstructure:"disable-repo-locking"`
DisableGlobalApplyLock bool `mapstructure:"disable-global-apply-lock"`
DisableUnlockLabel string `mapstructure:"disable-unlock-label"`
DiscardApprovalOnPlanFlag bool `mapstructure:"discard-approval-on-plan"`
EmojiReaction string `mapstructure:"emoji-reaction"`