Skip to content

Commit 7e72b34

Browse files
authored
Merge pull request #1156 from mumoshu/fix-describe-locks-bug
Fix describe locks bug
2 parents 0688c62 + 5c29e37 commit 7e72b34

4 files changed

Lines changed: 202 additions & 80 deletions

File tree

deploy/configmap.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ const (
6363

6464
// configMapKey is a helper function that returns the key within the ConfigMap for the project and the environment
6565
// which is either locked or unlocked.
66-
func (c *Coordinator) configMapKey(project, environment string) string {
66+
func configMapKey(project, environment string) string {
6767
return project + Sep + environment
6868
}
6969

7070
func splitConfigMapKey(key string) (string, string) {
71-
parts := strings.Split(key, Sep)
72-
return parts[0], parts[1]
71+
i := strings.LastIndex(key, Sep)
72+
return key[:i], key[i+1:]
7373
}
7474

7575
func (c *Coordinator) getOrCreateConfigMap(ctx context.Context) (*corev1.ConfigMap, error) {

deploy/lock.go

Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -88,31 +88,8 @@ func (c *Coordinator) lock(ctx context.Context, project, environment, user, reas
8888
return fmt.Errorf("unable to get or create configmap: %w", err)
8989
}
9090

91-
key := c.configMapKey(project, environment)
92-
value, err := strToConfigMapValue(configMap.Data[key])
93-
if err != nil {
94-
return fmt.Errorf("unable to unmarshal str into value: %w", err)
95-
}
96-
97-
if value.Locked {
98-
return ErrAlreadyLocked
99-
}
100-
101-
if n := len(value.LockHistory); n >= MaxHistoryItems {
102-
value.LockHistory = value.LockHistory[n-MaxHistoryItems+1:]
103-
}
104-
105-
value.LockHistory = append(value.LockHistory, LockHistoryItem{
106-
User: user,
107-
Action: LockActionLock,
108-
At: c.Now(),
109-
Reason: reason,
110-
})
111-
112-
value.Locked = true
113-
114-
configMap.Data[key], err = configMapValueToStr(value)
115-
if err != nil {
91+
enc := &keysAndValuesEncoding{data: configMap.Data}
92+
if err := enc.lock(project, environment, user, reason, c.Now()); err != nil {
11693
return err
11794
}
11895

@@ -156,37 +133,8 @@ func (c *Coordinator) unlock(ctx context.Context, project, environment, user str
156133
return err
157134
}
158135

159-
key := c.configMapKey(project, environment)
160-
value, err := strToConfigMapValue(configMap.Data[key])
161-
if err != nil {
162-
return err
163-
}
164-
165-
if !value.Locked {
166-
return ErrAlreadyUnlocked
167-
}
168-
169-
if force {
170-
value.Locked = false
171-
} else {
172-
if len(value.LockHistory) == 0 || value.LockHistory[len(value.LockHistory)-1].User != user {
173-
return newNotAllowedToUnlockError(user)
174-
}
175-
176-
if n := len(value.LockHistory); n >= MaxHistoryItems {
177-
value.LockHistory = value.LockHistory[n-MaxHistoryItems+1:]
178-
}
179-
180-
value.Locked = false
181-
value.LockHistory = append(value.LockHistory, LockHistoryItem{
182-
User: user,
183-
Action: LockActionUnlock,
184-
At: c.Now(),
185-
})
186-
}
187-
188-
configMap.Data[key], err = configMapValueToStr(value)
189-
if err != nil {
136+
enc := &keysAndValuesEncoding{data: configMap.Data}
137+
if err := enc.unlock(project, environment, user, force, c.Now()); err != nil {
190138
return err
191139
}
192140

@@ -264,28 +212,11 @@ func (c *Coordinator) FetchLocks(ctx context.Context, projectFilter, phaseFilter
264212
return nil, err
265213
}
266214

267-
locks := make(map[string]map[string]Phase)
268-
for k, v := range configMap.Data {
269-
value, err := strToConfigMapValue(v)
270-
if err != nil {
271-
return nil, err
272-
}
273-
274-
project, environment := splitConfigMapKey(k)
275-
276-
if projectFilter != "" && project != projectFilter {
277-
continue
278-
}
279-
280-
if phaseFilter != "" && environment != phaseFilter {
281-
continue
282-
}
215+
enc := &keysAndValuesEncoding{data: configMap.Data}
283216

284-
if locks[project] == nil {
285-
locks[project] = make(map[string]Phase)
286-
}
287-
288-
locks[project][environment] = value
217+
locks, err := enc.describeLocks(projectFilter, phaseFilter)
218+
if err != nil {
219+
return nil, err
289220
}
290221

291222
return locks, nil

deploy/lock_encoding.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package deploy
2+
3+
import (
4+
"fmt"
5+
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
)
8+
9+
type keysAndValuesEncoding struct {
10+
data map[string]string
11+
}
12+
13+
func (e *keysAndValuesEncoding) lock(project, environment, user, reason string, at metav1.Time) error {
14+
key := configMapKey(project, environment)
15+
value, err := strToConfigMapValue(e.data[key])
16+
if err != nil {
17+
return fmt.Errorf("unable to unmarshal str into value: %w", err)
18+
}
19+
20+
if value.Locked {
21+
return ErrAlreadyLocked
22+
}
23+
24+
if n := len(value.LockHistory); n >= MaxHistoryItems {
25+
value.LockHistory = value.LockHistory[n-MaxHistoryItems+1:]
26+
}
27+
28+
value.LockHistory = append(value.LockHistory, LockHistoryItem{
29+
User: user,
30+
Action: LockActionLock,
31+
At: at,
32+
Reason: reason,
33+
})
34+
35+
value.Locked = true
36+
37+
e.data[key], err = configMapValueToStr(value)
38+
if err != nil {
39+
return err
40+
}
41+
42+
return nil
43+
}
44+
45+
func (e *keysAndValuesEncoding) unlock(project, environment, user string, force bool, at metav1.Time) error {
46+
key := configMapKey(project, environment)
47+
value, err := strToConfigMapValue(e.data[key])
48+
if err != nil {
49+
return err
50+
}
51+
52+
if !value.Locked {
53+
return ErrAlreadyUnlocked
54+
}
55+
56+
if force {
57+
value.Locked = false
58+
} else {
59+
if len(value.LockHistory) == 0 || value.LockHistory[len(value.LockHistory)-1].User != user {
60+
return newNotAllowedToUnlockError(user)
61+
}
62+
63+
if n := len(value.LockHistory); n >= MaxHistoryItems {
64+
value.LockHistory = value.LockHistory[n-MaxHistoryItems+1:]
65+
}
66+
67+
value.Locked = false
68+
value.LockHistory = append(value.LockHistory, LockHistoryItem{
69+
User: user,
70+
Action: LockActionUnlock,
71+
At: at,
72+
})
73+
}
74+
75+
e.data[key], err = configMapValueToStr(value)
76+
if err != nil {
77+
return err
78+
}
79+
80+
return nil
81+
}
82+
83+
func (e *keysAndValuesEncoding) describeLocks(projectFilter, phaseFilter string) (map[string]map[string]Phase, error) {
84+
locks := make(map[string]map[string]Phase)
85+
for k, v := range e.data {
86+
value, err := strToConfigMapValue(v)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
project, environment := splitConfigMapKey(k)
92+
93+
if projectFilter != "" && project != projectFilter {
94+
continue
95+
}
96+
97+
if phaseFilter != "" && environment != phaseFilter {
98+
continue
99+
}
100+
101+
if locks[project] == nil {
102+
locks[project] = make(map[string]Phase)
103+
}
104+
105+
locks[project][environment] = value
106+
}
107+
108+
return locks, nil
109+
}

deploy/lock_encoding_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package deploy
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
)
11+
12+
func TestKeysAndValuesEncoding(t *testing.T) {
13+
data := map[string]string{}
14+
enc := &keysAndValuesEncoding{
15+
data: data,
16+
}
17+
18+
now, err := time.Parse(time.RFC3339, "2021-09-01T00:00:00Z")
19+
require.NoError(t, err)
20+
21+
kNow := metav1.NewTime(now.Local())
22+
23+
require.NoError(t, enc.lock("myproject1", "prod", "user1", "for deployment of revision 1a", kNow))
24+
require.ErrorIs(t, enc.lock("myproject1", "prod", "user1", "for deployment of revision 1b", kNow), ErrAlreadyLocked)
25+
require.ErrorIs(t, enc.lock("myproject1", "prod", "user2", "for deployment of revision 1b", kNow), ErrAlreadyLocked)
26+
27+
require.ErrorIs(t, enc.unlock("myproject2", "prod", "user1", false, kNow), ErrAlreadyUnlocked)
28+
require.NoError(t, enc.lock("myproject2", "prod", "user1", "for deployment of revision 2a", kNow))
29+
30+
require.NoError(t, enc.lock("myproject2-api", "prod", "user1", "for deployment of revision 3a", kNow))
31+
32+
locks, err := enc.describeLocks("", "")
33+
require.NoError(t, err)
34+
35+
assert.Equal(t, map[string]map[string]Phase{
36+
"myproject1": {
37+
"prod": {
38+
Locked: true,
39+
LockHistory: []LockHistoryItem{
40+
{
41+
User: "user1",
42+
Action: LockActionLock,
43+
At: kNow,
44+
Reason: "for deployment of revision 1a",
45+
},
46+
},
47+
},
48+
},
49+
"myproject2": {
50+
"prod": {
51+
Locked: true,
52+
LockHistory: []LockHistoryItem{
53+
{
54+
User: "user1",
55+
Action: LockActionLock,
56+
At: kNow,
57+
Reason: "for deployment of revision 2a",
58+
},
59+
},
60+
},
61+
},
62+
"myproject2-api": {
63+
"prod": {
64+
Locked: true,
65+
LockHistory: []LockHistoryItem{
66+
{
67+
User: "user1",
68+
Action: LockActionLock,
69+
At: kNow,
70+
Reason: "for deployment of revision 3a",
71+
},
72+
},
73+
},
74+
},
75+
}, locks)
76+
77+
assert.Equal(t, map[string]string{
78+
"myproject1-prod": `{"locked":true,"lockHistory":[{"user":"user1","action":"lock","at":"2021-09-01T00:00:00Z","reason":"for deployment of revision 1a"}]}`,
79+
"myproject2-prod": `{"locked":true,"lockHistory":[{"user":"user1","action":"lock","at":"2021-09-01T00:00:00Z","reason":"for deployment of revision 2a"}]}`,
80+
"myproject2-api-prod": `{"locked":true,"lockHistory":[{"user":"user1","action":"lock","at":"2021-09-01T00:00:00Z","reason":"for deployment of revision 3a"}]}`,
81+
}, data)
82+
}

0 commit comments

Comments
 (0)