Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More describe-locks test #1137

Merged
merged 3 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions deploy/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package deploy
import (
"context"
"fmt"
"sort"

kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -197,8 +198,64 @@ func (c *Coordinator) unlock(ctx context.Context, project, environment, user str
return nil
}

// DescribeLocks returns a map of project names to a map of environment names to the lock information.
func (c *Coordinator) DescribeLocks(ctx context.Context) (map[string]map[string]Phase, error) {
type PhaseDesc struct {
Name string
Phase
}

type ProjectDesc struct {
Name string
Phases []PhaseDesc
}

func (c *Coordinator) DescribeLocks(ctx context.Context) ([]ProjectDesc, error) {
locks, err := c.describeLocks(ctx)
if err != nil {
return nil, err
}

priorities := map[string]int{
"production": 1,
"staging": 2,
}

var projects []ProjectDesc
for project, phasesMap := range locks {
var phases []PhaseDesc
for name, phase := range phasesMap {
phases = append(phases, PhaseDesc{name, phase})
}

sort.SliceStable(phases, func(i, j int) bool {
pi, ok := priorities[phases[i].Name]
if !ok {
pi = 3
}

pj, ok := priorities[phases[j].Name]
if !ok {
pj = 3
}

if pi != pj {
return pi < pj
}

return phases[i].Name < phases[j].Name
})

projects = append(projects, ProjectDesc{project, phases})
}

sort.SliceStable(projects, func(i, j int) bool {
return projects[i].Name < projects[j].Name
})

return projects, nil
}

// describeLocks returns a map of project names to a map of environment names to the lock information.
func (c *Coordinator) describeLocks(ctx context.Context) (map[string]map[string]Phase, error) {
configMap, err := c.getOrCreateConfigMap(ctx)
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions deploy/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestDescribeLocks(t *testing.T) {

require.NoError(t, c.Lock(ctx, "myproject1", "prod", "user1", "for deployment of revision a"))

locks, err := c.DescribeLocks(ctx)
locks, err := c.describeLocks(ctx)
require.NoError(t, err)
require.Len(t, locks, 1)

Expand All @@ -114,7 +114,7 @@ func TestDescribeLocks(t *testing.T) {

require.NoError(t, c.Unlock(ctx, "myproject1", "prod", "user1", false))

locks, err = c.DescribeLocks(ctx)
locks, err = c.describeLocks(ctx)
require.NoError(t, err)
require.Len(t, locks, 1)
require.Equal(t, map[string]Phase{
Expand Down
9 changes: 6 additions & 3 deletions slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,16 +307,19 @@ func (s *SlackListener) unlock(cmd *slackcmd.Unlock, triggeredBy User, replyIn s

// describeLocks describes the locks of all projects and environments, and replies to the given channel.
func (s *SlackListener) describeLocks() slack.MsgOption {
locks, err := s.getOrCreateCoordinator().DescribeLocks(context.Background())
projects, err := s.getOrCreateCoordinator().DescribeLocks(context.Background())
if err != nil {
return s.errorMessage(err.Error())
}

var buf strings.Builder
for project, envs := range locks {
for _, pj := range projects {
project := pj.Name
envs := pj.Phases
buf.WriteString(project)
buf.WriteString("\n")
for env, lock := range envs {
for _, lock := range envs {
env := lock.Name
buf.WriteString(" ")
buf.WriteString(env)
buf.WriteString(": ")
Expand Down
90 changes: 88 additions & 2 deletions slack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"k8s.io/client-go/kubernetes"
)

var myprojectConfigMap = corev1.ConfigMap{
var project1ConfigMap = corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "myproject1",
Labels: map[string]string{
Expand All @@ -33,6 +33,21 @@ var myprojectConfigMap = corev1.ConfigMap{
},
Data: map[string]string{
"Phases": `- name: production
- name: staging
`,
},
}

var project2ConfigMap = corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "myproject2",
Labels: map[string]string{
"gocat.zaim.net/configmap-type": "project",
},
},
Data: map[string]string{
"Phases": `- name: production
- name: staging
`,
},
}
Expand Down Expand Up @@ -70,6 +85,13 @@ user4
},
}

var configMaps = []corev1.ConfigMap{
project1ConfigMap,
project2ConfigMap,
githubuserMappingConfigMap,
rolebindingConfigMap,
}

// TestSlackLockUnlock tests the lock and unlock commands against
// a pre-configured Kubernetes cluster, fake Slack API, and fake GitHub API.
// It verifies that the lock and unlock commands work as expected, by sending
Expand All @@ -83,7 +105,7 @@ func TestSlackLockUnlock(t *testing.T) {
clientset, err := k.ClientSet()
require.NoError(t, err)

setupConfigMaps(t, clientset, myprojectConfigMap, githubuserMappingConfigMap, rolebindingConfigMap)
setupConfigMaps(t, clientset, configMaps...)
setupNamespace(t, clientset, "gocat")

messages := make(chan message, 10)
Expand Down Expand Up @@ -273,6 +295,46 @@ func TestSlackLockUnlock(t *testing.T) {
production: Locked (by user2, for deployment of revision a)
`, nextMessage().Text())

// Lock staging
require.NoError(t, l.handleMessageEvent(&slackevents.AppMentionEvent{
User: "U1235",
Channel: "C1234",
Text: "lock myproject1 staging for deployment of revision b",
}))
require.Equal(t, "Locked myproject1 staging", nextMessage().Text())

// Describe locks
require.NoError(t, l.handleMessageEvent(&slackevents.AppMentionEvent{
User: "U1235",
Channel: "C1234",
Text: "describe locks",
}))
require.Equal(t, `myproject1
production: Locked (by user2, for deployment of revision a)
staging: Locked (by user2, for deployment of revision b)
`, nextMessage().Text())

// Lock project 2 staging
require.NoError(t, l.handleMessageEvent(&slackevents.AppMentionEvent{
User: "U1235",
Channel: "C1234",
Text: "lock myproject2 staging for deployment of revision c",
}))
require.Equal(t, "Locked myproject2 staging", nextMessage().Text())

// Describe locks
require.NoError(t, l.handleMessageEvent(&slackevents.AppMentionEvent{
User: "U1235",
Channel: "C1234",
Text: "describe locks",
}))
require.Equal(t, `myproject1
production: Locked (by user2, for deployment of revision a)
staging: Locked (by user2, for deployment of revision b)
myproject2
staging: Locked (by user2, for deployment of revision c)
`, nextMessage().Text())

// User 1 is a developer so cannot unlock the project forcefully
require.NoError(t, l.handleMessageEvent(&slackevents.AppMentionEvent{
User: "U1234",
Expand All @@ -297,6 +359,30 @@ func TestSlackLockUnlock(t *testing.T) {
}))
require.Equal(t, `myproject1
production: Unlocked
staging: Locked (by user2, for deployment of revision b)
myproject2
staging: Locked (by user2, for deployment of revision c)
`, nextMessage().Text())

// Unlock project 2 staging
require.NoError(t, l.handleMessageEvent(&slackevents.AppMentionEvent{
User: "U1235",
Channel: "C1234",
Text: "unlock myproject2 staging",
}))
require.Equal(t, "Unlocked myproject2 staging", nextMessage().Text())

// Describe locks
require.NoError(t, l.handleMessageEvent(&slackevents.AppMentionEvent{
User: "U1235",
Channel: "C1234",
Text: "describe locks",
}))
require.Equal(t, `myproject1
production: Unlocked
staging: Locked (by user2, for deployment of revision b)
myproject2
staging: Unlocked
`, nextMessage().Text())
}

Expand Down
Loading