Skip to content

Commit 637921c

Browse files
committed
Tests for initHumanUsers and initinitRobotUsers.
Change the Cluster class in the process to implelement Teams API calls and Oauth token fetches as interfaces, so that we can mock them in the tests.
1 parent 611cfe9 commit 637921c

File tree

4 files changed

+173
-26
lines changed

4 files changed

+173
-26
lines changed

pkg/cluster/cluster.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,11 @@ type Cluster struct {
7171
deleteOptions *metav1.DeleteOptions
7272
podEventsQueue *cache.FIFO
7373

74-
teamsAPIClient *teams.API
75-
KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place?
76-
currentProcess spec.Process
77-
processMu sync.RWMutex
74+
teamsAPIClient teams.Interface
75+
oauthTokenGetter OAuthTokenGetter
76+
KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place?
77+
currentProcess spec.Process
78+
processMu sync.RWMutex
7879
}
7980

8081
type compareStatefulsetResult struct {
@@ -112,9 +113,10 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec spec.Postgresql
112113
deleteOptions: &metav1.DeleteOptions{OrphanDependents: &orphanDependents},
113114
podEventsQueue: podEventsQueue,
114115
KubeClient: kubeClient,
115-
teamsAPIClient: teams.NewTeamsAPI(cfg.OpConfig.TeamsAPIUrl, logger),
116116
}
117117
cluster.logger = logger.WithField("pkg", "cluster").WithField("cluster-name", cluster.clusterName())
118+
cluster.teamsAPIClient = teams.NewTeamsAPI(cfg.OpConfig.TeamsAPIUrl, logger)
119+
cluster.oauthTokenGetter = NewSecretOauthTokenGetter(&kubeClient, cfg.OpConfig.OAuthTokenSecretName)
118120
cluster.patroni = patroni.New(cluster.logger)
119121

120122
return cluster

pkg/cluster/cluster_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package cluster
2+
3+
import (
4+
"fmt"
5+
"github.com/Sirupsen/logrus"
6+
"github.com/zalando-incubator/postgres-operator/pkg/spec"
7+
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
8+
"github.com/zalando-incubator/postgres-operator/pkg/util/teams"
9+
"reflect"
10+
"testing"
11+
)
12+
13+
var logger = logrus.New().WithField("test", "cluster")
14+
var cl = New(Config{}, k8sutil.KubernetesClient{}, spec.Postgresql{}, logger)
15+
16+
func TestInitRobotUsers(t *testing.T) {
17+
testName := "TestInitRobotUsers"
18+
tests := []struct {
19+
manifestUsers map[string]spec.UserFlags
20+
infraRoles map[string]spec.PgUser
21+
result map[string]spec.PgUser
22+
err error
23+
}{
24+
{
25+
manifestUsers: map[string]spec.UserFlags{"foo": {"superuser", "createdb"}},
26+
infraRoles: map[string]spec.PgUser{"foo": {Name: "foo", Password: "bar"}},
27+
result: map[string]spec.PgUser{"foo": {Name: "foo", Password: "bar",
28+
Flags: []string{"CREATEDB", "LOGIN", "SUPERUSER"}}},
29+
err: nil,
30+
},
31+
{
32+
manifestUsers: map[string]spec.UserFlags{"!fooBar": {"superuser", "createdb"}},
33+
err: fmt.Errorf(`invalid username: "!fooBar"`),
34+
},
35+
{
36+
manifestUsers: map[string]spec.UserFlags{"foobar": {"!superuser", "createdb"}},
37+
err: fmt.Errorf(`invalid flags for user "foobar": ` +
38+
`user flag "!superuser" is not alphanumeric`),
39+
},
40+
{
41+
manifestUsers: map[string]spec.UserFlags{"foobar": {"superuser1", "createdb"}},
42+
err: fmt.Errorf(`invalid flags for user "foobar": ` +
43+
`user flag "SUPERUSER1" is not valid`),
44+
},
45+
{
46+
manifestUsers: map[string]spec.UserFlags{"foobar": {"inherit", "noinherit"}},
47+
err: fmt.Errorf(`invalid flags for user "foobar": ` +
48+
`conflicting user flags: "NOINHERIT" and "INHERIT"`),
49+
},
50+
}
51+
for _, tt := range tests {
52+
cl.Spec.Users = tt.manifestUsers
53+
cl.pgUsers = tt.infraRoles
54+
if err := cl.initRobotUsers(); err != nil {
55+
if tt.err == nil {
56+
t.Errorf("%s got an unexpected error: %v", testName, err)
57+
}
58+
if err.Error() != tt.err.Error() {
59+
t.Errorf("%s expected error %v, got %v", testName, tt.err, err)
60+
}
61+
} else {
62+
if !reflect.DeepEqual(cl.pgUsers, tt.result) {
63+
t.Errorf("%s expected: %#v, got %#v", testName, tt.result, cl.pgUsers)
64+
}
65+
}
66+
}
67+
}
68+
69+
type mockOAuthTokenGetter struct {
70+
}
71+
72+
func (m *mockOAuthTokenGetter) getOAuthToken() (string, error) {
73+
return "", nil
74+
}
75+
76+
type mockTeamsAPIClient struct {
77+
members []string
78+
}
79+
80+
func (m *mockTeamsAPIClient) TeamInfo(teamID, token string) (tm *teams.Team, err error) {
81+
return &teams.Team{Members: m.members}, nil
82+
}
83+
84+
func (m *mockTeamsAPIClient) setMembers(members []string) {
85+
m.members = members
86+
}
87+
88+
func TestInitHumanUsers(t *testing.T) {
89+
90+
var mockTeamsAPI mockTeamsAPIClient
91+
cl.oauthTokenGetter = &mockOAuthTokenGetter{}
92+
cl.teamsAPIClient = &mockTeamsAPI
93+
testName := "TestInitHumanUsers"
94+
95+
cl.OpConfig.EnableTeamSuperuser = true
96+
cl.OpConfig.EnableTeamsAPI = true
97+
cl.OpConfig.PamRoleName = "zalandos"
98+
cl.Spec.TeamID = "test"
99+
100+
tests := []struct {
101+
existingRoles map[string]spec.PgUser
102+
teamRoles []string
103+
result map[string]spec.PgUser
104+
}{
105+
{
106+
existingRoles: map[string]spec.PgUser{"foo": {Name: "foo", Flags: []string{"NOLOGIN"}},
107+
"bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
108+
teamRoles: []string{"foo"},
109+
result: map[string]spec.PgUser{"foo": {Name: "foo", MemberOf: []string{cl.OpConfig.PamRoleName}, Flags: []string{"LOGIN", "SUPERUSER"}},
110+
"bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
111+
},
112+
}
113+
114+
for _, tt := range tests {
115+
cl.pgUsers = tt.existingRoles
116+
mockTeamsAPI.setMembers(tt.teamRoles)
117+
if err := cl.initHumanUsers(); err != nil {
118+
t.Errorf("%s got an unexpected error %v", testName, err)
119+
}
120+
121+
if !reflect.DeepEqual(cl.pgUsers, tt.result) {
122+
t.Errorf("%s expects %#v, got %#v", testName, tt.result, cl.pgUsers)
123+
}
124+
}
125+
}

pkg/cluster/util.go

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,46 @@ import (
1616
"github.com/zalando-incubator/postgres-operator/pkg/spec"
1717
"github.com/zalando-incubator/postgres-operator/pkg/util"
1818
"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
19+
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
1920
"github.com/zalando-incubator/postgres-operator/pkg/util/retryutil"
2021
"sort"
2122
)
2223

24+
// OAuthTokenGetter provides the method for fetching OAuth tokens
25+
type OAuthTokenGetter interface {
26+
getOAuthToken() (string, error)
27+
}
28+
29+
// OAuthTokenGetter enables fetching OAuth tokens by reading Kubernetes secrets
30+
type SecretOauthTokenGetter struct {
31+
kubeClient *k8sutil.KubernetesClient
32+
OAuthTokenSecretName spec.NamespacedName
33+
}
34+
35+
func NewSecretOauthTokenGetter(kubeClient *k8sutil.KubernetesClient,
36+
OAuthTokenSecretName spec.NamespacedName) *SecretOauthTokenGetter {
37+
return &SecretOauthTokenGetter{kubeClient, OAuthTokenSecretName}
38+
}
39+
40+
func (g *SecretOauthTokenGetter) getOAuthToken() (string, error) {
41+
//TODO: we can move this function to the Controller in case it will be needed there. As for now we use it only in the Cluster
42+
// Temporary getting postgresql-operator secret from the NamespaceDefault
43+
credentialsSecret, err := g.kubeClient.
44+
Secrets(g.OAuthTokenSecretName.Namespace).
45+
Get(g.OAuthTokenSecretName.Name, metav1.GetOptions{})
46+
47+
if err != nil {
48+
return "", fmt.Errorf("could not get credentials secret: %v", err)
49+
}
50+
data := credentialsSecret.Data
51+
52+
if string(data["read-only-token-type"]) != "Bearer" {
53+
return "", fmt.Errorf("wrong token type: %v", data["read-only-token-type"])
54+
}
55+
56+
return string(data["read-only-token-secret"]), nil
57+
}
58+
2359
func isValidUsername(username string) bool {
2460
return userRegexp.MatchString(username)
2561
}
@@ -150,26 +186,6 @@ func (c *Cluster) logVolumeChanges(old, new spec.Volume) {
150186
c.logger.Debugf("diff\n%s\n", util.PrettyDiff(old, new))
151187
}
152188

153-
func (c *Cluster) getOAuthToken() (string, error) {
154-
//TODO: we can move this function to the Controller in case it will be needed there. As for now we use it only in the Cluster
155-
// Temporary getting postgresql-operator secret from the NamespaceDefault
156-
credentialsSecret, err := c.KubeClient.
157-
Secrets(c.OpConfig.OAuthTokenSecretName.Namespace).
158-
Get(c.OpConfig.OAuthTokenSecretName.Name, metav1.GetOptions{})
159-
160-
if err != nil {
161-
c.logger.Debugf("oauth token secret name: %q", c.OpConfig.OAuthTokenSecretName)
162-
return "", fmt.Errorf("could not get credentials secret: %v", err)
163-
}
164-
data := credentialsSecret.Data
165-
166-
if string(data["read-only-token-type"]) != "Bearer" {
167-
return "", fmt.Errorf("wrong token type: %v", data["read-only-token-type"])
168-
}
169-
170-
return string(data["read-only-token-secret"]), nil
171-
}
172-
173189
func (c *Cluster) getTeamMembers() ([]string, error) {
174190
if c.Spec.TeamID == "" {
175191
return nil, fmt.Errorf("no teamId specified")
@@ -179,7 +195,7 @@ func (c *Cluster) getTeamMembers() ([]string, error) {
179195
return []string{}, nil
180196
}
181197

182-
token, err := c.getOAuthToken()
198+
token, err := c.oauthTokenGetter.getOAuthToken()
183199
if err != nil {
184200
return []string{}, fmt.Errorf("could not get oauth token: %v", err)
185201
}

pkg/util/teams/teams.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ type httpClient interface {
4343
Do(req *http.Request) (*http.Response, error)
4444
}
4545

46+
type Interface interface {
47+
TeamInfo(teamID, token string) (tm *Team, err error)
48+
}
49+
4650
// API describes teams API
4751
type API struct {
4852
httpClient

0 commit comments

Comments
 (0)