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.
This commit is contained in:
parent
611cfe96d6
commit
637921cdee
|
|
@ -71,10 +71,11 @@ type Cluster struct {
|
||||||
deleteOptions *metav1.DeleteOptions
|
deleteOptions *metav1.DeleteOptions
|
||||||
podEventsQueue *cache.FIFO
|
podEventsQueue *cache.FIFO
|
||||||
|
|
||||||
teamsAPIClient *teams.API
|
teamsAPIClient teams.Interface
|
||||||
KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place?
|
oauthTokenGetter OAuthTokenGetter
|
||||||
currentProcess spec.Process
|
KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place?
|
||||||
processMu sync.RWMutex
|
currentProcess spec.Process
|
||||||
|
processMu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type compareStatefulsetResult struct {
|
type compareStatefulsetResult struct {
|
||||||
|
|
@ -112,9 +113,10 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec spec.Postgresql
|
||||||
deleteOptions: &metav1.DeleteOptions{OrphanDependents: &orphanDependents},
|
deleteOptions: &metav1.DeleteOptions{OrphanDependents: &orphanDependents},
|
||||||
podEventsQueue: podEventsQueue,
|
podEventsQueue: podEventsQueue,
|
||||||
KubeClient: kubeClient,
|
KubeClient: kubeClient,
|
||||||
teamsAPIClient: teams.NewTeamsAPI(cfg.OpConfig.TeamsAPIUrl, logger),
|
|
||||||
}
|
}
|
||||||
cluster.logger = logger.WithField("pkg", "cluster").WithField("cluster-name", cluster.clusterName())
|
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)
|
cluster.patroni = patroni.New(cluster.logger)
|
||||||
|
|
||||||
return cluster
|
return cluster
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,10 +16,46 @@ import (
|
||||||
"github.com/zalando-incubator/postgres-operator/pkg/spec"
|
"github.com/zalando-incubator/postgres-operator/pkg/spec"
|
||||||
"github.com/zalando-incubator/postgres-operator/pkg/util"
|
"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/constants"
|
||||||
|
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
|
||||||
"github.com/zalando-incubator/postgres-operator/pkg/util/retryutil"
|
"github.com/zalando-incubator/postgres-operator/pkg/util/retryutil"
|
||||||
"sort"
|
"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 {
|
func isValidUsername(username string) bool {
|
||||||
return userRegexp.MatchString(username)
|
return userRegexp.MatchString(username)
|
||||||
}
|
}
|
||||||
|
|
@ -150,26 +186,6 @@ func (c *Cluster) logVolumeChanges(old, new spec.Volume) {
|
||||||
c.logger.Debugf("diff\n%s\n", util.PrettyDiff(old, new))
|
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) {
|
func (c *Cluster) getTeamMembers() ([]string, error) {
|
||||||
if c.Spec.TeamID == "" {
|
if c.Spec.TeamID == "" {
|
||||||
return nil, fmt.Errorf("no teamId specified")
|
return nil, fmt.Errorf("no teamId specified")
|
||||||
|
|
@ -179,7 +195,7 @@ func (c *Cluster) getTeamMembers() ([]string, error) {
|
||||||
return []string{}, nil
|
return []string{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := c.getOAuthToken()
|
token, err := c.oauthTokenGetter.getOAuthToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}, fmt.Errorf("could not get oauth token: %v", err)
|
return []string{}, fmt.Errorf("could not get oauth token: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,10 @@ type httpClient interface {
|
||||||
Do(req *http.Request) (*http.Response, error)
|
Do(req *http.Request) (*http.Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Interface interface {
|
||||||
|
TeamInfo(teamID, token string) (tm *Team, err error)
|
||||||
|
}
|
||||||
|
|
||||||
// API describes teams API
|
// API describes teams API
|
||||||
type API struct {
|
type API struct {
|
||||||
httpClient
|
httpClient
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue