-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
project_locker.go
95 lines (86 loc) · 3.58 KB
/
project_locker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// 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"
"github.com/runatlantis/atlantis/server/core/locking"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs"
"github.com/runatlantis/atlantis/server/logging"
)
//go:generate pegomock generate --package mocks -o mocks/mock_project_lock.go ProjectLocker
// ProjectLocker locks this project against other plans being run until this
// project is unlocked.
type ProjectLocker interface {
// TryLock attempts to acquire the lock for this project. It returns true if the lock
// was acquired. If it returns false, the lock was not acquired and the second
// return value will be a string describing why the lock was not acquired.
// The third return value is a function that can be called to unlock the
// lock. It will only be set if the lock was acquired. Any errors will set
// error.
TryLock(log logging.SimpleLogging, pull models.PullRequest, user models.User, workspace string, project models.Project, repoLocking bool) (*TryLockResponse, error)
}
// DefaultProjectLocker implements ProjectLocker.
type DefaultProjectLocker struct {
Locker locking.Locker
NoOpLocker locking.Locker
VCSClient vcs.Client
}
// TryLockResponse is the result of trying to lock a project.
type TryLockResponse struct {
// LockAcquired is true if the lock was acquired.
LockAcquired bool
// LockFailureReason is the reason why the lock was not acquired. It will
// only be set if LockAcquired is false.
LockFailureReason string
// UnlockFn will unlock the lock created by the caller. This might be called
// if there is an error later and the caller doesn't want to continue to
// hold the lock.
UnlockFn func() error
// LockKey is the key for the lock if the lock was acquired.
LockKey string
}
// TryLock implements ProjectLocker.TryLock.
func (p *DefaultProjectLocker) TryLock(log logging.SimpleLogging, pull models.PullRequest, user models.User, workspace string, project models.Project, repoLocking bool) (*TryLockResponse, error) {
locker := p.Locker
if !repoLocking {
locker = p.NoOpLocker
}
lockAttempt, err := locker.TryLock(project, workspace, pull, user)
if err != nil {
return nil, err
}
if !lockAttempt.LockAcquired && lockAttempt.CurrLock.Pull.Num != pull.Num {
link, err := p.VCSClient.MarkdownPullLink(lockAttempt.CurrLock.Pull)
if err != nil {
return nil, err
}
failureMsg := fmt.Sprintf(
"This project is currently locked by an unapplied plan from pull %s. To continue, delete the lock from %s or apply that plan and merge the pull request.\n\nOnce the lock is released, comment `atlantis plan` here to re-plan.",
link,
link)
return &TryLockResponse{
LockAcquired: false,
LockFailureReason: failureMsg,
}, nil
}
log.Info("Acquired lock with id '%s'", lockAttempt.LockKey)
return &TryLockResponse{
LockAcquired: true,
UnlockFn: func() error {
_, err := p.Locker.Unlock(lockAttempt.LockKey)
return err
},
LockKey: lockAttempt.LockKey,
}, nil
}