PostgresTeam CRD for advanced team management (#1165)
* PostgresTeamCRD for advanced team management * rework internal structure to be closer to CRD * superusers instead of admin * add more util functions and unit tests * fix initHumanUsers * check for superusers when creating normal teams * polishing and fixes * adding the essential missing pieces * add documentation and update rbac * reflect some feedback * reflect more feedback * fixing debug logs and raise QueueResyncPeriodTPR * add two more flags to disable CRD and its superuser support * fix chart * update go modules * move to client 1.19.3 and update codegen
This commit is contained in:
		
							parent
							
								
									3a86dfc8bb
								
							
						
					
					
						commit
						d658b9672e
					
				|  | @ -319,6 +319,10 @@ spec: | ||||||
|               properties: |               properties: | ||||||
|                 enable_admin_role_for_users: |                 enable_admin_role_for_users: | ||||||
|                   type: boolean |                   type: boolean | ||||||
|  |                 enable_postgres_team_crd: | ||||||
|  |                   type: boolean | ||||||
|  |                 enable_postgres_team_crd_superusers: | ||||||
|  |                   type: boolean | ||||||
|                 enable_team_superuser: |                 enable_team_superuser: | ||||||
|                   type: boolean |                   type: boolean | ||||||
|                 enable_teams_api: |                 enable_teams_api: | ||||||
|  |  | ||||||
|  | @ -0,0 +1,67 @@ | ||||||
|  | apiVersion: apiextensions.k8s.io/v1beta1 | ||||||
|  | kind: CustomResourceDefinition | ||||||
|  | metadata: | ||||||
|  |   name: postgresteams.acid.zalan.do | ||||||
|  |   labels: | ||||||
|  |     app.kubernetes.io/name: postgres-operator | ||||||
|  |   annotations: | ||||||
|  |     "helm.sh/hook": crd-install | ||||||
|  | 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: | ||||||
|  |             additionalSuperuserTeams: | ||||||
|  |               type: object | ||||||
|  |               description: "Map for teamId and associated additional superuser teams" | ||||||
|  |               additionalProperties: | ||||||
|  |                 type: array | ||||||
|  |                 nullable: true | ||||||
|  |                 description: "List of teams to become Postgres superusers" | ||||||
|  |                 items: | ||||||
|  |                   type: string | ||||||
|  |             additionalTeams: | ||||||
|  |               type: object | ||||||
|  |               description: "Map for teamId and associated additional teams" | ||||||
|  |               additionalProperties: | ||||||
|  |                 type: array | ||||||
|  |                 nullable: true | ||||||
|  |                 description: "List of teams whose members will also be added to the Postgres cluster" | ||||||
|  |                 items: | ||||||
|  |                   type: string | ||||||
|  |             additionalMembers: | ||||||
|  |               type: object | ||||||
|  |               description: "Map for teamId and associated additional users" | ||||||
|  |               additionalProperties: | ||||||
|  |                 type: array | ||||||
|  |                 nullable: true | ||||||
|  |                 description: "List of users who will also be added to the Postgres cluster" | ||||||
|  |                 items: | ||||||
|  |                   type: string | ||||||
|  | @ -25,6 +25,15 @@ rules: | ||||||
|   - patch |   - patch | ||||||
|   - update |   - update | ||||||
|   - watch |   - watch | ||||||
|  | # operator only reads PostgresTeams | ||||||
|  | - apiGroups: | ||||||
|  |   - acid.zalan.do | ||||||
|  |   resources: | ||||||
|  |   - postgresteams | ||||||
|  |   verbs: | ||||||
|  |   - get | ||||||
|  |   - list | ||||||
|  |   - watch | ||||||
| # to create or get/update CRDs when starting up | # to create or get/update CRDs when starting up | ||||||
| - apiGroups: | - apiGroups: | ||||||
|   - apiextensions.k8s.io |   - apiextensions.k8s.io | ||||||
|  |  | ||||||
|  | @ -256,6 +256,11 @@ configTeamsApi: | ||||||
|   # team_admin_role will have the rights to grant roles coming from PG manifests |   # 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: true | ||||||
|  |   # toogle to create additional superuser teams from PostgresTeam CRs | ||||||
|  |   # enable_postgres_team_crd_superusers: "false" | ||||||
|  | 
 | ||||||
|   # toggle to grant superuser to team members created from the Teams API |   # 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 |   # toggles usage of the Teams API by the operator | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| image: | image: | ||||||
|   registry: registry.opensource.zalan.do |   registry: registry.opensource.zalan.do | ||||||
|   repository: acid/postgres-operator |   repository: acid/postgres-operator | ||||||
|   tag: v1.5.0 |   tag: v1.5.0-61-ged2b3239-dirty  | ||||||
|   pullPolicy: "IfNotPresent" |   pullPolicy: "IfNotPresent" | ||||||
| 
 | 
 | ||||||
| # Optionally specify an array of imagePullSecrets. | # Optionally specify an array of imagePullSecrets. | ||||||
|  | @ -248,6 +248,11 @@ configTeamsApi: | ||||||
|   # team_admin_role will have the rights to grant roles coming from PG manifests |   # 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: "true" | ||||||
|  |   # toogle to create additional superuser teams from PostgresTeam CRs | ||||||
|  |   # enable_postgres_team_crd_superusers: "false" | ||||||
|  | 
 | ||||||
|   # toggle to grant superuser to team members created from the Teams API |   # toggle to grant superuser to team members created from the Teams API | ||||||
|   # enable_team_superuser: "false" |   # enable_team_superuser: "false" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -561,9 +561,12 @@ database. | ||||||
| * **Human users** originate from the [Teams API](user.md#teams-api-roles) that | * **Human users** originate from the [Teams API](user.md#teams-api-roles) that | ||||||
| returns a list of the team members given a team id. The operator differentiates | returns a list of the team members given a team id. The operator differentiates | ||||||
| between (a) product teams that own a particular Postgres cluster and are granted | between (a) product teams that own a particular Postgres cluster and are granted | ||||||
| admin rights to maintain it, and (b) Postgres superuser teams that get the | admin rights to maintain it, (b) Postgres superuser teams that get superuser | ||||||
| superuser access to all Postgres databases running in a K8s cluster for the | access to all Postgres databases running in a K8s cluster for the purposes of | ||||||
| purposes of maintaining and troubleshooting. | maintaining and troubleshooting, and (c) additional teams, superuser teams or | ||||||
|  | members associated with the owning team. The latter is managed via the | ||||||
|  | [PostgresTeam CRD](user.md#additional-teams-and-members-per-cluster). | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| ## Understanding rolling update of Spilo pods | ## Understanding rolling update of Spilo pods | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -598,8 +598,8 @@ key. | ||||||
|   The default is `"log_statement:all"` |   The default is `"log_statement:all"` | ||||||
| 
 | 
 | ||||||
| * **enable_team_superuser** | * **enable_team_superuser** | ||||||
|   whether to grant superuser to team members created from the Teams API. |   whether to grant superuser to members of the cluster's owning team created | ||||||
|   The default is `false`. |   from the Teams API. The default is `false`. | ||||||
| 
 | 
 | ||||||
| * **team_admin_role** | * **team_admin_role** | ||||||
|   role name to grant to team members created from the Teams API. The default is |   role name to grant to team members created from the Teams API. The default is | ||||||
|  | @ -632,6 +632,16 @@ key. | ||||||
|   cluster to administer Postgres and maintain infrastructure built around it. |   cluster to administer Postgres and maintain infrastructure built around it. | ||||||
|   The default is empty. |   The default is empty. | ||||||
| 
 | 
 | ||||||
|  | * **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. | ||||||
|  |   The default is `true`. | ||||||
|  | 
 | ||||||
|  | * **enable_postgres_team_crd_superusers** | ||||||
|  |   in a `PostgresTeam` CRD additional superuser teams can assigned to teams that | ||||||
|  |   own clusters. With this flag set to `false`, it will be ignored. | ||||||
|  |   The default is `false`. | ||||||
|  | 
 | ||||||
| ## Logging and REST API | ## Logging and REST API | ||||||
| 
 | 
 | ||||||
| Parameters affecting logging and REST API listener. In the CRD-based | Parameters affecting logging and REST API listener. In the CRD-based | ||||||
|  |  | ||||||
							
								
								
									
										61
									
								
								docs/user.md
								
								
								
								
							
							
						
						
									
										61
									
								
								docs/user.md
								
								
								
								
							|  | @ -269,6 +269,67 @@ to choose superusers, group roles, [PAM configuration](https://github.com/CyberD | ||||||
| etc. An OAuth2 token can be passed to the Teams API via a secret. The name for | etc. An OAuth2 token can be passed to the Teams API via a secret. The name for | ||||||
| this secret is configurable with the `oauth_token_secret_name` parameter. | this secret is configurable with the `oauth_token_secret_name` parameter. | ||||||
| 
 | 
 | ||||||
|  | ### Additional teams and members per cluster | ||||||
|  | 
 | ||||||
|  | Postgres clusters are associated with one team by providing the `teamID` in | ||||||
|  | the manifest. Additional superuser teams can be configured as mentioned in | ||||||
|  | the previous paragraph. However, this is a global setting. To assign | ||||||
|  | additional teams, superuser teams and single users to clusters of a given | ||||||
|  | team, use the [PostgresTeam CRD](../manifests/postgresteam.yaml). It provides | ||||||
|  | a simple mapping structure. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | apiVersion: "acid.zalan.do/v1" | ||||||
|  | kind: PostgresTeam | ||||||
|  | metadata: | ||||||
|  |   name: custom-team-membership | ||||||
|  | spec: | ||||||
|  |   additionalSuperuserTeams: | ||||||
|  |     acid: | ||||||
|  |     - "postgres_superusers" | ||||||
|  |   additionalTeams: | ||||||
|  |     acid: [] | ||||||
|  |   additionalMembers: | ||||||
|  |     acid: | ||||||
|  |     - "elephant" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | One `PostgresTeam` resource could contain mappings of multiple teams but you | ||||||
|  | can choose to create separate CRDs, alternatively. On each CRD creation or | ||||||
|  | update the operator will gather all mappings to create additional human users | ||||||
|  | in databases the next time they are synced. Additional teams are resolved | ||||||
|  | transitively, meaning you will also add users for their `additionalTeams` | ||||||
|  | or (not and) `additionalSuperuserTeams`. | ||||||
|  | 
 | ||||||
|  | For each additional team the Teams API would be queried. Additional members | ||||||
|  | will be added either way. There can be "virtual teams" that do not exists in | ||||||
|  | your Teams API but users of associated teams as well as members will get | ||||||
|  | created. With `PostgresTeams` it's also easy to cover team name changes. Just | ||||||
|  | add the mapping between old and new team name and the rest can stay the same. | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | apiVersion: "acid.zalan.do/v1" | ||||||
|  | kind: PostgresTeam | ||||||
|  | metadata: | ||||||
|  |   name: virtualteam-membership | ||||||
|  | spec: | ||||||
|  |   additionalSuperuserTeams: | ||||||
|  |     acid: | ||||||
|  |     - "virtual_superusers" | ||||||
|  |     virtual_superusers: | ||||||
|  |     - "real_teamA" | ||||||
|  |     - "real_teamB" | ||||||
|  |     real_teamA: | ||||||
|  |     - "real_teamA_renamed" | ||||||
|  |   additionalTeams: | ||||||
|  |     real_teamA: | ||||||
|  |     - "real_teamA_renamed" | ||||||
|  |   additionalMembers: | ||||||
|  |     virtual_superusers: | ||||||
|  |     - "foo" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Prepared databases with roles and default privileges | ## Prepared databases with roles and default privileges | ||||||
| 
 | 
 | ||||||
| The `users` section in the manifests only allows for creating database roles | The `users` section in the manifests only allows for creating database roles | ||||||
|  |  | ||||||
|  | @ -41,6 +41,8 @@ data: | ||||||
|   enable_master_load_balancer: "false" |   enable_master_load_balancer: "false" | ||||||
|   # enable_pod_antiaffinity: "false" |   # enable_pod_antiaffinity: "false" | ||||||
|   # enable_pod_disruption_budget: "true" |   # enable_pod_disruption_budget: "true" | ||||||
|  |   # enable_postgres_team_crd: "true" | ||||||
|  |   # enable_postgres_team_crd_superusers: "false" | ||||||
|   enable_replica_load_balancer: "false" |   enable_replica_load_balancer: "false" | ||||||
|   # enable_shm_volume: "true" |   # enable_shm_volume: "true" | ||||||
|   # enable_sidecars: "true" |   # enable_sidecars: "true" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | apiVersion: "acid.zalan.do/v1" | ||||||
|  | kind: PostgresTeam | ||||||
|  | metadata: | ||||||
|  |   name: custom-team-membership | ||||||
|  | spec: | ||||||
|  |   additionalSuperuserTeams: | ||||||
|  |     acid: | ||||||
|  |     - "postgres_superusers" | ||||||
|  |   additionalTeams: | ||||||
|  |     acid: [] | ||||||
|  |   additionalMembers: | ||||||
|  |     acid: | ||||||
|  |     - "elephant" | ||||||
|  | @ -26,6 +26,15 @@ rules: | ||||||
|   - patch |   - patch | ||||||
|   - update |   - update | ||||||
|   - watch |   - watch | ||||||
|  | # operator only reads PostgresTeams | ||||||
|  | - apiGroups: | ||||||
|  |   - acid.zalan.do | ||||||
|  |   resources: | ||||||
|  |   - postgresteams | ||||||
|  |   verbs: | ||||||
|  |   - get | ||||||
|  |   - list | ||||||
|  |   - watch | ||||||
| # to create or get/update CRDs when starting up | # to create or get/update CRDs when starting up | ||||||
| - apiGroups: | - apiGroups: | ||||||
|   - apiextensions.k8s.io |   - apiextensions.k8s.io | ||||||
|  |  | ||||||
|  | @ -325,6 +325,10 @@ spec: | ||||||
|               properties: |               properties: | ||||||
|                 enable_admin_role_for_users: |                 enable_admin_role_for_users: | ||||||
|                   type: boolean |                   type: boolean | ||||||
|  |                 enable_postgres_team_crd: | ||||||
|  |                   type: boolean | ||||||
|  |                 enable_postgres_team_crd_superusers: | ||||||
|  |                   type: boolean | ||||||
|                 enable_team_superuser: |                 enable_team_superuser: | ||||||
|                   type: boolean |                   type: boolean | ||||||
|                 enable_teams_api: |                 enable_teams_api: | ||||||
|  |  | ||||||
|  | @ -122,6 +122,8 @@ configuration: | ||||||
|     enable_database_access: true |     enable_database_access: true | ||||||
|   teams_api: |   teams_api: | ||||||
|     # enable_admin_role_for_users: true |     # enable_admin_role_for_users: true | ||||||
|  |     # enable_postgres_team_crd: true | ||||||
|  |     # enable_postgres_team_crd_superusers: false | ||||||
|     enable_team_superuser: false |     enable_team_superuser: false | ||||||
|     enable_teams_api: false |     enable_teams_api: false | ||||||
|     # pam_configuration: "" |     # pam_configuration: "" | ||||||
|  |  | ||||||
|  | @ -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: | ||||||
|  |             additionalSuperuserTeams: | ||||||
|  |               type: object | ||||||
|  |               description: "Map for teamId and associated additional superuser teams" | ||||||
|  |               additionalProperties: | ||||||
|  |                 type: array | ||||||
|  |                 nullable: true | ||||||
|  |                 description: "List of teams to become Postgres superusers" | ||||||
|  |                 items: | ||||||
|  |                   type: string | ||||||
|  |             additionalTeams: | ||||||
|  |               type: object | ||||||
|  |               description: "Map for teamId and associated additional teams" | ||||||
|  |               additionalProperties: | ||||||
|  |                 type: array | ||||||
|  |                 nullable: true | ||||||
|  |                 description: "List of teams whose members will also be added to the Postgres cluster" | ||||||
|  |                 items: | ||||||
|  |                   type: string | ||||||
|  |             additionalMembers: | ||||||
|  |               type: object | ||||||
|  |               description: "Map for teamId and associated additional users" | ||||||
|  |               additionalProperties: | ||||||
|  |                 type: array | ||||||
|  |                 nullable: true | ||||||
|  |                 description: "List of users who will also be added to the Postgres cluster" | ||||||
|  |                 items: | ||||||
|  |                   type: string | ||||||
|  | @ -1235,6 +1235,12 @@ var OperatorConfigCRDResourceValidation = apiextv1beta1.CustomResourceValidation | ||||||
| 							"enable_admin_role_for_users": { | 							"enable_admin_role_for_users": { | ||||||
| 								Type: "boolean", | 								Type: "boolean", | ||||||
| 							}, | 							}, | ||||||
|  | 							"enable_postgres_team_crd": { | ||||||
|  | 								Type: "boolean", | ||||||
|  | 							}, | ||||||
|  | 							"enable_postgres_team_crd_superusers": { | ||||||
|  | 								Type: "boolean", | ||||||
|  | 							}, | ||||||
| 							"enable_team_superuser": { | 							"enable_team_superuser": { | ||||||
| 								Type: "boolean", | 								Type: "boolean", | ||||||
| 							}, | 							}, | ||||||
|  |  | ||||||
|  | @ -135,16 +135,18 @@ type OperatorDebugConfiguration struct { | ||||||
| 
 | 
 | ||||||
| // TeamsAPIConfiguration defines the configuration of TeamsAPI
 | // TeamsAPIConfiguration defines the configuration of TeamsAPI
 | ||||||
| type TeamsAPIConfiguration struct { | type TeamsAPIConfiguration struct { | ||||||
| 	EnableTeamsAPI           bool              `json:"enable_teams_api,omitempty"` | 	EnableTeamsAPI                  bool              `json:"enable_teams_api,omitempty"` | ||||||
| 	TeamsAPIUrl              string            `json:"teams_api_url,omitempty"` | 	TeamsAPIUrl                     string            `json:"teams_api_url,omitempty"` | ||||||
| 	TeamAPIRoleConfiguration map[string]string `json:"team_api_role_configuration,omitempty"` | 	TeamAPIRoleConfiguration        map[string]string `json:"team_api_role_configuration,omitempty"` | ||||||
| 	EnableTeamSuperuser      bool              `json:"enable_team_superuser,omitempty"` | 	EnableTeamSuperuser             bool              `json:"enable_team_superuser,omitempty"` | ||||||
| 	EnableAdminRoleForUsers  bool              `json:"enable_admin_role_for_users,omitempty"` | 	EnableAdminRoleForUsers         bool              `json:"enable_admin_role_for_users,omitempty"` | ||||||
| 	TeamAdminRole            string            `json:"team_admin_role,omitempty"` | 	TeamAdminRole                   string            `json:"team_admin_role,omitempty"` | ||||||
| 	PamRoleName              string            `json:"pam_role_name,omitempty"` | 	PamRoleName                     string            `json:"pam_role_name,omitempty"` | ||||||
| 	PamConfiguration         string            `json:"pam_configuration,omitempty"` | 	PamConfiguration                string            `json:"pam_configuration,omitempty"` | ||||||
| 	ProtectedRoles           []string          `json:"protected_role_names,omitempty"` | 	ProtectedRoles                  []string          `json:"protected_role_names,omitempty"` | ||||||
| 	PostgresSuperuserTeams   []string          `json:"postgres_superuser_teams,omitempty"` | 	PostgresSuperuserTeams          []string          `json:"postgres_superuser_teams,omitempty"` | ||||||
|  | 	EnablePostgresTeamCRD           *bool             `json:"enable_postgres_team_crd,omitempty"` | ||||||
|  | 	EnablePostgresTeamCRDSuperusers bool              `json:"enable_postgres_team_crd_superusers,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LoggingRESTAPIConfiguration defines Logging API conf
 | // LoggingRESTAPIConfiguration defines Logging API conf
 | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  | 	AdditionalSuperuserTeams map[string][]string `json:"additionalSuperuserTeams,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"` | ||||||
|  | } | ||||||
|  | @ -1,11 +1,10 @@ | ||||||
| package v1 | package v1 | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	acidzalando "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||||
| 
 |  | ||||||
| 	"github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // APIVersion of the `postgresql` and `operator` CRDs
 | // APIVersion of the `postgresql` and `operator` CRDs
 | ||||||
|  | @ -44,6 +43,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { | ||||||
| 	// TODO: User uppercase CRDResourceKind of our types in the next major API version
 | 	// TODO: User uppercase CRDResourceKind of our types in the next major API version
 | ||||||
| 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("postgresql"), &Postgresql{}) | 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("postgresql"), &Postgresql{}) | ||||||
| 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("postgresqlList"), &PostgresqlList{}) | 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("postgresqlList"), &PostgresqlList{}) | ||||||
|  | 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("PostgresTeam"), &PostgresTeam{}) | ||||||
|  | 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("PostgresTeamList"), &PostgresTeamList{}) | ||||||
| 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("OperatorConfiguration"), | 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("OperatorConfiguration"), | ||||||
| 		&OperatorConfiguration{}) | 		&OperatorConfiguration{}) | ||||||
| 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("OperatorConfigurationList"), | 	scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("OperatorConfigurationList"), | ||||||
|  |  | ||||||
|  | @ -711,6 +711,127 @@ func (in *PostgresStatus) DeepCopy() *PostgresStatus { | ||||||
| 	return out | 	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.AdditionalSuperuserTeams != nil { | ||||||
|  | 		in, out := &in.AdditionalSuperuserTeams, &out.AdditionalSuperuserTeams | ||||||
|  | 		*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.
 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
| func (in *PostgresUsersConfiguration) DeepCopyInto(out *PostgresUsersConfiguration) { | func (in *PostgresUsersConfiguration) DeepCopyInto(out *PostgresUsersConfiguration) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  | @ -993,6 +1114,11 @@ func (in *TeamsAPIConfiguration) DeepCopyInto(out *TeamsAPIConfiguration) { | ||||||
| 		*out = make([]string, len(*in)) | 		*out = make([]string, len(*in)) | ||||||
| 		copy(*out, *in) | 		copy(*out, *in) | ||||||
| 	} | 	} | ||||||
|  | 	if in.EnablePostgresTeamCRD != nil { | ||||||
|  | 		in, out := &in.EnablePostgresTeamCRD, &out.EnablePostgresTeamCRD | ||||||
|  | 		*out = new(bool) | ||||||
|  | 		**out = **in | ||||||
|  | 	} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -14,19 +14,10 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/r3labs/diff" | 	"github.com/r3labs/diff" | ||||||
| 	"github.com/sirupsen/logrus" | 	"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" | 	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/generated/clientset/versioned/scheme" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/spec" | 	"github.com/zalando/postgres-operator/pkg/spec" | ||||||
|  | 	pgteams "github.com/zalando/postgres-operator/pkg/teams" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util" | 	"github.com/zalando/postgres-operator/pkg/util" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/config" | 	"github.com/zalando/postgres-operator/pkg/util/config" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/constants" | 	"github.com/zalando/postgres-operator/pkg/util/constants" | ||||||
|  | @ -34,7 +25,16 @@ import ( | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/patroni" | 	"github.com/zalando/postgres-operator/pkg/util/patroni" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/teams" | 	"github.com/zalando/postgres-operator/pkg/util/teams" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/users" | 	"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" | 	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 ( | var ( | ||||||
|  | @ -48,6 +48,7 @@ var ( | ||||||
| type Config struct { | type Config struct { | ||||||
| 	OpConfig                     config.Config | 	OpConfig                     config.Config | ||||||
| 	RestConfig                   *rest.Config | 	RestConfig                   *rest.Config | ||||||
|  | 	PgTeamMap                    pgteams.PostgresTeamMap | ||||||
| 	InfrastructureRoles          map[string]spec.PgUser // inherited from the controller
 | 	InfrastructureRoles          map[string]spec.PgUser // inherited from the controller
 | ||||||
| 	PodServiceAccount            *v1.ServiceAccount | 	PodServiceAccount            *v1.ServiceAccount | ||||||
| 	PodServiceAccountRoleBinding *rbacv1.RoleBinding | 	PodServiceAccountRoleBinding *rbacv1.RoleBinding | ||||||
|  | @ -1107,7 +1108,7 @@ func (c *Cluster) initTeamMembers(teamID string, isPostgresSuperuserTeam bool) e | ||||||
| 		if c.shouldAvoidProtectedOrSystemRole(username, "API role") { | 		if c.shouldAvoidProtectedOrSystemRole(username, "API role") { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if c.OpConfig.EnableTeamSuperuser || isPostgresSuperuserTeam { | 		if (c.OpConfig.EnableTeamSuperuser && teamID == c.Spec.TeamID) || isPostgresSuperuserTeam { | ||||||
| 			flags = append(flags, constants.RoleFlagSuperuser) | 			flags = append(flags, constants.RoleFlagSuperuser) | ||||||
| 		} else { | 		} else { | ||||||
| 			if c.OpConfig.TeamAdminRole != "" { | 			if c.OpConfig.TeamAdminRole != "" { | ||||||
|  | @ -1136,17 +1137,38 @@ func (c *Cluster) initTeamMembers(teamID string, isPostgresSuperuserTeam bool) e | ||||||
| func (c *Cluster) initHumanUsers() error { | func (c *Cluster) initHumanUsers() error { | ||||||
| 
 | 
 | ||||||
| 	var clusterIsOwnedBySuperuserTeam bool | 	var clusterIsOwnedBySuperuserTeam bool | ||||||
|  | 	superuserTeams := []string{} | ||||||
|  | 
 | ||||||
|  | 	if c.OpConfig.EnablePostgresTeamCRDSuperusers { | ||||||
|  | 		superuserTeams = c.PgTeamMap.GetAdditionalSuperuserTeams(c.Spec.TeamID, true) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, postgresSuperuserTeam := range c.OpConfig.PostgresSuperuserTeams { | 	for _, postgresSuperuserTeam := range c.OpConfig.PostgresSuperuserTeams { | ||||||
| 		err := c.initTeamMembers(postgresSuperuserTeam, true) | 		if !(util.SliceContains(superuserTeams, postgresSuperuserTeam)) { | ||||||
| 		if err != nil { | 			superuserTeams = append(superuserTeams, postgresSuperuserTeam) | ||||||
| 			return fmt.Errorf("Cannot create a team %q of Postgres superusers: %v", postgresSuperuserTeam, err) |  | ||||||
| 		} | 		} | ||||||
| 		if postgresSuperuserTeam == c.Spec.TeamID { | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, superuserTeam := range superuserTeams { | ||||||
|  | 		err := c.initTeamMembers(superuserTeam, true) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Cannot initialize members for team %q of Postgres superusers: %v", superuserTeam, err) | ||||||
|  | 		} | ||||||
|  | 		if superuserTeam == c.Spec.TeamID { | ||||||
| 			clusterIsOwnedBySuperuserTeam = true | 			clusterIsOwnedBySuperuserTeam = true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	additionalTeams := c.PgTeamMap.GetAdditionalTeams(c.Spec.TeamID, true) | ||||||
|  | 	for _, additionalTeam := range additionalTeams { | ||||||
|  | 		if !(util.SliceContains(superuserTeams, additionalTeam)) { | ||||||
|  | 			err := c.initTeamMembers(additionalTeam, false) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("Cannot initialize members for additional team %q for cluster owned by %q: %v", additionalTeam, c.Spec.TeamID, err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if clusterIsOwnedBySuperuserTeam { | 	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) | 		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 | 		return nil | ||||||
|  | @ -1154,7 +1176,7 @@ func (c *Cluster) initHumanUsers() error { | ||||||
| 
 | 
 | ||||||
| 	err := c.initTeamMembers(c.Spec.TeamID, false) | 	err := c.initTeamMembers(c.Spec.TeamID, false) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("Cannot create a team %q of admins owning the PG cluster: %v", c.Spec.TeamID, err) | 		return fmt.Errorf("Cannot initialize members for team %q who owns the Postgres cluster: %v", c.Spec.TeamID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  |  | ||||||
|  | @ -238,24 +238,37 @@ func (c *Cluster) getTeamMembers(teamID string) ([]string, error) { | ||||||
| 		return nil, fmt.Errorf("no teamId specified") | 		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 { | 	if !c.OpConfig.EnableTeamsAPI { | ||||||
| 		c.logger.Debugf("team API is disabled, returning empty list of members for team %q", teamID) | 		c.logger.Debugf("team API is disabled, only returning %d members for team %q", len(members), teamID) | ||||||
| 		return []string{}, nil | 		return members, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	token, err := c.oauthTokenGetter.getOAuthToken() | 	token, err := c.oauthTokenGetter.getOAuthToken() | ||||||
| 	if err != nil { | 	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) | 		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 []string{}, nil | 		return members, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	teamInfo, err := c.teamsAPIClient.TeamInfo(teamID, token) | 	teamInfo, err := c.teamsAPIClient.TeamInfo(teamID, token) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.logger.Warnf("could not get team info for team %q, returning empty list of team members: %v", teamID, err) | 		c.logger.Warnf("could not get team info for team %q, only returning %d members: %v", teamID, len(members), err) | ||||||
| 		return []string{}, nil | 		return members, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return teamInfo.Members, nil | 	for _, member := range teamInfo.Members { | ||||||
|  | 		if !(util.SliceContains(members, member)) { | ||||||
|  | 			members = append(members, member) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return members, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) waitForPodLabel(podEvents chan PodEvent, stopChan chan struct{}, role *PostgresRole) (*v1.Pod, error) { | func (c *Cluster) waitForPodLabel(podEvents chan PodEvent, stopChan chan struct{}, role *PostgresRole) (*v1.Pod, error) { | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ import ( | ||||||
| 	"github.com/zalando/postgres-operator/pkg/cluster" | 	"github.com/zalando/postgres-operator/pkg/cluster" | ||||||
| 	acidv1informer "github.com/zalando/postgres-operator/pkg/generated/informers/externalversions/acid.zalan.do/v1" | 	acidv1informer "github.com/zalando/postgres-operator/pkg/generated/informers/externalversions/acid.zalan.do/v1" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/spec" | 	"github.com/zalando/postgres-operator/pkg/spec" | ||||||
|  | 	"github.com/zalando/postgres-operator/pkg/teams" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util" | 	"github.com/zalando/postgres-operator/pkg/util" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/config" | 	"github.com/zalando/postgres-operator/pkg/util/config" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/constants" | 	"github.com/zalando/postgres-operator/pkg/util/constants" | ||||||
|  | @ -34,8 +35,9 @@ import ( | ||||||
| 
 | 
 | ||||||
| // Controller represents operator controller
 | // Controller represents operator controller
 | ||||||
| type Controller struct { | type Controller struct { | ||||||
| 	config   spec.ControllerConfig | 	config    spec.ControllerConfig | ||||||
| 	opConfig *config.Config | 	opConfig  *config.Config | ||||||
|  | 	pgTeamMap teams.PostgresTeamMap | ||||||
| 
 | 
 | ||||||
| 	logger     *logrus.Entry | 	logger     *logrus.Entry | ||||||
| 	KubeClient k8sutil.KubernetesClient | 	KubeClient k8sutil.KubernetesClient | ||||||
|  | @ -56,10 +58,11 @@ type Controller struct { | ||||||
| 	clusterHistory   map[spec.NamespacedName]ringlog.RingLogger // history of the cluster changes
 | 	clusterHistory   map[spec.NamespacedName]ringlog.RingLogger // history of the cluster changes
 | ||||||
| 	teamClusters     map[string][]spec.NamespacedName | 	teamClusters     map[string][]spec.NamespacedName | ||||||
| 
 | 
 | ||||||
| 	postgresqlInformer cache.SharedIndexInformer | 	postgresqlInformer   cache.SharedIndexInformer | ||||||
| 	podInformer        cache.SharedIndexInformer | 	postgresTeamInformer cache.SharedIndexInformer | ||||||
| 	nodesInformer      cache.SharedIndexInformer | 	podInformer          cache.SharedIndexInformer | ||||||
| 	podCh              chan cluster.PodEvent | 	nodesInformer        cache.SharedIndexInformer | ||||||
|  | 	podCh                chan cluster.PodEvent | ||||||
| 
 | 
 | ||||||
| 	clusterEventQueues    []*cache.FIFO // [workerID]Queue
 | 	clusterEventQueues    []*cache.FIFO // [workerID]Queue
 | ||||||
| 	lastClusterSyncTime   int64 | 	lastClusterSyncTime   int64 | ||||||
|  | @ -326,6 +329,12 @@ func (c *Controller) initController() { | ||||||
| 
 | 
 | ||||||
| 	c.initSharedInformers() | 	c.initSharedInformers() | ||||||
| 
 | 
 | ||||||
|  | 	if c.opConfig.EnablePostgresTeamCRD != nil && *c.opConfig.EnablePostgresTeamCRD { | ||||||
|  | 		c.loadPostgresTeams() | ||||||
|  | 	} else { | ||||||
|  | 		c.pgTeamMap = teams.PostgresTeamMap{} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if c.opConfig.DebugLogging { | 	if c.opConfig.DebugLogging { | ||||||
| 		c.logger.Logger.Level = logrus.DebugLevel | 		c.logger.Logger.Level = logrus.DebugLevel | ||||||
| 	} | 	} | ||||||
|  | @ -357,6 +366,7 @@ func (c *Controller) initController() { | ||||||
| 
 | 
 | ||||||
| func (c *Controller) initSharedInformers() { | func (c *Controller) initSharedInformers() { | ||||||
| 
 | 
 | ||||||
|  | 	// Postgresqls
 | ||||||
| 	c.postgresqlInformer = acidv1informer.NewPostgresqlInformer( | 	c.postgresqlInformer = acidv1informer.NewPostgresqlInformer( | ||||||
| 		c.KubeClient.AcidV1ClientSet, | 		c.KubeClient.AcidV1ClientSet, | ||||||
| 		c.opConfig.WatchedNamespace, | 		c.opConfig.WatchedNamespace, | ||||||
|  | @ -369,6 +379,20 @@ func (c *Controller) initSharedInformers() { | ||||||
| 		DeleteFunc: c.postgresqlDelete, | 		DeleteFunc: c.postgresqlDelete, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | 	// PostgresTeams
 | ||||||
|  | 	if c.opConfig.EnablePostgresTeamCRD != nil && *c.opConfig.EnablePostgresTeamCRD { | ||||||
|  | 		c.postgresTeamInformer = acidv1informer.NewPostgresTeamInformer( | ||||||
|  | 			c.KubeClient.AcidV1ClientSet, | ||||||
|  | 			c.opConfig.WatchedNamespace, | ||||||
|  | 			constants.QueueResyncPeriodTPR*6, // 30 min
 | ||||||
|  | 			cache.Indexers{}) | ||||||
|  | 
 | ||||||
|  | 		c.postgresTeamInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ | ||||||
|  | 			AddFunc:    c.postgresTeamAdd, | ||||||
|  | 			UpdateFunc: c.postgresTeamUpdate, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Pods
 | 	// Pods
 | ||||||
| 	podLw := &cache.ListWatch{ | 	podLw := &cache.ListWatch{ | ||||||
| 		ListFunc:  c.podListFunc, | 		ListFunc:  c.podListFunc, | ||||||
|  | @ -429,6 +453,10 @@ func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) { | ||||||
| 	go c.apiserver.Run(stopCh, wg) | 	go c.apiserver.Run(stopCh, wg) | ||||||
| 	go c.kubeNodesInformer(stopCh, wg) | 	go c.kubeNodesInformer(stopCh, wg) | ||||||
| 
 | 
 | ||||||
|  | 	if c.opConfig.EnablePostgresTeamCRD != nil && *c.opConfig.EnablePostgresTeamCRD { | ||||||
|  | 		go c.runPostgresTeamInformer(stopCh, wg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	c.logger.Info("started working in background") | 	c.logger.Info("started working in background") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -444,6 +472,12 @@ func (c *Controller) runPostgresqlInformer(stopCh <-chan struct{}, wg *sync.Wait | ||||||
| 	c.postgresqlInformer.Run(stopCh) | 	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 { | func queueClusterKey(eventType EventType, uid types.UID) string { | ||||||
| 	return fmt.Sprintf("%s-%s", eventType, uid) | 	return fmt.Sprintf("%s-%s", eventType, uid) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -163,6 +163,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur | ||||||
| 	result.PamConfiguration = util.Coalesce(fromCRD.TeamsAPI.PamConfiguration, "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees") | 	result.PamConfiguration = util.Coalesce(fromCRD.TeamsAPI.PamConfiguration, "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees") | ||||||
| 	result.ProtectedRoles = util.CoalesceStrArr(fromCRD.TeamsAPI.ProtectedRoles, []string{"admin"}) | 	result.ProtectedRoles = util.CoalesceStrArr(fromCRD.TeamsAPI.ProtectedRoles, []string{"admin"}) | ||||||
| 	result.PostgresSuperuserTeams = fromCRD.TeamsAPI.PostgresSuperuserTeams | 	result.PostgresSuperuserTeams = fromCRD.TeamsAPI.PostgresSuperuserTeams | ||||||
|  | 	result.EnablePostgresTeamCRD = util.CoalesceBool(fromCRD.TeamsAPI.EnablePostgresTeamCRD, util.True()) | ||||||
|  | 	result.EnablePostgresTeamCRDSuperusers = fromCRD.TeamsAPI.EnablePostgresTeamCRDSuperusers | ||||||
| 
 | 
 | ||||||
| 	// logging REST API config
 | 	// logging REST API config
 | ||||||
| 	result.APIPort = util.CoalesceInt(fromCRD.LoggingRESTAPI.APIPort, 8080) | 	result.APIPort = util.CoalesceInt(fromCRD.LoggingRESTAPI.APIPort, 8080) | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ import ( | ||||||
| 	acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" | 	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/cluster" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/spec" | 	"github.com/zalando/postgres-operator/pkg/spec" | ||||||
|  | 	"github.com/zalando/postgres-operator/pkg/teams" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util" | 	"github.com/zalando/postgres-operator/pkg/util" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/config" | 	"github.com/zalando/postgres-operator/pkg/util/config" | ||||||
| 	"github.com/zalando/postgres-operator/pkg/util/k8sutil" | 	"github.com/zalando/postgres-operator/pkg/util/k8sutil" | ||||||
|  | @ -30,6 +31,7 @@ func (c *Controller) makeClusterConfig() cluster.Config { | ||||||
| 	return cluster.Config{ | 	return cluster.Config{ | ||||||
| 		RestConfig:          c.config.RestConfig, | 		RestConfig:          c.config.RestConfig, | ||||||
| 		OpConfig:            config.Copy(c.opConfig), | 		OpConfig:            config.Copy(c.opConfig), | ||||||
|  | 		PgTeamMap:           c.pgTeamMap, | ||||||
| 		InfrastructureRoles: infrastructureRoles, | 		InfrastructureRoles: infrastructureRoles, | ||||||
| 		PodServiceAccount:   c.PodServiceAccount, | 		PodServiceAccount:   c.PodServiceAccount, | ||||||
| 	} | 	} | ||||||
|  | @ -394,6 +396,37 @@ func (c *Controller) getInfrastructureRole( | ||||||
| 	return roles, nil | 	return roles, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *Controller) loadPostgresTeams() { | ||||||
|  | 	// reset team map
 | ||||||
|  | 	c.pgTeamMap = teams.PostgresTeamMap{} | ||||||
|  | 
 | ||||||
|  | 	pgTeams, err := c.KubeClient.AcidV1ClientSet.AcidV1().PostgresTeams(c.opConfig.WatchedNamespace).List(context.TODO(), metav1.ListOptions{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.logger.Errorf("could not list postgres team objects: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.pgTeamMap.Load(pgTeams) | ||||||
|  | 	c.logger.Debugf("Internal Postgres Team Cache: %#v", c.pgTeamMap) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Controller) postgresTeamAdd(obj interface{}) { | ||||||
|  | 	pgTeam, ok := obj.(*acidv1.PostgresTeam) | ||||||
|  | 	if !ok { | ||||||
|  | 		c.logger.Errorf("could not cast to PostgresTeam spec") | ||||||
|  | 	} | ||||||
|  | 	c.logger.Debugf("PostgreTeam %q added. Reloading postgres team CRDs and overwriting cached map", pgTeam.Name) | ||||||
|  | 	c.loadPostgresTeams() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Controller) postgresTeamUpdate(prev, obj interface{}) { | ||||||
|  | 	pgTeam, ok := obj.(*acidv1.PostgresTeam) | ||||||
|  | 	if !ok { | ||||||
|  | 		c.logger.Errorf("could not cast to PostgresTeam spec") | ||||||
|  | 	} | ||||||
|  | 	c.logger.Debugf("PostgreTeam %q updated. Reloading postgres team CRDs and overwriting cached map", pgTeam.Name) | ||||||
|  | 	c.loadPostgresTeams() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *Controller) podClusterName(pod *v1.Pod) spec.NamespacedName { | func (c *Controller) podClusterName(pod *v1.Pod) spec.NamespacedName { | ||||||
| 	if name, ok := pod.Labels[c.opConfig.ClusterNameLabel]; ok { | 	if name, ok := pod.Labels[c.opConfig.ClusterNameLabel]; ok { | ||||||
| 		return spec.NamespacedName{ | 		return spec.NamespacedName{ | ||||||
|  |  | ||||||
|  | @ -33,6 +33,7 @@ import ( | ||||||
| type AcidV1Interface interface { | type AcidV1Interface interface { | ||||||
| 	RESTClient() rest.Interface | 	RESTClient() rest.Interface | ||||||
| 	OperatorConfigurationsGetter | 	OperatorConfigurationsGetter | ||||||
|  | 	PostgresTeamsGetter | ||||||
| 	PostgresqlsGetter | 	PostgresqlsGetter | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -45,6 +46,10 @@ func (c *AcidV1Client) OperatorConfigurations(namespace string) OperatorConfigur | ||||||
| 	return newOperatorConfigurations(c, namespace) | 	return newOperatorConfigurations(c, namespace) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *AcidV1Client) PostgresTeams(namespace string) PostgresTeamInterface { | ||||||
|  | 	return newPostgresTeams(c, namespace) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *AcidV1Client) Postgresqls(namespace string) PostgresqlInterface { | func (c *AcidV1Client) Postgresqls(namespace string) PostgresqlInterface { | ||||||
| 	return newPostgresqls(c, namespace) | 	return newPostgresqls(c, namespace) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -38,6 +38,10 @@ func (c *FakeAcidV1) OperatorConfigurations(namespace string) v1.OperatorConfigu | ||||||
| 	return &FakeOperatorConfigurations{c, namespace} | 	return &FakeOperatorConfigurations{c, namespace} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *FakeAcidV1) PostgresTeams(namespace string) v1.PostgresTeamInterface { | ||||||
|  | 	return &FakePostgresTeams{c, namespace} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *FakeAcidV1) Postgresqls(namespace string) v1.PostgresqlInterface { | func (c *FakeAcidV1) Postgresqls(namespace string) v1.PostgresqlInterface { | ||||||
| 	return &FakePostgresqls{c, namespace} | 	return &FakePostgresqls{c, namespace} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | } | ||||||
|  | @ -26,4 +26,6 @@ package v1 | ||||||
| 
 | 
 | ||||||
| type OperatorConfigurationExpansion interface{} | type OperatorConfigurationExpansion interface{} | ||||||
| 
 | 
 | ||||||
|  | type PostgresTeamExpansion interface{} | ||||||
|  | 
 | ||||||
| type PostgresqlExpansion interface{} | type PostgresqlExpansion interface{} | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | } | ||||||
|  | @ -30,6 +30,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| // Interface provides access to all the informers in this group version.
 | // Interface provides access to all the informers in this group version.
 | ||||||
| type Interface interface { | type Interface interface { | ||||||
|  | 	// PostgresTeams returns a PostgresTeamInformer.
 | ||||||
|  | 	PostgresTeams() PostgresTeamInformer | ||||||
| 	// Postgresqls returns a PostgresqlInformer.
 | 	// Postgresqls returns a PostgresqlInformer.
 | ||||||
| 	Postgresqls() PostgresqlInformer | 	Postgresqls() PostgresqlInformer | ||||||
| } | } | ||||||
|  | @ -45,6 +47,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList | ||||||
| 	return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} | 	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.
 | // Postgresqls returns a PostgresqlInformer.
 | ||||||
| func (v *version) Postgresqls() PostgresqlInformer { | func (v *version) Postgresqls() PostgresqlInformer { | ||||||
| 	return &postgresqlInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} | 	return &postgresqlInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} | ||||||
|  |  | ||||||
|  | @ -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()) | ||||||
|  | } | ||||||
|  | @ -59,6 +59,8 @@ func (f *genericInformer) Lister() cache.GenericLister { | ||||||
| func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { | ||||||
| 	switch resource { | 	switch resource { | ||||||
| 	// Group=acid.zalan.do, Version=v1
 | 	// 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"): | 	case v1.SchemeGroupVersion.WithResource("postgresqls"): | ||||||
| 		return &genericInformer{resource: resource.GroupResource(), informer: f.Acid().V1().Postgresqls().Informer()}, nil | 		return &genericInformer{resource: resource.GroupResource(), informer: f.Acid().V1().Postgresqls().Informer()}, nil | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,6 +24,14 @@ SOFTWARE. | ||||||
| 
 | 
 | ||||||
| package v1 | 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
 | // PostgresqlListerExpansion allows custom methods to be added to
 | ||||||
| // PostgresqlLister.
 | // PostgresqlLister.
 | ||||||
| type PostgresqlListerExpansion interface{} | type PostgresqlListerExpansion interface{} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,105 @@ | ||||||
|  | /* | ||||||
|  | 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.
 | ||||||
|  | // All objects returned here must be treated as read-only.
 | ||||||
|  | type PostgresTeamLister interface { | ||||||
|  | 	// List lists all PostgresTeams in the indexer.
 | ||||||
|  | 	// Objects returned here must be treated as read-only.
 | ||||||
|  | 	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.
 | ||||||
|  | // All objects returned here must be treated as read-only.
 | ||||||
|  | type PostgresTeamNamespaceLister interface { | ||||||
|  | 	// List lists all PostgresTeams in the indexer for a given namespace.
 | ||||||
|  | 	// Objects returned here must be treated as read-only.
 | ||||||
|  | 	List(selector labels.Selector) (ret []*v1.PostgresTeam, err error) | ||||||
|  | 	// Get retrieves the PostgresTeam from the indexer for a given namespace and name.
 | ||||||
|  | 	// Objects returned here must be treated as read-only.
 | ||||||
|  | 	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 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,118 @@ | ||||||
|  | package teams | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" | ||||||
|  | 	"github.com/zalando/postgres-operator/pkg/util" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // PostgresTeamMap is the operator's internal representation of all PostgresTeam CRDs
 | ||||||
|  | type PostgresTeamMap map[string]postgresTeamMembership | ||||||
|  | 
 | ||||||
|  | type postgresTeamMembership struct { | ||||||
|  | 	AdditionalSuperuserTeams []string | ||||||
|  | 	AdditionalTeams          []string | ||||||
|  | 	AdditionalMembers        []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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) toMap() map[string][]string { | ||||||
|  | 	newTeamMap := make(map[string][]string) | ||||||
|  | 	for team, items := range *ths { | ||||||
|  | 		list := []string{} | ||||||
|  | 		for item := range items { | ||||||
|  | 			list = append(list, item) | ||||||
|  | 		} | ||||||
|  | 		newTeamMap[team] = list | ||||||
|  | 	} | ||||||
|  | 	return newTeamMap | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ths *teamHashSet) mergeCrdMap(crdTeamMap map[string][]string) { | ||||||
|  | 	for t, at := range crdTeamMap { | ||||||
|  | 		ths.add(t, at) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func fetchTeams(teamset *map[string]struct{}, set teamHashSet) { | ||||||
|  | 	for key := range set { | ||||||
|  | 		(*teamset)[key] = struct{}{} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ptm *PostgresTeamMap) fetchAdditionalTeams(team string, superuserTeams bool, transitive bool, exclude []string) []string { | ||||||
|  | 
 | ||||||
|  | 	var teams []string | ||||||
|  | 
 | ||||||
|  | 	if superuserTeams { | ||||||
|  | 		teams = (*ptm)[team].AdditionalSuperuserTeams | ||||||
|  | 	} else { | ||||||
|  | 		teams = (*ptm)[team].AdditionalTeams | ||||||
|  | 	} | ||||||
|  | 	if transitive { | ||||||
|  | 		exclude = append(exclude, team) | ||||||
|  | 		for _, additionalTeam := range teams { | ||||||
|  | 			if !(util.SliceContains(exclude, additionalTeam)) { | ||||||
|  | 				transitiveTeams := (*ptm).fetchAdditionalTeams(additionalTeam, superuserTeams, transitive, exclude) | ||||||
|  | 				for _, transitiveTeam := range transitiveTeams { | ||||||
|  | 					if !(util.SliceContains(exclude, transitiveTeam)) && !(util.SliceContains(teams, transitiveTeam)) { | ||||||
|  | 						teams = append(teams, transitiveTeam) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return teams | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetAdditionalTeams function to retrieve list of additional teams
 | ||||||
|  | func (ptm *PostgresTeamMap) GetAdditionalTeams(team string, transitive bool) []string { | ||||||
|  | 	return ptm.fetchAdditionalTeams(team, false, transitive, []string{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetAdditionalSuperuserTeams function to retrieve list of additional superuser teams
 | ||||||
|  | func (ptm *PostgresTeamMap) GetAdditionalSuperuserTeams(team string, transitive bool) []string { | ||||||
|  | 	return ptm.fetchAdditionalTeams(team, true, transitive, []string{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load function to import data from PostgresTeam CRD
 | ||||||
|  | func (ptm *PostgresTeamMap) Load(pgTeams *acidv1.PostgresTeamList) { | ||||||
|  | 	superuserTeamSet := teamHashSet{} | ||||||
|  | 	teamSet := teamHashSet{} | ||||||
|  | 	teamMemberSet := teamHashSet{} | ||||||
|  | 	teamIDs := make(map[string]struct{}) | ||||||
|  | 
 | ||||||
|  | 	for _, pgTeam := range pgTeams.Items { | ||||||
|  | 		superuserTeamSet.mergeCrdMap(pgTeam.Spec.AdditionalSuperuserTeams) | ||||||
|  | 		teamSet.mergeCrdMap(pgTeam.Spec.AdditionalTeams) | ||||||
|  | 		teamMemberSet.mergeCrdMap(pgTeam.Spec.AdditionalMembers) | ||||||
|  | 	} | ||||||
|  | 	fetchTeams(&teamIDs, superuserTeamSet) | ||||||
|  | 	fetchTeams(&teamIDs, teamSet) | ||||||
|  | 	fetchTeams(&teamIDs, teamMemberSet) | ||||||
|  | 
 | ||||||
|  | 	for teamID := range teamIDs { | ||||||
|  | 		(*ptm)[teamID] = postgresTeamMembership{ | ||||||
|  | 			AdditionalSuperuserTeams: util.CoalesceStrArr(superuserTeamSet.toMap()[teamID], []string{}), | ||||||
|  | 			AdditionalTeams:          util.CoalesceStrArr(teamSet.toMap()[teamID], []string{}), | ||||||
|  | 			AdditionalMembers:        util.CoalesceStrArr(teamMemberSet.toMap()[teamID], []string{}), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,194 @@ | ||||||
|  | package teams | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" | ||||||
|  | 	"github.com/zalando/postgres-operator/pkg/util" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	True       = true | ||||||
|  | 	False      = false | ||||||
|  | 	pgTeamList = 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{ | ||||||
|  | 					AdditionalSuperuserTeams: map[string][]string{"teamA": []string{"teamB", "team24x7"}, "teamB": []string{"teamA", "teamC", "team24x7"}}, | ||||||
|  | 					AdditionalTeams:          map[string][]string{"teamA": []string{"teamC"}, "teamB": []string{}}, | ||||||
|  | 					AdditionalMembers:        map[string][]string{"team24x7": []string{"optimusprime"}, "teamB": []string{"drno"}}, | ||||||
|  | 				}, | ||||||
|  | 			}, { | ||||||
|  | 				TypeMeta: metav1.TypeMeta{ | ||||||
|  | 					Kind:       "PostgresTeam", | ||||||
|  | 					APIVersion: "acid.zalan.do/v1", | ||||||
|  | 				}, | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name: "teamC", | ||||||
|  | 				}, | ||||||
|  | 				Spec: acidv1.PostgresTeamSpec{ | ||||||
|  | 					AdditionalSuperuserTeams: map[string][]string{"teamC": []string{"team24x7"}}, | ||||||
|  | 					AdditionalTeams:          map[string][]string{"teamA": []string{"teamC"}, "teamC": []string{"teamA", "teamB", "acid"}}, | ||||||
|  | 					AdditionalMembers:        map[string][]string{"acid": []string{"batman"}}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // PostgresTeamMap is the operator's internal representation of all PostgresTeam CRDs
 | ||||||
|  | func TestLoadingPostgresTeamCRD(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name  string | ||||||
|  | 		crd   acidv1.PostgresTeamList | ||||||
|  | 		ptm   PostgresTeamMap | ||||||
|  | 		error string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			"Check that CRD is imported correctly into the internal format", | ||||||
|  | 			pgTeamList, | ||||||
|  | 			PostgresTeamMap{ | ||||||
|  | 				"teamA": { | ||||||
|  | 					AdditionalSuperuserTeams: []string{"teamB", "team24x7"}, | ||||||
|  | 					AdditionalTeams:          []string{"teamC"}, | ||||||
|  | 					AdditionalMembers:        []string{}, | ||||||
|  | 				}, | ||||||
|  | 				"teamB": { | ||||||
|  | 					AdditionalSuperuserTeams: []string{"teamA", "teamC", "team24x7"}, | ||||||
|  | 					AdditionalTeams:          []string{}, | ||||||
|  | 					AdditionalMembers:        []string{"drno"}, | ||||||
|  | 				}, | ||||||
|  | 				"teamC": { | ||||||
|  | 					AdditionalSuperuserTeams: []string{"team24x7"}, | ||||||
|  | 					AdditionalTeams:          []string{"teamA", "teamB", "acid"}, | ||||||
|  | 					AdditionalMembers:        []string{}, | ||||||
|  | 				}, | ||||||
|  | 				"team24x7": { | ||||||
|  | 					AdditionalSuperuserTeams: []string{}, | ||||||
|  | 					AdditionalTeams:          []string{}, | ||||||
|  | 					AdditionalMembers:        []string{"optimusprime"}, | ||||||
|  | 				}, | ||||||
|  | 				"acid": { | ||||||
|  | 					AdditionalSuperuserTeams: []string{}, | ||||||
|  | 					AdditionalTeams:          []string{}, | ||||||
|  | 					AdditionalMembers:        []string{"batman"}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			"Mismatch between PostgresTeam CRD and internal map", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		postgresTeamMap := PostgresTeamMap{} | ||||||
|  | 		postgresTeamMap.Load(&tt.crd) | ||||||
|  | 		for team, ptmeamMembership := range postgresTeamMap { | ||||||
|  | 			if !util.IsEqualIgnoreOrder(ptmeamMembership.AdditionalSuperuserTeams, tt.ptm[team].AdditionalSuperuserTeams) { | ||||||
|  | 				t.Errorf("%s: %v: expected additional members %#v, got %#v", tt.name, tt.error, tt.ptm, postgresTeamMap) | ||||||
|  | 			} | ||||||
|  | 			if !util.IsEqualIgnoreOrder(ptmeamMembership.AdditionalTeams, tt.ptm[team].AdditionalTeams) { | ||||||
|  | 				t.Errorf("%s: %v: expected additional teams %#v, got %#v", tt.name, tt.error, tt.ptm, postgresTeamMap) | ||||||
|  | 			} | ||||||
|  | 			if !util.IsEqualIgnoreOrder(ptmeamMembership.AdditionalMembers, tt.ptm[team].AdditionalMembers) { | ||||||
|  | 				t.Errorf("%s: %v: expected additional superuser teams %#v, got %#v", tt.name, tt.error, tt.ptm, postgresTeamMap) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TestGetAdditionalTeams if returns teams with and without transitive dependencies
 | ||||||
|  | func TestGetAdditionalTeams(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name       string | ||||||
|  | 		team       string | ||||||
|  | 		transitive bool | ||||||
|  | 		teams      []string | ||||||
|  | 		error      string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			"Check that additional teams are returned", | ||||||
|  | 			"teamA", | ||||||
|  | 			false, | ||||||
|  | 			[]string{"teamC"}, | ||||||
|  | 			"GetAdditionalTeams returns wrong list", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"Check that additional teams are returned incl. transitive teams", | ||||||
|  | 			"teamA", | ||||||
|  | 			true, | ||||||
|  | 			[]string{"teamC", "teamB", "acid"}, | ||||||
|  | 			"GetAdditionalTeams returns wrong list", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"Check that empty list is returned", | ||||||
|  | 			"teamB", | ||||||
|  | 			false, | ||||||
|  | 			[]string{}, | ||||||
|  | 			"GetAdditionalTeams returns wrong list", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	postgresTeamMap := PostgresTeamMap{} | ||||||
|  | 	postgresTeamMap.Load(&pgTeamList) | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		additionalTeams := postgresTeamMap.GetAdditionalTeams(tt.team, tt.transitive) | ||||||
|  | 		if !util.IsEqualIgnoreOrder(additionalTeams, tt.teams) { | ||||||
|  | 			t.Errorf("%s: %v: expected additional teams %#v, got %#v", tt.name, tt.error, tt.teams, additionalTeams) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TestGetAdditionalSuperuserTeams if returns teams with and without transitive dependencies
 | ||||||
|  | func TestGetAdditionalSuperuserTeams(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name       string | ||||||
|  | 		team       string | ||||||
|  | 		transitive bool | ||||||
|  | 		teams      []string | ||||||
|  | 		error      string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			"Check that additional superuser teams are returned", | ||||||
|  | 			"teamA", | ||||||
|  | 			false, | ||||||
|  | 			[]string{"teamB", "team24x7"}, | ||||||
|  | 			"GetAdditionalSuperuserTeams returns wrong list", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"Check that additional superuser teams are returned incl. transitive superuser teams", | ||||||
|  | 			"teamA", | ||||||
|  | 			true, | ||||||
|  | 			[]string{"teamB", "teamC", "team24x7"}, | ||||||
|  | 			"GetAdditionalSuperuserTeams returns wrong list", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"Check that empty list is returned", | ||||||
|  | 			"team24x7", | ||||||
|  | 			false, | ||||||
|  | 			[]string{}, | ||||||
|  | 			"GetAdditionalSuperuserTeams returns wrong list", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	postgresTeamMap := PostgresTeamMap{} | ||||||
|  | 	postgresTeamMap.Load(&pgTeamList) | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		additionalTeams := postgresTeamMap.GetAdditionalSuperuserTeams(tt.team, tt.transitive) | ||||||
|  | 		if !util.IsEqualIgnoreOrder(additionalTeams, tt.teams) { | ||||||
|  | 			t.Errorf("%s: %v: expected additional teams %#v, got %#v", tt.name, tt.error, tt.teams, additionalTeams) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -169,6 +169,8 @@ type Config struct { | ||||||
| 	EnableTeamSuperuser                    bool              `name:"enable_team_superuser" default:"false"` | 	EnableTeamSuperuser                    bool              `name:"enable_team_superuser" default:"false"` | ||||||
| 	TeamAdminRole                          string            `name:"team_admin_role" default:"admin"` | 	TeamAdminRole                          string            `name:"team_admin_role" default:"admin"` | ||||||
| 	EnableAdminRoleForUsers                bool              `name:"enable_admin_role_for_users" default:"true"` | 	EnableAdminRoleForUsers                bool              `name:"enable_admin_role_for_users" default:"true"` | ||||||
|  | 	EnablePostgresTeamCRD                  *bool             `name:"enable_postgres_team_crd" default:"true"` | ||||||
|  | 	EnablePostgresTeamCRDSuperusers        bool              `name:"enable_postgres_team_crd_superusers" default:"false"` | ||||||
| 	EnableMasterLoadBalancer               bool              `name:"enable_master_load_balancer" default:"true"` | 	EnableMasterLoadBalancer               bool              `name:"enable_master_load_balancer" default:"true"` | ||||||
| 	EnableReplicaLoadBalancer              bool              `name:"enable_replica_load_balancer" default:"false"` | 	EnableReplicaLoadBalancer              bool              `name:"enable_replica_load_balancer" default:"false"` | ||||||
| 	CustomServiceAnnotations               map[string]string `name:"custom_service_annotations"` | 	CustomServiceAnnotations               map[string]string `name:"custom_service_annotations"` | ||||||
|  |  | ||||||
|  | @ -10,7 +10,9 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"math/big" | 	"math/big" | ||||||
| 	"math/rand" | 	"math/rand" | ||||||
|  | 	"reflect" | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | 	"sort" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | @ -134,6 +136,21 @@ func PrettyDiff(a, b interface{}) string { | ||||||
| 	return strings.Join(Diff(a, b), "\n") | 	return strings.Join(Diff(a, b), "\n") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Compare two string slices while ignoring the order of elements
 | ||||||
|  | func IsEqualIgnoreOrder(a, b []string) bool { | ||||||
|  | 	if len(a) != len(b) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	a_copy := make([]string, len(a)) | ||||||
|  | 	b_copy := make([]string, len(b)) | ||||||
|  | 	copy(a_copy, a) | ||||||
|  | 	copy(b_copy, b) | ||||||
|  | 	sort.Strings(a_copy) | ||||||
|  | 	sort.Strings(b_copy) | ||||||
|  | 
 | ||||||
|  | 	return reflect.DeepEqual(a_copy, b_copy) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // SubstractStringSlices finds elements in a that are not in b and return them as a result slice.
 | // 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) { | func SubstractStringSlices(a []string, b []string) (result []string, equal bool) { | ||||||
| 	// Slices are assumed to contain unique elements only
 | 	// Slices are assumed to contain unique elements only
 | ||||||
|  | @ -176,6 +193,20 @@ func FindNamedStringSubmatch(r *regexp.Regexp, s string) map[string]string { | ||||||
| 	return res | 	return res | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SliceContains
 | ||||||
|  | func SliceContains(slice interface{}, item interface{}) bool { | ||||||
|  | 	s := reflect.ValueOf(slice) | ||||||
|  | 	if s.Kind() != reflect.Slice { | ||||||
|  | 		panic("Invalid data-type") | ||||||
|  | 	} | ||||||
|  | 	for i := 0; i < s.Len(); i++ { | ||||||
|  | 		if s.Index(i).Interface() == item { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // MapContains returns true if and only if haystack contains all the keys from the needle with matching corresponding values
 | // MapContains returns true if and only if haystack contains all the keys from the needle with matching corresponding values
 | ||||||
| func MapContains(haystack, needle map[string]string) bool { | func MapContains(haystack, needle map[string]string) bool { | ||||||
| 	if len(haystack) < len(needle) { | 	if len(haystack) < len(needle) { | ||||||
|  |  | ||||||
|  | @ -43,6 +43,17 @@ var prettyDiffTest = []struct { | ||||||
| 	{[]int{1, 2, 3, 4}, []int{1, 2, 3, 4}, ""}, | 	{[]int{1, 2, 3, 4}, []int{1, 2, 3, 4}, ""}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var isEqualIgnoreOrderTest = []struct { | ||||||
|  | 	inA      []string | ||||||
|  | 	inB      []string | ||||||
|  | 	outEqual bool | ||||||
|  | }{ | ||||||
|  | 	{[]string{"a", "b", "c"}, []string{"a", "b", "c"}, true}, | ||||||
|  | 	{[]string{"a", "b", "c"}, []string{"a", "c", "b"}, true}, | ||||||
|  | 	{[]string{"a", "b"}, []string{"a", "c", "b"}, false}, | ||||||
|  | 	{[]string{"a", "b", "c"}, []string{"a", "d", "c"}, false}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var substractTest = []struct { | var substractTest = []struct { | ||||||
| 	inA      []string | 	inA      []string | ||||||
| 	inB      []string | 	inB      []string | ||||||
|  | @ -53,6 +64,16 @@ var substractTest = []struct { | ||||||
| 	{[]string{"a", "b", "c", "d"}, []string{"a", "bb", "c", "d"}, []string{"b"}, false}, | 	{[]string{"a", "b", "c", "d"}, []string{"a", "bb", "c", "d"}, []string{"b"}, false}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var sliceContaintsTest = []struct { | ||||||
|  | 	slice []string | ||||||
|  | 	item  string | ||||||
|  | 	out   bool | ||||||
|  | }{ | ||||||
|  | 	{[]string{"a", "b", "c"}, "a", true}, | ||||||
|  | 	{[]string{"a", "b", "c"}, "d", false}, | ||||||
|  | 	{[]string{}, "d", false}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var mapContaintsTest = []struct { | var mapContaintsTest = []struct { | ||||||
| 	inA map[string]string | 	inA map[string]string | ||||||
| 	inB map[string]string | 	inB map[string]string | ||||||
|  | @ -136,6 +157,15 @@ func TestPrettyDiff(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestIsEqualIgnoreOrder(t *testing.T) { | ||||||
|  | 	for _, tt := range isEqualIgnoreOrderTest { | ||||||
|  | 		actualEqual := IsEqualIgnoreOrder(tt.inA, tt.inB) | ||||||
|  | 		if actualEqual != tt.outEqual { | ||||||
|  | 			t.Errorf("IsEqualIgnoreOrder expected: %t, got: %t", tt.outEqual, actualEqual) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestSubstractSlices(t *testing.T) { | func TestSubstractSlices(t *testing.T) { | ||||||
| 	for _, tt := range substractTest { | 	for _, tt := range substractTest { | ||||||
| 		actualRes, actualEqual := SubstractStringSlices(tt.inA, tt.inB) | 		actualRes, actualEqual := SubstractStringSlices(tt.inA, tt.inB) | ||||||
|  | @ -160,6 +190,15 @@ func TestFindNamedStringSubmatch(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestSliceContains(t *testing.T) { | ||||||
|  | 	for _, tt := range sliceContaintsTest { | ||||||
|  | 		res := SliceContains(tt.slice, tt.item) | ||||||
|  | 		if res != tt.out { | ||||||
|  | 			t.Errorf("SliceContains expected: %#v, got: %#v", tt.out, res) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestMapContains(t *testing.T) { | func TestMapContains(t *testing.T) { | ||||||
| 	for _, tt := range mapContaintsTest { | 	for _, tt := range mapContaintsTest { | ||||||
| 		res := MapContains(tt.inA, tt.inB) | 		res := MapContains(tt.inA, tt.inB) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue