Skip to content

Commit

Permalink
Tests for initHumanUsers and initinitRobotUsers.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
alexeyklyukin committed Dec 1, 2017
1 parent 0643652 commit 9933120
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 26 deletions.
12 changes: 7 additions & 5 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ type Cluster struct {
deleteOptions *metav1.DeleteOptions
podEventsQueue *cache.FIFO

teamsAPIClient *teams.API
KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place?
currentProcess spec.Process
processMu sync.RWMutex
teamsAPIClient teams.Interface
oauthTokenGetter OAuthTokenGetter
KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place?
currentProcess spec.Process
processMu sync.RWMutex
}

type compareStatefulsetResult struct {
Expand Down Expand Up @@ -112,9 +113,10 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec spec.Postgresql
deleteOptions: &metav1.DeleteOptions{OrphanDependents: &orphanDependents},
podEventsQueue: podEventsQueue,
KubeClient: kubeClient,
teamsAPIClient: teams.NewTeamsAPI(cfg.OpConfig.TeamsAPIUrl, logger),
}
cluster.logger = logger.WithField("pkg", "cluster").WithField("cluster-name", cluster.clusterName())
cluster.teamsAPIClient = teams.NewTeamsAPI(cfg.OpConfig.TeamsAPIUrl, logger)
cluster.oauthTokenGetter = NewSecretOauthTokenGetter(&kubeClient, cfg.OpConfig.OAuthTokenSecretName)
cluster.patroni = patroni.New(cluster.logger)

return cluster
Expand Down
125 changes: 125 additions & 0 deletions pkg/cluster/cluster_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package cluster

import (
"fmt"
"github.com/Sirupsen/logrus"
"github.com/zalando-incubator/postgres-operator/pkg/spec"
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
"github.com/zalando-incubator/postgres-operator/pkg/util/teams"
"reflect"
"testing"
)

var logger = logrus.New().WithField("test", "cluster")
var cl = New(Config{}, k8sutil.KubernetesClient{}, spec.Postgresql{}, logger)

func TestInitRobotUsers(t *testing.T) {
testName := "TestInitRobotUsers"
tests := []struct {
manifestUsers map[string]spec.UserFlags
infraRoles map[string]spec.PgUser
result map[string]spec.PgUser
err error
}{
{
manifestUsers: map[string]spec.UserFlags{"foo": {"superuser", "createdb"}},
infraRoles: map[string]spec.PgUser{"foo": {Name: "foo", Password: "bar"}},
result: map[string]spec.PgUser{"foo": {Name: "foo", Password: "bar",
Flags: []string{"CREATEDB", "LOGIN", "SUPERUSER"}}},
err: nil,
},
{
manifestUsers: map[string]spec.UserFlags{"!fooBar": {"superuser", "createdb"}},
err: fmt.Errorf(`invalid username: "!fooBar"`),
},
{
manifestUsers: map[string]spec.UserFlags{"foobar": {"!superuser", "createdb"}},
err: fmt.Errorf(`invalid flags for user "foobar": ` +
`user flag "!superuser" is not alphanumeric`),
},
{
manifestUsers: map[string]spec.UserFlags{"foobar": {"superuser1", "createdb"}},
err: fmt.Errorf(`invalid flags for user "foobar": ` +
`user flag "SUPERUSER1" is not valid`),
},
{
manifestUsers: map[string]spec.UserFlags{"foobar": {"inherit", "noinherit"}},
err: fmt.Errorf(`invalid flags for user "foobar": ` +
`conflicting user flags: "NOINHERIT" and "INHERIT"`),
},
}
for _, tt := range tests {
cl.Spec.Users = tt.manifestUsers
cl.pgUsers = tt.infraRoles
if err := cl.initRobotUsers(); err != nil {
if tt.err == nil {
t.Errorf("%s got an unexpected error: %v", testName, err)
}
if err.Error() != tt.err.Error() {
t.Errorf("%s expected error %v, got %v", testName, tt.err, err)
}
} else {
if !reflect.DeepEqual(cl.pgUsers, tt.result) {
t.Errorf("%s expected: %#v, got %#v", testName, tt.result, cl.pgUsers)
}
}
}
}

type mockOAuthTokenGetter struct {
}

func (m *mockOAuthTokenGetter) getOAuthToken() (string, error) {
return "", nil
}

type mockTeamsAPIClient struct {
members []string
}

func (m *mockTeamsAPIClient) TeamInfo(teamID, token string) (tm *teams.Team, err error) {
return &teams.Team{Members: m.members}, nil
}

func (m *mockTeamsAPIClient) setMembers(members []string) {
m.members = members
}

func TestInitHumanUsers(t *testing.T) {

var mockTeamsAPI mockTeamsAPIClient
cl.oauthTokenGetter = &mockOAuthTokenGetter{}
cl.teamsAPIClient = &mockTeamsAPI
testName := "TestInitHumanUsers"

cl.OpConfig.EnableTeamSuperuser = true
cl.OpConfig.EnableTeamsAPI = true
cl.OpConfig.PamRoleName = "zalandos"
cl.Spec.TeamID = "test"

tests := []struct {
existingRoles map[string]spec.PgUser
teamRoles []string
result map[string]spec.PgUser
}{
{
existingRoles: map[string]spec.PgUser{"foo": {Name: "foo", Flags: []string{"NOLOGIN"}},
"bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
teamRoles: []string{"foo"},
result: map[string]spec.PgUser{"foo": {Name: "foo", MemberOf: []string{cl.OpConfig.PamRoleName}, Flags: []string{"LOGIN", "SUPERUSER"}},
"bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
},
}

for _, tt := range tests {
cl.pgUsers = tt.existingRoles
mockTeamsAPI.setMembers(tt.teamRoles)
if err := cl.initHumanUsers(); err != nil {
t.Errorf("%s got an unexpected error %v", testName, err)
}

if !reflect.DeepEqual(cl.pgUsers, tt.result) {
t.Errorf("%s expects %#v, got %#v", testName, tt.result, cl.pgUsers)
}
}
}
58 changes: 37 additions & 21 deletions pkg/cluster/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,46 @@ import (
"github.com/zalando-incubator/postgres-operator/pkg/spec"
"github.com/zalando-incubator/postgres-operator/pkg/util"
"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
"github.com/zalando-incubator/postgres-operator/pkg/util/retryutil"
"sort"
)

// OAuthTokenGetter provides the method for fetching OAuth tokens
type OAuthTokenGetter interface {
getOAuthToken() (string, error)
}

// OAuthTokenGetter enables fetching OAuth tokens by reading Kubernetes secrets
type SecretOauthTokenGetter struct {
kubeClient *k8sutil.KubernetesClient
OAuthTokenSecretName spec.NamespacedName
}

func NewSecretOauthTokenGetter(kubeClient *k8sutil.KubernetesClient,
OAuthTokenSecretName spec.NamespacedName) *SecretOauthTokenGetter {
return &SecretOauthTokenGetter{kubeClient, OAuthTokenSecretName}
}

func (g *SecretOauthTokenGetter) getOAuthToken() (string, error) {
//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
// Temporary getting postgresql-operator secret from the NamespaceDefault
credentialsSecret, err := g.kubeClient.
Secrets(g.OAuthTokenSecretName.Namespace).
Get(g.OAuthTokenSecretName.Name, metav1.GetOptions{})

if err != nil {
return "", fmt.Errorf("could not get credentials secret: %v", err)
}
data := credentialsSecret.Data

if string(data["read-only-token-type"]) != "Bearer" {
return "", fmt.Errorf("wrong token type: %v", data["read-only-token-type"])
}

return string(data["read-only-token-secret"]), nil
}

func isValidUsername(username string) bool {
return userRegexp.MatchString(username)
}
Expand Down Expand Up @@ -150,26 +186,6 @@ func (c *Cluster) logVolumeChanges(old, new spec.Volume) {
c.logger.Debugf("diff\n%s\n", util.PrettyDiff(old, new))
}

func (c *Cluster) getOAuthToken() (string, error) {
//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
// Temporary getting postgresql-operator secret from the NamespaceDefault
credentialsSecret, err := c.KubeClient.
Secrets(c.OpConfig.OAuthTokenSecretName.Namespace).
Get(c.OpConfig.OAuthTokenSecretName.Name, metav1.GetOptions{})

if err != nil {
c.logger.Debugf("oauth token secret name: %q", c.OpConfig.OAuthTokenSecretName)
return "", fmt.Errorf("could not get credentials secret: %v", err)
}
data := credentialsSecret.Data

if string(data["read-only-token-type"]) != "Bearer" {
return "", fmt.Errorf("wrong token type: %v", data["read-only-token-type"])
}

return string(data["read-only-token-secret"]), nil
}

func (c *Cluster) getTeamMembers() ([]string, error) {
if c.Spec.TeamID == "" {
return nil, fmt.Errorf("no teamId specified")
Expand All @@ -179,7 +195,7 @@ func (c *Cluster) getTeamMembers() ([]string, error) {
return []string{}, nil
}

token, err := c.getOAuthToken()
token, err := c.oauthTokenGetter.getOAuthToken()
if err != nil {
return []string{}, fmt.Errorf("could not get oauth token: %v", err)
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/util/teams/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ type httpClient interface {
Do(req *http.Request) (*http.Response, error)
}

type Interface interface {
TeamInfo(teamID, token string) (tm *Team, err error)
}

// API describes teams API
type API struct {
httpClient
Expand Down

0 comments on commit 9933120

Please sign in to comment.