Rename roles that are removed from PostgresTeam CRD (#1457)

* rename db roles that are removed from manifests

* extend PostgresTeam e2e test

* make suffix configurable and add deprecated field to pgUser struct

* deny LOGIN from deprecated roles

* update feature documentation
This commit is contained in:
Felix Kunde 2021-05-21 15:49:39 +02:00 committed by GitHub
parent 7a8dc6084d
commit eeb59c5bfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 262 additions and 49 deletions

View File

@ -443,6 +443,9 @@ spec:
enable_postgres_team_crd_superusers:
type: boolean
default: false
enable_team_member_deprecation:
type: boolean
default: false
enable_team_superuser:
type: boolean
default: false
@ -465,6 +468,9 @@ spec:
type: string
default:
- admin
role_deletion_suffix:
type: string
default: "_deleted"
team_admin_role:
type: string
default: "admin"

View File

@ -289,13 +289,13 @@ configLogicalBackup:
# automate creation of human users with teams API service
configTeamsApi:
# team_admin_role will have the rights to grant roles coming from PG manifests
# enable_admin_role_for_users: true
enable_admin_role_for_users: true
# operator watches for PostgresTeam CRs to assign additional teams and members to clusters
enable_postgres_team_crd: false
# toogle to create additional superuser teams from PostgresTeam CRs
# enable_postgres_team_crd_superusers: false
enable_postgres_team_crd_superusers: false
# toggle to automatically rename roles of former team members and deny LOGIN
enable_team_member_deprecation: false
# toggle to grant superuser to team members created from the Teams API
enable_team_superuser: false
# toggles usage of the Teams API by the operator
@ -306,12 +306,13 @@ configTeamsApi:
# operator will add all team member roles to this group and add a pg_hba line
pam_role_name: zalandos
# List of teams which members need the superuser role in each Postgres cluster
# postgres_superuser_teams:
# - postgres_superusers
postgres_superuser_teams:
- postgres_superusers
# List of roles that cannot be overwritten by an application, team or infrastructure role
protected_role_names:
- admin
# Suffix to add if members are removed from TeamsAPI or PostgresTeam CRD
role_deletion_suffix: "_deleted"
# role name to grant to team members created from the Teams API
team_admin_role: admin
# postgres config parameters to apply to each team member role

View File

@ -280,36 +280,32 @@ configLogicalBackup:
# automate creation of human users with teams API service
configTeamsApi:
# team_admin_role will have the rights to grant roles coming from PG manifests
# enable_admin_role_for_users: "true"
enable_admin_role_for_users: "true"
# operator watches for PostgresTeam CRs to assign additional teams and members to clusters
enable_postgres_team_crd: "false"
# toogle to create additional superuser teams from PostgresTeam CRs
# enable_postgres_team_crd_superusers: "false"
enable_postgres_team_crd_superusers: "false"
# toggle to automatically rename roles of former team members and deny LOGIN
enable_team_member_deprecation: "false"
# toggle to grant superuser to team members created from the Teams API
# enable_team_superuser: "false"
enable_team_superuser: "false"
# toggles usage of the Teams API by the operator
enable_teams_api: "false"
# should contain a URL to use for authentication (username and token)
# pam_configuration: https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees
# operator will add all team member roles to this group and add a pg_hba line
# pam_role_name: zalandos
pam_role_name: "zalandos"
# List of teams which members need the superuser role in each Postgres cluster
# postgres_superuser_teams: "postgres_superusers"
postgres_superuser_teams: "postgres_superusers"
# List of roles that cannot be overwritten by an application, team or infrastructure role
# protected_role_names: "admin"
protected_role_names: "admin"
# Suffix to add if members are removed from TeamsAPI or PostgresTeam CRD
role_deletion_suffix: "_deleted"
# role name to grant to team members created from the Teams API
# team_admin_role: "admin"
team_admin_role: "admin"
# postgres config parameters to apply to each team member role
# team_api_role_configuration: "log_statement:all"
team_api_role_configuration: "log_statement:all"
# URL of the Teams API service
# teams_api_url: http://fake-teams-api.default.svc.cluster.local

View File

@ -704,6 +704,19 @@ key.
cluster to administer Postgres and maintain infrastructure built around it.
The default is empty.
* **role_deletion_suffix**
defines a suffix that - when `enable_team_member_deprecation` is set to
`true` - will be appended to database role names of team members that were
removed from either the team in the Teams API or a `PostgresTeam` custom
resource (additionalMembers). When re-added, the operator will rename roles
with the defined suffix back to the original role name.
The default is `_deleted`.
* **enable_team_member_deprecation**
if `true` database roles of former team members will be renamed by appending
the configured `role_deletion_suffix` and `LOGIN` privilege will be revoked.
The default is `false`.
* **enable_postgres_team_crd**
toggle to make the operator watch for created or updated `PostgresTeam` CRDs
and create roles for specified additional teams and members.

View File

@ -407,6 +407,23 @@ spec:
- "briggs"
```
#### Removed members
The Postgres Operator does not delete database roles when users are removed
from manifests. But, using the `PostgresTeam` custom resource or Teams API it
is very easy to add roles to many clusters. Manually reverting such a change
is cumbersome. Therefore, if members are removed from a `PostgresTeam` or the
Teams API the operator can rename roles appending a configured suffix to the
name (see `role_deletion_suffix` option) and revoke the `LOGIN` privilege.
The suffix makes it easy then for a cleanup script to remove those deprecated
roles completely. Switch `enable_team_member_deprecation` to `true` to enable
this behavior.
When a role is re-added to a `PostgresTeam` manifest (or to the source behind
the Teams API) the operator will check for roles with the configured suffix
and if found, rename the role back to the original name and grant `LOGIN`
again.
## Prepared databases with roles and default privileges
The `users` section in the manifests only allows for creating database roles

View File

@ -197,13 +197,15 @@ class EndToEndTestCase(unittest.TestCase):
enable_postgres_team_crd = {
"data": {
"enable_postgres_team_crd": "true",
"resync_period": "15s",
"enable_team_member_deprecation": "true",
"role_deletion_suffix": "_delete_me",
"resync_period": "15s"
},
}
self.k8s.update_config(enable_postgres_team_crd)
self.eventuallyEqual(lambda: self.k8s.get_operator_state(), {"0": "idle"},
"Operator does not get in sync")
self.k8s.api.custom_objects_api.patch_namespaced_custom_object(
'acid.zalan.do', 'v1', 'default',
'postgresteams', 'custom-team-membership',
@ -222,18 +224,60 @@ class EndToEndTestCase(unittest.TestCase):
}
})
# make sure we let one sync pass and the new user being added
time.sleep(15)
leader = self.k8s.get_cluster_leader_pod()
user_query = """
SELECT usename
FROM pg_catalog.pg_user
WHERE usename IN ('elephant', 'kind');
SELECT rolname
FROM pg_catalog.pg_roles
WHERE rolname IN ('elephant', 'kind');
"""
users = self.query_database(leader.metadata.name, "postgres", user_query)
self.eventuallyEqual(lambda: len(users), 2,
"Not all additional users found in database: {}".format(users))
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
"Not all additional users found in database", 10, 5)
# replace additional member and check if the removed member's role is renamed
self.k8s.api.custom_objects_api.patch_namespaced_custom_object(
'acid.zalan.do', 'v1', 'default',
'postgresteams', 'custom-team-membership',
{
'spec': {
'additionalMembers': {
'e2e': [
'tester'
]
},
}
})
user_query = """
SELECT rolname
FROM pg_catalog.pg_roles
WHERE (rolname = 'tester' AND rolcanlogin)
OR (rolname = 'kind_delete_me' AND NOT rolcanlogin);
"""
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
"Database role of replaced member in PostgresTeam not renamed", 10, 5)
# re-add additional member and check if the role is renamed back
self.k8s.api.custom_objects_api.patch_namespaced_custom_object(
'acid.zalan.do', 'v1', 'default',
'postgresteams', 'custom-team-membership',
{
'spec': {
'additionalMembers': {
'e2e': [
'kind'
]
},
}
})
user_query = """
SELECT rolname
FROM pg_catalog.pg_roles
WHERE (rolname = 'kind' AND rolcanlogin)
OR (rolname = 'tester_delete_me' AND NOT rolcanlogin);
"""
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
"Database role of recreated member in PostgresTeam not renamed back to original name", 10, 5)
# revert config change
revert_resync = {
@ -407,9 +451,9 @@ class EndToEndTestCase(unittest.TestCase):
leader = k8s.get_cluster_leader_pod()
schemas_query = """
select schema_name
from information_schema.schemata
where schema_name = 'pooler'
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name = 'pooler'
"""
db_list = self.list_databases(leader.metadata.name)
@ -529,6 +573,7 @@ class EndToEndTestCase(unittest.TestCase):
"Parameters": None,
"AdminRole": "",
"Origin": 2,
"Deleted": False
})
return True
except:
@ -1417,7 +1462,7 @@ class EndToEndTestCase(unittest.TestCase):
k8s = self.k8s
result_set = []
db_list = []
db_list_query = "select datname from pg_database"
db_list_query = "SELECT datname FROM pg_database"
exec_query = r"psql -tAq -c \"{}\" -d {}"
try:

View File

@ -51,6 +51,7 @@ data:
# enable_shm_volume: "true"
# enable_sidecars: "true"
enable_spilo_wal_path_compat: "true"
enable_team_member_deprecation: "false"
# enable_team_superuser: "false"
enable_teams_api: "false"
# etcd_host: ""
@ -111,6 +112,7 @@ data:
resource_check_timeout: 10m
resync_period: 30m
ring_log_lines: "100"
role_deletion_suffix: "_deleted"
secret_name_template: "{username}.{cluster}.credentials"
# sidecar_docker_images: ""
# set_memory_request_to_limit: "false"

View File

@ -439,6 +439,9 @@ spec:
enable_postgres_team_crd_superusers:
type: boolean
default: false
enable_team_member_deprecation:
type: boolean
default: false
enable_team_superuser:
type: boolean
default: false
@ -461,6 +464,9 @@ spec:
type: string
default:
- admin
role_deletion_suffix:
type: string
default: "_deleted"
team_admin_role:
type: string
default: "admin"

View File

@ -141,6 +141,7 @@ configuration:
# enable_admin_role_for_users: true
# enable_postgres_team_crd: false
# enable_postgres_team_crd_superusers: false
enable_team_member_deprecation: false
enable_team_superuser: false
enable_teams_api: false
# pam_configuration: ""
@ -149,6 +150,7 @@ configuration:
# - postgres_superusers
protected_role_names:
- admin
role_deletion_suffix: "_deleted"
team_admin_role: admin
team_api_role_configuration:
log_statement: all

View File

@ -1377,6 +1377,9 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
"enable_postgres_team_crd_superusers": {
Type: "boolean",
},
"enable_team_member_deprecation": {
Type: "boolean",
},
"enable_team_superuser": {
Type: "boolean",
},
@ -1405,6 +1408,9 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
},
},
},
"role_deletion_suffix": {
Type: "string",
},
"team_admin_role": {
Type: "string",
},

View File

@ -159,6 +159,8 @@ type TeamsAPIConfiguration struct {
PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"`
EnablePostgresTeamCRD bool `json:"enable_postgres_team_crd,omitempty"`
EnablePostgresTeamCRDSuperusers bool `json:"enable_postgres_team_crd_superusers,omitempty"`
EnableTeamMemberDeprecation bool `json:"enable_team_member_deprecation,omitempty"`
RoleDeletionSuffix string `json:"role_deletion_suffix,omitempty"`
}
// LoggingRESTAPIConfiguration defines Logging API conf

View File

@ -74,6 +74,7 @@ type Cluster struct {
eventRecorder record.EventRecorder
patroni patroni.Interface
pgUsers map[string]spec.PgUser
pgUsersCache map[string]spec.PgUser
systemUsers map[string]spec.PgUser
podSubscribers map[spec.NamespacedName]chan PodEvent
podSubscribersMu sync.RWMutex
@ -129,7 +130,9 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres
Secrets: make(map[types.UID]*v1.Secret),
Services: make(map[PostgresRole]*v1.Service),
Endpoints: make(map[PostgresRole]*v1.Endpoints)},
userSyncStrategy: users.DefaultUserSyncStrategy{PasswordEncryption: passwordEncryption},
userSyncStrategy: users.DefaultUserSyncStrategy{
PasswordEncryption: passwordEncryption,
RoleDeletionSuffix: cfg.OpConfig.RoleDeletionSuffix},
deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy},
podEventsQueue: podEventsQueue,
KubeClient: kubeClient,
@ -190,6 +193,17 @@ func (c *Cluster) isNewCluster() bool {
func (c *Cluster) initUsers() error {
c.setProcessName("initializing users")
// if team member deprecation is enabled save current state of pgUsers
// to check for deleted roles
c.pgUsersCache = map[string]spec.PgUser{}
if c.OpConfig.EnableTeamMemberDeprecation {
for k, v := range c.pgUsers {
if v.Origin == spec.RoleOriginTeamsAPI {
c.pgUsersCache[k] = v
}
}
}
// clear our the previous state of the cluster users (in case we are
// running a sync).
c.systemUsers = map[string]spec.PgUser{}
@ -650,7 +664,7 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
needConnectionPooler := needMasterConnectionPoolerWorker(&newSpec.Spec) ||
needReplicaConnectionPoolerWorker(&newSpec.Spec)
if !sameUsers || needConnectionPooler {
c.logger.Debugf("syncing secrets")
c.logger.Debugf("initialize users")
if err := c.initUsers(); err != nil {
c.logger.Errorf("could not init users: %v", err)
updateFailed = true

View File

@ -198,6 +198,7 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser
rolname, rolpassword string
rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool
roloptions, memberof []string
roldeleted bool
)
err := rows.Scan(&rolname, &rolpassword, &rolsuper, &rolinherit,
&rolcreaterole, &rolcreatedb, &rolcanlogin, pq.Array(&roloptions), pq.Array(&memberof))
@ -216,7 +217,11 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser
parameters[fields[0]] = fields[1]
}
users[rolname] = spec.PgUser{Name: rolname, Password: rolpassword, Flags: flags, MemberOf: memberof, Parameters: parameters}
if strings.HasSuffix(rolname, c.OpConfig.RoleDeletionSuffix) {
roldeleted = true
}
users[rolname] = spec.PgUser{Name: rolname, Password: rolpassword, Flags: flags, MemberOf: memberof, Parameters: parameters, Deleted: roldeleted}
}
return users, nil

View File

@ -551,10 +551,29 @@ func (c *Cluster) syncRoles() (err error) {
}
}()
// mapping between original role name and with deletion suffix
deletedUsers := map[string]string{}
// create list of database roles to query
for _, u := range c.pgUsers {
userNames = append(userNames, u.Name)
// add team member role name with rename suffix in case we need to rename it back
if u.Origin == spec.RoleOriginTeamsAPI && c.OpConfig.EnableTeamMemberDeprecation {
deletedUsers[u.Name+c.OpConfig.RoleDeletionSuffix] = u.Name
userNames = append(userNames, u.Name+c.OpConfig.RoleDeletionSuffix)
}
}
// add team members that exist only in cache
// to trigger a rename of the role in ProduceSyncRequests
for _, cachedUser := range c.pgUsersCache {
if _, exists := c.pgUsers[cachedUser.Name]; !exists {
userNames = append(userNames, cachedUser.Name)
}
}
// add pooler user to list of pgUsers, too
// to check if the pooler user exists or has to be created
if needMasterConnectionPooler(&c.Spec) || needReplicaConnectionPooler(&c.Spec) {
connectionPoolerUser := c.systemUsers[constants.ConnectionPoolerUserKeyName]
userNames = append(userNames, connectionPoolerUser.Name)
@ -569,6 +588,16 @@ func (c *Cluster) syncRoles() (err error) {
return fmt.Errorf("error getting users from the database: %v", err)
}
// update pgUsers where a deleted role was found
// so that they are skipped in ProduceSyncRequests
for _, dbUser := range dbUsers {
if originalUser, exists := deletedUsers[dbUser.Name]; exists {
recreatedUser := c.pgUsers[originalUser]
recreatedUser.Deleted = true
c.pgUsers[originalUser] = recreatedUser
}
}
pgSyncRequests := c.userSyncStrategy.ProduceSyncRequests(dbUsers, c.pgUsers)
if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil {
return fmt.Errorf("error executing sync statements: %v", err)

View File

@ -242,7 +242,7 @@ func (c *Cluster) getTeamMembers(teamID string) ([]string, error) {
for team, membership := range *c.Config.PgTeamMap {
if team == teamID {
additionalMembers = membership.AdditionalMembers
c.logger.Debugf("found %d additional members for team %q", len(members), teamID)
c.logger.Debugf("found %d additional members for team %q", len(additionalMembers), teamID)
}
}
@ -256,14 +256,12 @@ func (c *Cluster) getTeamMembers(teamID string) ([]string, error) {
token, err := c.oauthTokenGetter.getOAuthToken()
if err != nil {
c.logger.Warnf("could not get oauth token to authenticate to team service API, only returning %d members for team %q: %v", len(members), teamID, err)
return members, nil
return nil, fmt.Errorf("could not get oauth token to authenticate to team service API: %v", err)
}
teamInfo, err := c.teamsAPIClient.TeamInfo(teamID, token)
if err != nil {
c.logger.Warnf("could not get team info for team %q, only returning %d members: %v", teamID, len(members), err)
return members, nil
return nil, fmt.Errorf("could not get team info for team %q: %v", teamID, err)
}
for _, member := range teamInfo.Members {

View File

@ -180,6 +180,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
result.PostgresSuperuserTeams = fromCRD.TeamsAPI.PostgresSuperuserTeams
result.EnablePostgresTeamCRD = fromCRD.TeamsAPI.EnablePostgresTeamCRD
result.EnablePostgresTeamCRDSuperusers = fromCRD.TeamsAPI.EnablePostgresTeamCRDSuperusers
result.EnableTeamMemberDeprecation = fromCRD.TeamsAPI.EnableTeamMemberDeprecation
result.RoleDeletionSuffix = util.Coalesce(fromCRD.TeamsAPI.RoleDeletionSuffix, "_deleted")
// logging REST API config
result.APIPort = util.CoalesceInt(fromCRD.LoggingRESTAPI.APIPort, 8080)

View File

@ -42,6 +42,7 @@ const (
PGSyncUserAdd = iota
PGsyncUserAlter
PGSyncAlterSet // handle ALTER ROLE SET parameter = value
PGSyncUserRename
)
// PgUser contains information about a single user.
@ -53,6 +54,7 @@ type PgUser struct {
MemberOf []string `yaml:"inrole"`
Parameters map[string]string `yaml:"db_parameters"`
AdminRole string `yaml:"admin_role"`
Deleted bool `yaml:"deleted"`
}
func (user *PgUser) Valid() bool {

View File

@ -9,8 +9,6 @@ import (
)
var (
True = true
False = false
pgTeamList = acidv1.PostgresTeamList{
TypeMeta: metav1.TypeMeta{
Kind: "List",

View File

@ -176,6 +176,8 @@ type Config struct {
EnableTeamsAPI bool `name:"enable_teams_api" default:"true"`
EnableTeamSuperuser bool `name:"enable_team_superuser" default:"false"`
TeamAdminRole string `name:"team_admin_role" default:"admin"`
RoleDeletionSuffix string `name:"role_deletion_suffix" default:"_deleted"`
EnableTeamMemberDeprecation bool `name:"enable_team_member_deprecation" default:"false"`
EnableAdminRoleForUsers bool `name:"enable_admin_role_for_users" default:"true"`
EnablePostgresTeamCRD bool `name:"enable_postgres_team_crd" default:"false"`
EnablePostgresTeamCRDSuperusers bool `name:"enable_postgres_team_crd_superusers" default:"false"`

View File

@ -9,11 +9,13 @@ import (
"github.com/zalando/postgres-operator/pkg/spec"
"github.com/zalando/postgres-operator/pkg/util"
"github.com/zalando/postgres-operator/pkg/util/constants"
)
const (
createUserSQL = `SET LOCAL synchronous_commit = 'local'; CREATE ROLE "%s" %s %s;`
alterUserSQL = `ALTER ROLE "%s" %s`
alterUserRenameSQL = `ALTER ROLE "%s" RENAME TO "%s%s"`
alterRoleResetAllSQL = `ALTER ROLE "%s" RESET ALL`
alterRoleSetSQL = `ALTER ROLE "%s" SET %s TO %s`
grantToUserSQL = `GRANT %s TO "%s"`
@ -29,6 +31,7 @@ const (
// (except for the NOLOGIN). TODO: process other NOflags, i.e. NOSUPERUSER correctly.
type DefaultUserSyncStrategy struct {
PasswordEncryption string
RoleDeletionSuffix string
}
// ProduceSyncRequests figures out the types of changes that need to happen with the given users.
@ -36,8 +39,11 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
newUsers spec.PgUserMap) []spec.PgSyncUserRequest {
var reqs []spec.PgSyncUserRequest
// No existing roles are deleted or stripped of role memebership/flags
for name, newUser := range newUsers {
// do not create user that exists in DB with deletion suffix
if newUser.Deleted {
continue
}
dbUser, exists := dbUsers[name]
if !exists {
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncUserAdd, User: newUser})
@ -70,6 +76,25 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
}
}
// No existing roles are deleted or stripped of role membership/flags
// but team roles will be renamed and denied from LOGIN
for name, dbUser := range dbUsers {
if _, exists := newUsers[name]; !exists {
// toggle LOGIN flag based on role deletion
userFlags := make([]string, len(dbUser.Flags))
userFlags = append(userFlags, dbUser.Flags...)
if dbUser.Deleted {
dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagNoLogin, constants.RoleFlagLogin)
} else {
dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagLogin, constants.RoleFlagNoLogin)
}
if !util.IsEqualIgnoreOrder(userFlags, dbUser.Flags) {
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGsyncUserAlter, User: dbUser})
}
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncUserRename, User: dbUser})
}
}
return reqs
}
@ -94,6 +119,11 @@ func (strategy DefaultUserSyncStrategy) ExecuteSyncRequests(requests []spec.PgSy
reqretries = append(reqretries, request)
errors = append(errors, fmt.Sprintf("could not set custom user %q parameters: %v", request.User.Name, err))
}
case spec.PGSyncUserRename:
if err := strategy.alterPgUserRename(request.User, db); err != nil {
reqretries = append(reqretries, request)
errors = append(errors, fmt.Sprintf("could not rename custom user %q: %v", request.User.Name, err))
}
default:
return fmt.Errorf("unrecognized operation: %v", request.Kind)
}
@ -124,6 +154,23 @@ func (strategy DefaultUserSyncStrategy) alterPgUserSet(user spec.PgUser, db *sql
return nil
}
func (strategy DefaultUserSyncStrategy) alterPgUserRename(user spec.PgUser, db *sql.DB) error {
var query string
// append or trim deletion suffix depending if the user has the suffix or not
if user.Deleted {
newName := strings.TrimSuffix(user.Name, strategy.RoleDeletionSuffix)
query = fmt.Sprintf(alterUserRenameSQL, user.Name, newName, "")
} else {
query = fmt.Sprintf(alterUserRenameSQL, user.Name, user.Name, strategy.RoleDeletionSuffix)
}
if _, err := db.Exec(query); err != nil {
return err
}
return nil
}
func (strategy DefaultUserSyncStrategy) createPgUser(user spec.PgUser, db *sql.DB) error {
var userFlags []string
var userPassword string

View File

@ -151,6 +151,18 @@ func IsEqualIgnoreOrder(a, b []string) bool {
return reflect.DeepEqual(a_copy, b_copy)
}
// SliceReplaceElement
func StringSliceReplaceElement(s []string, a, b string) (result []string) {
tmp := make([]string, 0, len(s))
for _, str := range s {
if str == a {
str = b
}
tmp = append(tmp, str)
}
return tmp
}
// SubstractStringSlices finds elements in a that are not in b and return them as a result slice.
func SubstractStringSlices(a []string, b []string) (result []string, equal bool) {
// Slices are assumed to contain unique elements only

View File

@ -166,6 +166,14 @@ func TestIsEqualIgnoreOrder(t *testing.T) {
}
}
func TestStringSliceReplaceElement(t *testing.T) {
testSlice := []string{"a", "b", "c"}
testSlice = StringSliceReplaceElement(testSlice, "b", "d")
if !SliceContains(testSlice, "d") {
t.Errorf("testSlide item not replaced: %v", testSlice)
}
}
func TestSubstractSlices(t *testing.T) {
for _, tt := range substractTest {
actualRes, actualEqual := SubstractStringSlices(tt.inA, tt.inB)