PostgresTeamCRD for advanced team management

This commit is contained in:
Felix Kunde 2020-10-12 16:20:00 +02:00
parent 38e15183a2
commit 9f0e1f703b
19 changed files with 1073 additions and 28 deletions

View File

@ -0,0 +1,63 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: postgresteams.acid.zalan.do
spec:
group: acid.zalan.do
names:
kind: PostgresTeam
listKind: PostgresTeamList
plural: postgresteams
singular: postgresteam
shortNames:
- pgteam
scope: Namespaced
subresources:
status: {}
version: v1
validation:
openAPIV3Schema:
type: object
required:
- kind
- apiVersion
- spec
properties:
kind:
type: string
enum:
- postgresteam
apiVersion:
type: string
enum:
- acid.zalan.do/v1
spec:
type: object
properties:
additionalAdminTeams:
type: object
description: "Map for teamId and associted additional admin teams"
additionalProperties:
type: array
nullable: true
description: "List of teams to become Postgres admins"
items:
type: string
additionalTeams:
type: object
description: "Map for teamId and associted additional teams"
additionalProperties:
type: array
nullable: true
description: "List of teams whose members will also be added to the Postgres cluster"
items:
type: string
additionalUsers:
type: object
description: "Map for teamId and associted additional users"
additionalProperties:
type: array
nullable: true
description: "List of users who will also be added to the Postgres cluster"
items:
type: string

View File

@ -0,0 +1,33 @@
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// PostgresTeam defines Custom Resource Definition Object for team management.
type PostgresTeam struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PostgresTeamSpec `json:"spec"`
}
// PostgresTeamSpec defines the specification for the PostgresTeam TPR.
type PostgresTeamSpec struct {
AdditionalAdminTeams map[string][]string `json:"additionalAdminTeams,omitempty"`
AdditionalTeams map[string][]string `json:"additionalTeams,omitempty"`
AdditionalMembers map[string][]string `json:"additionalMembers,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// PostgresTeamList defines a list of PostgresTeam definitions.
type PostgresTeamList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []PostgresTeam `json:"items"`
}

View File

@ -711,6 +711,127 @@ func (in *PostgresStatus) DeepCopy() *PostgresStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PostgresTeam) DeepCopyInto(out *PostgresTeam) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresTeam.
func (in *PostgresTeam) DeepCopy() *PostgresTeam {
if in == nil {
return nil
}
out := new(PostgresTeam)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PostgresTeam) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PostgresTeamList) DeepCopyInto(out *PostgresTeamList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]PostgresTeam, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresTeamList.
func (in *PostgresTeamList) DeepCopy() *PostgresTeamList {
if in == nil {
return nil
}
out := new(PostgresTeamList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PostgresTeamList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PostgresTeamSpec) DeepCopyInto(out *PostgresTeamSpec) {
*out = *in
if in.AdditionalAdminTeams != nil {
in, out := &in.AdditionalAdminTeams, &out.AdditionalAdminTeams
*out = make(map[string][]string, len(*in))
for key, val := range *in {
var outVal []string
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]string, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
if in.AdditionalTeams != nil {
in, out := &in.AdditionalTeams, &out.AdditionalTeams
*out = make(map[string][]string, len(*in))
for key, val := range *in {
var outVal []string
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]string, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
if in.AdditionalMembers != nil {
in, out := &in.AdditionalMembers, &out.AdditionalMembers
*out = make(map[string][]string, len(*in))
for key, val := range *in {
var outVal []string
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]string, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresTeamSpec.
func (in *PostgresTeamSpec) DeepCopy() *PostgresTeamSpec {
if in == nil {
return nil
}
out := new(PostgresTeamSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PostgresUsersConfiguration) DeepCopyInto(out *PostgresUsersConfiguration) {
*out = *in

View File

@ -14,18 +14,9 @@ import (
"github.com/r3labs/diff"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
policybeta1 "k8s.io/api/policy/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/tools/reference"
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
"github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/scheme"
"github.com/zalando/postgres-operator/pkg/postgresteams"
"github.com/zalando/postgres-operator/pkg/spec"
"github.com/zalando/postgres-operator/pkg/util"
"github.com/zalando/postgres-operator/pkg/util/config"
@ -34,7 +25,16 @@ import (
"github.com/zalando/postgres-operator/pkg/util/patroni"
"github.com/zalando/postgres-operator/pkg/util/teams"
"github.com/zalando/postgres-operator/pkg/util/users"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
policybeta1 "k8s.io/api/policy/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/tools/reference"
)
var (
@ -48,6 +48,7 @@ var (
type Config struct {
OpConfig config.Config
RestConfig *rest.Config
PgTeamMap postgresteams.PostgresTeamMap
InfrastructureRoles map[string]spec.PgUser // inherited from the controller
PodServiceAccount *v1.ServiceAccount
PodServiceAccountRoleBinding *rbacv1.RoleBinding
@ -1130,17 +1131,24 @@ func (c *Cluster) initTeamMembers(teamID string, isPostgresSuperuserTeam bool) e
func (c *Cluster) initHumanUsers() error {
var clusterIsOwnedBySuperuserTeam bool
for _, postgresSuperuserTeam := range c.OpConfig.PostgresSuperuserTeams {
err := c.initTeamMembers(postgresSuperuserTeam, true)
if err != nil {
return fmt.Errorf("Cannot create a team %q of Postgres superusers: %v", postgresSuperuserTeam, err)
}
if postgresSuperuserTeam == c.Spec.TeamID {
clusterIsOwnedBySuperuserTeam = true
}
}
c.PgTeamMap.MergeTeams(c.Spec.TeamID, c.OpConfig.PostgresSuperuserTeams, true)
for additionalTeam := range c.PgTeamMap[c.Spec.TeamID].AdditionalTeams {
err := c.initTeamMembers(additionalTeam.Name, additionalTeam.IsAdmin)
if err != nil {
errorMsg := fmt.Sprintf("Cannot create team %q", additionalTeam.Name)
if additionalTeam.IsAdmin {
errorMsg = errorMsg + " of Postgres superusers"
}
return fmt.Errorf(errorMsg+": %v", err)
}
}
if clusterIsOwnedBySuperuserTeam {
c.logger.Infof("Team %q owning the cluster is also a team of superusers. Created superuser roles for its members instead of admin roles.", c.Spec.TeamID)
return nil

View File

@ -217,24 +217,44 @@ func (c *Cluster) getTeamMembers(teamID string) ([]string, error) {
return nil, fmt.Errorf("no teamId specified")
}
c.logger.Debugf("fetching possible additional team members for team %q", teamID)
members := []string{}
additionalMembers := c.PgTeamMap[c.Spec.TeamID].AdditionalMembers
for member := range additionalMembers {
members = append(members, member)
}
if !c.OpConfig.EnableTeamsAPI {
c.logger.Debugf("team API is disabled, returning empty list of members for team %q", teamID)
return []string{}, nil
c.logger.Debugf("team API is disabled, returning only additional members for team %q if set", teamID)
return members, nil
}
token, err := c.oauthTokenGetter.getOAuthToken()
if err != nil {
c.logger.Warnf("could not get oauth token to authenticate to team service API, returning empty list of team members: %v", err)
return []string{}, nil
c.logger.Warnf("could not get oauth token to authenticate to team service API, returning only additional members for team %q if set: %v", teamID, err)
return members, nil
}
teamInfo, err := c.teamsAPIClient.TeamInfo(teamID, token)
if err != nil {
c.logger.Warnf("could not get team info for team %q, returning empty list of team members: %v", teamID, err)
return []string{}, nil
c.logger.Warnf("could not get team info for team %q, returning only additional members if set: %v", teamID, err)
return members, nil
}
return teamInfo.Members, nil
for _, member := range teamInfo.Members {
contains := false
for _, additionalMember := range members {
if member == additionalMember {
contains = true
break
}
}
if !(contains) {
members = append(members, member)
}
}
return members, nil
}
func (c *Cluster) waitForPodLabel(podEvents chan PodEvent, stopChan chan struct{}, role *PostgresRole) (*v1.Pod, error) {

View File

@ -12,6 +12,7 @@ import (
"github.com/zalando/postgres-operator/pkg/apiserver"
"github.com/zalando/postgres-operator/pkg/cluster"
acidv1informer "github.com/zalando/postgres-operator/pkg/generated/informers/externalversions/acid.zalan.do/v1"
"github.com/zalando/postgres-operator/pkg/postgresteams"
"github.com/zalando/postgres-operator/pkg/spec"
"github.com/zalando/postgres-operator/pkg/util"
"github.com/zalando/postgres-operator/pkg/util/config"
@ -31,8 +32,9 @@ import (
// Controller represents operator controller
type Controller struct {
config spec.ControllerConfig
opConfig *config.Config
config spec.ControllerConfig
opConfig *config.Config
pgTeamMap *postgresteams.PostgresTeamMap
logger *logrus.Entry
KubeClient k8sutil.KubernetesClient
@ -53,10 +55,11 @@ type Controller struct {
clusterHistory map[spec.NamespacedName]ringlog.RingLogger // history of the cluster changes
teamClusters map[string][]spec.NamespacedName
postgresqlInformer cache.SharedIndexInformer
podInformer cache.SharedIndexInformer
nodesInformer cache.SharedIndexInformer
podCh chan cluster.PodEvent
postgresqlInformer cache.SharedIndexInformer
postgresTeamInformer cache.SharedIndexInformer
podInformer cache.SharedIndexInformer
nodesInformer cache.SharedIndexInformer
podCh chan cluster.PodEvent
clusterEventQueues []*cache.FIFO // [workerID]Queue
lastClusterSyncTime int64
@ -338,6 +341,17 @@ func (c *Controller) initSharedInformers() {
DeleteFunc: c.postgresqlDelete,
})
c.postgresTeamInformer = acidv1informer.NewPostgresTeamInformer(
c.KubeClient.AcidV1ClientSet,
c.opConfig.WatchedNamespace,
constants.QueueResyncPeriodTPR,
cache.Indexers{})
c.postgresTeamInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: c.loadPostgresTeams,
UpdateFunc: c.updatePostgresTeams,
})
// Pods
podLw := &cache.ListWatch{
ListFunc: c.podListFunc,
@ -394,6 +408,7 @@ func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
wg.Add(5)
go c.runPodInformer(stopCh, wg)
go c.runPostgresqlInformer(stopCh, wg)
go c.runPostgresTeamInformer(stopCh, wg)
go c.clusterResync(stopCh, wg)
go c.apiserver.Run(stopCh, wg)
go c.kubeNodesInformer(stopCh, wg)
@ -413,6 +428,12 @@ func (c *Controller) runPostgresqlInformer(stopCh <-chan struct{}, wg *sync.Wait
c.postgresqlInformer.Run(stopCh)
}
func (c *Controller) runPostgresTeamInformer(stopCh <-chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
c.postgresTeamInformer.Run(stopCh)
}
func queueClusterKey(eventType EventType, uid types.UID) string {
return fmt.Sprintf("%s-%s", eventType, uid)
}

View File

@ -14,6 +14,7 @@ import (
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
"github.com/zalando/postgres-operator/pkg/cluster"
"github.com/zalando/postgres-operator/pkg/postgresteams"
"github.com/zalando/postgres-operator/pkg/spec"
"github.com/zalando/postgres-operator/pkg/util"
"github.com/zalando/postgres-operator/pkg/util/config"
@ -394,6 +395,26 @@ func (c *Controller) getInfrastructureRole(
return roles, nil
}
func (c *Controller) loadPostgresTeams(obj interface{}) {
var pgTeamMap postgresteams.PostgresTeamMap
pgTeam, ok := obj.(*acidv1.PostgresTeam)
if !ok {
c.logger.Errorf("could not cast to PostgresTeam spec")
}
pgTeams, err := c.KubeClient.AcidV1ClientSet.AcidV1().PostgresTeams(pgTeam.Namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
c.logger.Errorf("could not list postgres team objects: %v", err)
}
pgTeamMap.Load(pgTeams)
}
func (c *Controller) updatePostgresTeams(prev, obj interface{}) {
c.loadPostgresTeams(obj)
}
func (c *Controller) podClusterName(pod *v1.Pod) spec.NamespacedName {
if name, ok := pod.Labels[c.opConfig.ClusterNameLabel]; ok {
return spec.NamespacedName{

View File

@ -33,6 +33,7 @@ import (
type AcidV1Interface interface {
RESTClient() rest.Interface
OperatorConfigurationsGetter
PostgresTeamsGetter
PostgresqlsGetter
}
@ -45,6 +46,10 @@ func (c *AcidV1Client) OperatorConfigurations(namespace string) OperatorConfigur
return newOperatorConfigurations(c, namespace)
}
func (c *AcidV1Client) PostgresTeams(namespace string) PostgresTeamInterface {
return newPostgresTeams(c, namespace)
}
func (c *AcidV1Client) Postgresqls(namespace string) PostgresqlInterface {
return newPostgresqls(c, namespace)
}

View File

@ -38,6 +38,10 @@ func (c *FakeAcidV1) OperatorConfigurations(namespace string) v1.OperatorConfigu
return &FakeOperatorConfigurations{c, namespace}
}
func (c *FakeAcidV1) PostgresTeams(namespace string) v1.PostgresTeamInterface {
return &FakePostgresTeams{c, namespace}
}
func (c *FakeAcidV1) Postgresqls(namespace string) v1.PostgresqlInterface {
return &FakePostgresqls{c, namespace}
}

View File

@ -0,0 +1,136 @@
/*
Copyright 2020 Compose, Zalando SE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
acidzalandov1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakePostgresTeams implements PostgresTeamInterface
type FakePostgresTeams struct {
Fake *FakeAcidV1
ns string
}
var postgresteamsResource = schema.GroupVersionResource{Group: "acid.zalan.do", Version: "v1", Resource: "postgresteams"}
var postgresteamsKind = schema.GroupVersionKind{Group: "acid.zalan.do", Version: "v1", Kind: "PostgresTeam"}
// Get takes name of the postgresTeam, and returns the corresponding postgresTeam object, and an error if there is any.
func (c *FakePostgresTeams) Get(ctx context.Context, name string, options v1.GetOptions) (result *acidzalandov1.PostgresTeam, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(postgresteamsResource, c.ns, name), &acidzalandov1.PostgresTeam{})
if obj == nil {
return nil, err
}
return obj.(*acidzalandov1.PostgresTeam), err
}
// List takes label and field selectors, and returns the list of PostgresTeams that match those selectors.
func (c *FakePostgresTeams) List(ctx context.Context, opts v1.ListOptions) (result *acidzalandov1.PostgresTeamList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(postgresteamsResource, postgresteamsKind, c.ns, opts), &acidzalandov1.PostgresTeamList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &acidzalandov1.PostgresTeamList{ListMeta: obj.(*acidzalandov1.PostgresTeamList).ListMeta}
for _, item := range obj.(*acidzalandov1.PostgresTeamList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested postgresTeams.
func (c *FakePostgresTeams) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(postgresteamsResource, c.ns, opts))
}
// Create takes the representation of a postgresTeam and creates it. Returns the server's representation of the postgresTeam, and an error, if there is any.
func (c *FakePostgresTeams) Create(ctx context.Context, postgresTeam *acidzalandov1.PostgresTeam, opts v1.CreateOptions) (result *acidzalandov1.PostgresTeam, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(postgresteamsResource, c.ns, postgresTeam), &acidzalandov1.PostgresTeam{})
if obj == nil {
return nil, err
}
return obj.(*acidzalandov1.PostgresTeam), err
}
// Update takes the representation of a postgresTeam and updates it. Returns the server's representation of the postgresTeam, and an error, if there is any.
func (c *FakePostgresTeams) Update(ctx context.Context, postgresTeam *acidzalandov1.PostgresTeam, opts v1.UpdateOptions) (result *acidzalandov1.PostgresTeam, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(postgresteamsResource, c.ns, postgresTeam), &acidzalandov1.PostgresTeam{})
if obj == nil {
return nil, err
}
return obj.(*acidzalandov1.PostgresTeam), err
}
// Delete takes name of the postgresTeam and deletes it. Returns an error if one occurs.
func (c *FakePostgresTeams) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(postgresteamsResource, c.ns, name), &acidzalandov1.PostgresTeam{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakePostgresTeams) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(postgresteamsResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &acidzalandov1.PostgresTeamList{})
return err
}
// Patch applies the patch and returns the patched postgresTeam.
func (c *FakePostgresTeams) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *acidzalandov1.PostgresTeam, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(postgresteamsResource, c.ns, name, pt, data, subresources...), &acidzalandov1.PostgresTeam{})
if obj == nil {
return nil, err
}
return obj.(*acidzalandov1.PostgresTeam), err
}

View File

@ -26,4 +26,6 @@ package v1
type OperatorConfigurationExpansion interface{}
type PostgresTeamExpansion interface{}
type PostgresqlExpansion interface{}

View File

@ -0,0 +1,184 @@
/*
Copyright 2020 Compose, Zalando SE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1
import (
"context"
"time"
v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
scheme "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/scheme"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// PostgresTeamsGetter has a method to return a PostgresTeamInterface.
// A group's client should implement this interface.
type PostgresTeamsGetter interface {
PostgresTeams(namespace string) PostgresTeamInterface
}
// PostgresTeamInterface has methods to work with PostgresTeam resources.
type PostgresTeamInterface interface {
Create(ctx context.Context, postgresTeam *v1.PostgresTeam, opts metav1.CreateOptions) (*v1.PostgresTeam, error)
Update(ctx context.Context, postgresTeam *v1.PostgresTeam, opts metav1.UpdateOptions) (*v1.PostgresTeam, error)
Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.PostgresTeam, error)
List(ctx context.Context, opts metav1.ListOptions) (*v1.PostgresTeamList, error)
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.PostgresTeam, err error)
PostgresTeamExpansion
}
// postgresTeams implements PostgresTeamInterface
type postgresTeams struct {
client rest.Interface
ns string
}
// newPostgresTeams returns a PostgresTeams
func newPostgresTeams(c *AcidV1Client, namespace string) *postgresTeams {
return &postgresTeams{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the postgresTeam, and returns the corresponding postgresTeam object, and an error if there is any.
func (c *postgresTeams) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.PostgresTeam, err error) {
result = &v1.PostgresTeam{}
err = c.client.Get().
Namespace(c.ns).
Resource("postgresteams").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of PostgresTeams that match those selectors.
func (c *postgresTeams) List(ctx context.Context, opts metav1.ListOptions) (result *v1.PostgresTeamList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.PostgresTeamList{}
err = c.client.Get().
Namespace(c.ns).
Resource("postgresteams").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested postgresTeams.
func (c *postgresTeams) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("postgresteams").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a postgresTeam and creates it. Returns the server's representation of the postgresTeam, and an error, if there is any.
func (c *postgresTeams) Create(ctx context.Context, postgresTeam *v1.PostgresTeam, opts metav1.CreateOptions) (result *v1.PostgresTeam, err error) {
result = &v1.PostgresTeam{}
err = c.client.Post().
Namespace(c.ns).
Resource("postgresteams").
VersionedParams(&opts, scheme.ParameterCodec).
Body(postgresTeam).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a postgresTeam and updates it. Returns the server's representation of the postgresTeam, and an error, if there is any.
func (c *postgresTeams) Update(ctx context.Context, postgresTeam *v1.PostgresTeam, opts metav1.UpdateOptions) (result *v1.PostgresTeam, err error) {
result = &v1.PostgresTeam{}
err = c.client.Put().
Namespace(c.ns).
Resource("postgresteams").
Name(postgresTeam.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(postgresTeam).
Do(ctx).
Into(result)
return
}
// Delete takes name of the postgresTeam and deletes it. Returns an error if one occurs.
func (c *postgresTeams) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("postgresteams").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *postgresTeams) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("postgresteams").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched postgresTeam.
func (c *postgresTeams) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.PostgresTeam, err error) {
result = &v1.PostgresTeam{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("postgresteams").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}

View File

@ -30,6 +30,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// PostgresTeams returns a PostgresTeamInformer.
PostgresTeams() PostgresTeamInformer
// Postgresqls returns a PostgresqlInformer.
Postgresqls() PostgresqlInformer
}
@ -45,6 +47,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// PostgresTeams returns a PostgresTeamInformer.
func (v *version) PostgresTeams() PostgresTeamInformer {
return &postgresTeamInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// Postgresqls returns a PostgresqlInformer.
func (v *version) Postgresqls() PostgresqlInformer {
return &postgresqlInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@ -0,0 +1,96 @@
/*
Copyright 2020 Compose, Zalando SE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1
import (
"context"
time "time"
acidzalandov1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
versioned "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned"
internalinterfaces "github.com/zalando/postgres-operator/pkg/generated/informers/externalversions/internalinterfaces"
v1 "github.com/zalando/postgres-operator/pkg/generated/listers/acid.zalan.do/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// PostgresTeamInformer provides access to a shared informer and lister for
// PostgresTeams.
type PostgresTeamInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.PostgresTeamLister
}
type postgresTeamInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewPostgresTeamInformer constructs a new informer for PostgresTeam type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewPostgresTeamInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredPostgresTeamInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredPostgresTeamInformer constructs a new informer for PostgresTeam type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredPostgresTeamInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AcidV1().PostgresTeams(namespace).List(context.TODO(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AcidV1().PostgresTeams(namespace).Watch(context.TODO(), options)
},
},
&acidzalandov1.PostgresTeam{},
resyncPeriod,
indexers,
)
}
func (f *postgresTeamInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredPostgresTeamInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *postgresTeamInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&acidzalandov1.PostgresTeam{}, f.defaultInformer)
}
func (f *postgresTeamInformer) Lister() v1.PostgresTeamLister {
return v1.NewPostgresTeamLister(f.Informer().GetIndexer())
}

View File

@ -59,6 +59,8 @@ func (f *genericInformer) Lister() cache.GenericLister {
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
switch resource {
// Group=acid.zalan.do, Version=v1
case v1.SchemeGroupVersion.WithResource("postgresteams"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Acid().V1().PostgresTeams().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("postgresqls"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Acid().V1().Postgresqls().Informer()}, nil

View File

@ -24,6 +24,14 @@ SOFTWARE.
package v1
// PostgresTeamListerExpansion allows custom methods to be added to
// PostgresTeamLister.
type PostgresTeamListerExpansion interface{}
// PostgresTeamNamespaceListerExpansion allows custom methods to be added to
// PostgresTeamNamespaceLister.
type PostgresTeamNamespaceListerExpansion interface{}
// PostgresqlListerExpansion allows custom methods to be added to
// PostgresqlLister.
type PostgresqlListerExpansion interface{}

View File

@ -0,0 +1,100 @@
/*
Copyright 2020 Compose, Zalando SE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1
import (
v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// PostgresTeamLister helps list PostgresTeams.
type PostgresTeamLister interface {
// List lists all PostgresTeams in the indexer.
List(selector labels.Selector) (ret []*v1.PostgresTeam, err error)
// PostgresTeams returns an object that can list and get PostgresTeams.
PostgresTeams(namespace string) PostgresTeamNamespaceLister
PostgresTeamListerExpansion
}
// postgresTeamLister implements the PostgresTeamLister interface.
type postgresTeamLister struct {
indexer cache.Indexer
}
// NewPostgresTeamLister returns a new PostgresTeamLister.
func NewPostgresTeamLister(indexer cache.Indexer) PostgresTeamLister {
return &postgresTeamLister{indexer: indexer}
}
// List lists all PostgresTeams in the indexer.
func (s *postgresTeamLister) List(selector labels.Selector) (ret []*v1.PostgresTeam, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1.PostgresTeam))
})
return ret, err
}
// PostgresTeams returns an object that can list and get PostgresTeams.
func (s *postgresTeamLister) PostgresTeams(namespace string) PostgresTeamNamespaceLister {
return postgresTeamNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// PostgresTeamNamespaceLister helps list and get PostgresTeams.
type PostgresTeamNamespaceLister interface {
// List lists all PostgresTeams in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1.PostgresTeam, err error)
// Get retrieves the PostgresTeam from the indexer for a given namespace and name.
Get(name string) (*v1.PostgresTeam, error)
PostgresTeamNamespaceListerExpansion
}
// postgresTeamNamespaceLister implements the PostgresTeamNamespaceLister
// interface.
type postgresTeamNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all PostgresTeams in the indexer for a given namespace.
func (s postgresTeamNamespaceLister) List(selector labels.Selector) (ret []*v1.PostgresTeam, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1.PostgresTeam))
})
return ret, err
}
// Get retrieves the PostgresTeam from the indexer for a given namespace and name.
func (s postgresTeamNamespaceLister) Get(name string) (*v1.PostgresTeam, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1.Resource("postgresteam"), name)
}
return obj.(*v1.PostgresTeam), nil
}

View File

@ -0,0 +1,103 @@
package postgresteams
import (
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
)
// PostgresTeamMap is the operator's internal representation of all PostgresTeam CRDs
type PostgresTeamMap map[string]postgresTeamMembership
type postgresTeamMembership struct {
AdditionalTeams map[additionalTeam]struct{}
AdditionalMembers map[string]struct{}
}
type additionalTeam struct {
Name string
IsAdmin bool
}
type teamHashSet map[string]map[string]struct{}
func (ths *teamHashSet) has(team string) bool {
_, ok := (*ths)[team]
return ok
}
func (ths *teamHashSet) add(newTeam string, newSet []string) {
set := make(map[string]struct{})
if ths.has(newTeam) {
set = (*ths)[newTeam]
}
for _, t := range newSet {
set[t] = struct{}{}
}
(*ths)[newTeam] = set
}
func (ths *teamHashSet) mergeCrdMap(crdTeamMap map[string][]string) {
for t, at := range crdTeamMap {
ths.add(t, at)
}
}
func (pgt *PostgresTeamMap) mapTeams(set teamHashSet, isAdmin bool) {
for team, items := range set {
newAdditionalTeams := make(map[additionalTeam]struct{})
newAdditionalMembers := make(map[string]struct{})
if currentTeamMembership, exists := (*pgt)[team]; exists {
newAdditionalTeams = currentTeamMembership.AdditionalTeams
newAdditionalMembers = currentTeamMembership.AdditionalMembers
}
for newTeam := range items {
newAdditionalTeams[additionalTeam{
Name: newTeam,
IsAdmin: isAdmin,
}] = struct{}{}
}
if len(newAdditionalTeams) > 0 {
(*pgt)[team] = postgresTeamMembership{newAdditionalTeams, newAdditionalMembers}
}
}
}
func (pgt *PostgresTeamMap) mapMembers(set teamHashSet) {
for team, items := range set {
newAdditionalTeams := make(map[additionalTeam]struct{})
newAdditionalMembers := make(map[string]struct{})
if currentTeamMembership, exists := (*pgt)[team]; exists {
newAdditionalTeams = currentTeamMembership.AdditionalTeams
newAdditionalMembers = currentTeamMembership.AdditionalMembers
}
for additionalMember := range items {
newAdditionalMembers[additionalMember] = struct{}{}
}
(*pgt)[team] = postgresTeamMembership{newAdditionalTeams, newAdditionalMembers}
}
}
// Load function to import data from PostgresTeam CRD
func (pgt *PostgresTeamMap) Load(pgTeams *acidv1.PostgresTeamList) {
adminTeamSet := teamHashSet{}
teamSet := teamHashSet{}
teamMemberSet := teamHashSet{}
for _, pgTeam := range pgTeams.Items {
adminTeamSet.mergeCrdMap(pgTeam.Spec.AdditionalAdminTeams)
teamSet.mergeCrdMap(pgTeam.Spec.AdditionalTeams)
teamMemberSet.mergeCrdMap(pgTeam.Spec.AdditionalMembers)
}
pgt.mapTeams(adminTeamSet, true)
pgt.mapTeams(teamSet, false)
pgt.mapMembers(teamMemberSet)
}
// MergeTeams function to add additional teams to internal team map
func (pgt *PostgresTeamMap) MergeTeams(teamID string, additionalTeams []string, isAdmin bool) {
teamSet := teamHashSet{}
if len(additionalTeams) > 0 {
teamSet.add(teamID, additionalTeams)
pgt.mapTeams(teamSet, isAdmin)
}
}

View File

@ -0,0 +1,111 @@
package postgresteams
import (
"reflect"
"testing"
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
True = true
False = false
)
// PostgresTeamMap is the operator's internal representation of all PostgresTeam CRDs
func TestLoadinngPostgresTeamCRD(t *testing.T) {
tests := []struct {
name string
crd acidv1.PostgresTeamList
pgt PostgresTeamMap
error string
}{
{
"Check that CRD is imported correctly into the internal format",
acidv1.PostgresTeamList{
TypeMeta: metav1.TypeMeta{
Kind: "List",
APIVersion: "v1",
},
Items: []acidv1.PostgresTeam{
{
TypeMeta: metav1.TypeMeta{
Kind: "PostgresTeam",
APIVersion: "acid.zalan.do/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "teamAB",
},
Spec: acidv1.PostgresTeamSpec{
AdditionalAdminTeams: map[string][]string{"teamA": []string{"teamB", "team24/7"}, "teamB": []string{"teamA", "team24/7"}},
AdditionalTeams: map[string][]string{"teamA": []string{"teamC"}, "teamB": []string{}},
AdditionalMembers: map[string][]string{"team24/7": []string{"optimusprime"}, "teamB": []string{"drno"}},
},
}, {
TypeMeta: metav1.TypeMeta{
Kind: "PostgresTeam",
APIVersion: "acid.zalan.do/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "teamC",
},
Spec: acidv1.PostgresTeamSpec{
AdditionalAdminTeams: map[string][]string{"teamC": []string{"team24/7"}},
AdditionalTeams: map[string][]string{"teamA": []string{"teamC"}, "teamC": []string{"teamA", "teamB"}},
AdditionalMembers: map[string][]string{"acid": []string{"batman"}},
},
},
},
},
PostgresTeamMap{
"teamA": {
AdditionalTeams: map[additionalTeam]struct{}{
additionalTeam{Name: "teamB", IsAdmin: True}: {},
additionalTeam{Name: "team24/7", IsAdmin: True}: {},
additionalTeam{Name: "teamC", IsAdmin: False}: {},
},
AdditionalMembers: map[string]struct{}{},
},
"teamB": {
AdditionalTeams: map[additionalTeam]struct{}{
additionalTeam{Name: "teamA", IsAdmin: True}: {},
additionalTeam{Name: "team24/7", IsAdmin: True}: {},
},
AdditionalMembers: map[string]struct{}{
"drno": {},
},
},
"teamC": {
AdditionalTeams: map[additionalTeam]struct{}{
additionalTeam{Name: "team24/7", IsAdmin: True}: {},
additionalTeam{Name: "teamA", IsAdmin: False}: {},
additionalTeam{Name: "teamB", IsAdmin: False}: {},
},
AdditionalMembers: map[string]struct{}{},
},
"team24/7": {
AdditionalTeams: map[additionalTeam]struct{}{},
AdditionalMembers: map[string]struct{}{
"optimusprime": {},
},
},
"acid": {
AdditionalTeams: map[additionalTeam]struct{}{},
AdditionalMembers: map[string]struct{}{
"batman": {},
},
},
},
"Mismatch between PostgresTeam CRD and internal map",
},
}
for _, tt := range tests {
postgresTeamMap := PostgresTeamMap{}
postgresTeamMap.Load(&tt.crd)
if !reflect.DeepEqual(postgresTeamMap, tt.pgt) {
t.Errorf("%s: %v", tt.name, tt.error)
}
}
}