Create human users from teams api
This commit is contained in:
		
							parent
							
								
									58506634c4
								
							
						
					
					
						commit
						6e2d64bd50
					
				|  | @ -9,7 +9,9 @@ import ( | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 
 | 
 | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/controller" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/controller" | ||||||
|  | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil" | ||||||
|  | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/teams" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -43,10 +45,12 @@ func ControllerConfig() *controller.Config { | ||||||
| 
 | 
 | ||||||
| 	restClient, err := k8sutil.KubernetesRestClient(restConfig) | 	restClient, err := k8sutil.KubernetesRestClient(restConfig) | ||||||
| 
 | 
 | ||||||
|  | 	teamsApi := teams.NewTeamsAPI(constants.TeamsAPIUrl) | ||||||
| 	return &controller.Config{ | 	return &controller.Config{ | ||||||
| 		Namespace:      Namespace, | 		Namespace:      Namespace, | ||||||
| 		KubeClient:     client, | 		KubeClient:     client, | ||||||
| 		RestClient:     restClient, | 		RestClient:     restClient, | ||||||
|  | 		TeamsAPIClient: teamsApi, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,9 @@ | ||||||
| FROM alpine | FROM alpine | ||||||
| MAINTAINER Team ACID @ Zalando <team-acid@zalando.de> | MAINTAINER Team ACID @ Zalando <team-acid@zalando.de> | ||||||
| 
 | 
 | ||||||
|  | # We need root certificates for dealing with teams api over https | ||||||
|  | RUN apk --no-cache add ca-certificates | ||||||
|  | 
 | ||||||
| COPY build/* / | COPY build/* / | ||||||
| 
 | 
 | ||||||
| ENTRYPOINT ["/postgres-operator"] | ENTRYPOINT ["/postgres-operator"] | ||||||
|  |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | apiVersion: "zalando.org/v1" | ||||||
|  | kind: PlatformCredentialsSet | ||||||
|  | metadata: | ||||||
|  |   name: postgresql-operator | ||||||
|  | spec: | ||||||
|  |   application: postgresql-operator | ||||||
|  |   tokens: | ||||||
|  |     read-only: | ||||||
|  |       privileges: | ||||||
|  |     cluster-registry-rw: | ||||||
|  |       privileges: | ||||||
|  |     cluster-rw: | ||||||
|  |       privileges: | ||||||
|  | @ -21,10 +21,11 @@ import ( | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/util" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util" | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/retryutil" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/retryutil" | ||||||
|  | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/teams" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	superUsername       = "superuser" | 	superuserName       = "postgres" | ||||||
| 	replicationUsername = "replication" | 	replicationUsername = "replication" | ||||||
| 
 | 
 | ||||||
| 	alphaNumericRegexp = regexp.MustCompile("^[a-zA-Z0-9]*$") | 	alphaNumericRegexp = regexp.MustCompile("^[a-zA-Z0-9]*$") | ||||||
|  | @ -36,6 +37,7 @@ type Config struct { | ||||||
| 	KubeClient     *kubernetes.Clientset //TODO: move clients to the better place?
 | 	KubeClient     *kubernetes.Clientset //TODO: move clients to the better place?
 | ||||||
| 	RestClient     *rest.RESTClient | 	RestClient     *rest.RESTClient | ||||||
| 	EtcdClient     etcdclient.KeysAPI | 	EtcdClient     etcdclient.KeysAPI | ||||||
|  | 	TeamsAPIClient *teams.TeamsAPI | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type pgUser struct { | type pgUser struct { | ||||||
|  | @ -45,8 +47,8 @@ type pgUser struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Cluster struct { | type Cluster struct { | ||||||
| 	logger      *logrus.Entry |  | ||||||
| 	config      Config | 	config      Config | ||||||
|  | 	logger      *logrus.Entry | ||||||
| 	etcdHost    string | 	etcdHost    string | ||||||
| 	dockerImage string | 	dockerImage string | ||||||
| 	cluster     *spec.Postgresql | 	cluster     *spec.Postgresql | ||||||
|  | @ -71,6 +73,37 @@ func New(cfg Config, spec *spec.Postgresql) *Cluster { | ||||||
| 	return cluster | 	return cluster | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *Cluster) getReadonlyToken() (string, error) { | ||||||
|  | 	credentialsSecret, err := c.config.KubeClient.Secrets(c.config.Namespace).Get("postgresql-operator") | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", fmt.Errorf("Can't get credentials secret: %s", err) | ||||||
|  | 	} | ||||||
|  | 	data := credentialsSecret.Data | ||||||
|  | 
 | ||||||
|  | 	if string(data["read-only-token-type"]) != "Bearer" { | ||||||
|  | 		return "", fmt.Errorf("Wrong token type: %s", data["read-only-token-type"]) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return string(data["read-only-token-secret"]), nil | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Cluster) getTeamMembers() ([]string, error) { | ||||||
|  | 	token, err := c.getReadonlyToken() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("Can't get oauth token: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.config.TeamsAPIClient.OauthToken = token | ||||||
|  | 	teamInfo, err := c.config.TeamsAPIClient.TeamInfo((*c.cluster.Spec).TeamId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("Can't get team info: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return teamInfo.Members, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *Cluster) labelsSet() labels.Set { | func (c *Cluster) labelsSet() labels.Set { | ||||||
| 	return labels.Set{ | 	return labels.Set{ | ||||||
| 		"application":   "spilo", | 		"application":   "spilo", | ||||||
|  | @ -78,10 +111,10 @@ func (c *Cluster) labelsSet() labels.Set { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) credentialSecretName(userName string) string { | func (c *Cluster) credentialSecretName(username string) string { | ||||||
| 	return fmt.Sprintf( | 	return fmt.Sprintf( | ||||||
| 		"%s.%s.credentials.%s.%s", | 		"%s.%s.credentials.%s.%s", | ||||||
| 		userName, | 		username, | ||||||
| 		(*c.cluster).Metadata.Name, | 		(*c.cluster).Metadata.Name, | ||||||
| 		constants.TPRName, | 		constants.TPRName, | ||||||
| 		constants.TPRVendor) | 		constants.TPRVendor) | ||||||
|  | @ -91,7 +124,7 @@ func isValidUsername(username string) bool { | ||||||
| 	return alphaNumericRegexp.MatchString(username) | 	return alphaNumericRegexp.MatchString(username) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func validateUserFlags(userFlags []string) (flags []string, err error) { | func normalizeUserFlags(userFlags []string) (flags []string, err error) { | ||||||
| 	uniqueFlags := make(map[string]bool) | 	uniqueFlags := make(map[string]bool) | ||||||
| 
 | 
 | ||||||
| 	for _, flag := range userFlags { | 	for _, flag := range userFlags { | ||||||
|  | @ -106,10 +139,6 @@ func validateUserFlags(userFlags []string) (flags []string, err error) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if _, ok := uniqueFlags["NOLOGIN"]; !ok { |  | ||||||
| 		uniqueFlags["LOGIN"] = true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	flags = []string{} | 	flags = []string{} | ||||||
| 	for k := range uniqueFlags { | 	for k := range uniqueFlags { | ||||||
| 		flags = append(flags, k) | 		flags = append(flags, k) | ||||||
|  | @ -122,8 +151,8 @@ func (c *Cluster) init() { | ||||||
| 	users := (*c.cluster.Spec).Users | 	users := (*c.cluster.Spec).Users | ||||||
| 	c.pgUsers = make(map[string]pgUser, len(users)+2) // + [superuser and replication]
 | 	c.pgUsers = make(map[string]pgUser, len(users)+2) // + [superuser and replication]
 | ||||||
| 
 | 
 | ||||||
| 	c.pgUsers[superUsername] = pgUser{ | 	c.pgUsers[superuserName] = pgUser{ | ||||||
| 		name:     superUsername, | 		name:     superuserName, | ||||||
| 		password: util.RandomPassword(constants.PasswordLength), | 		password: util.RandomPassword(constants.PasswordLength), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -132,27 +161,35 @@ func (c *Cluster) init() { | ||||||
| 		password: util.RandomPassword(constants.PasswordLength), | 		password: util.RandomPassword(constants.PasswordLength), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for userName, userFlags := range users { | 	for username, userFlags := range users { | ||||||
| 		if !isValidUsername(userName) { | 		if !isValidUsername(username) { | ||||||
| 			c.logger.Warningf("Invalid '%s' username", userName) | 			c.logger.Warningf("Invalid username: '%s'", username) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		flags, err := validateUserFlags(userFlags) | 		flags, err := normalizeUserFlags(userFlags) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			c.logger.Warningf("Invalid flags for user '%s': %s", userName, err) | 			c.logger.Warningf("Invalid flags for user '%s': %s", username, err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		c.pgUsers[userName] = pgUser{ | 		c.pgUsers[username] = pgUser{ | ||||||
| 			name:     userName, | 			name:     username, | ||||||
| 			password: util.RandomPassword(constants.PasswordLength), | 			password: util.RandomPassword(constants.PasswordLength), | ||||||
| 			flags:    flags, | 			flags:    flags, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	teamMembers, err := c.getTeamMembers() | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.logger.Errorf("Can't get list of team members: %s", err) | ||||||
|  | 	} else { | ||||||
|  | 		for _, username := range teamMembers { | ||||||
|  | 			c.pgUsers[username] = pgUser{name: username} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | func (c *Cluster) waitPodDelete() error { | ||||||
| func (c *Cluster) waitPodsDestroy() error { |  | ||||||
| 	ls := c.labelsSet() | 	ls := c.labelsSet() | ||||||
| 
 | 
 | ||||||
| 	listOptions := v1.ListOptions{ | 	listOptions := v1.ListOptions{ | ||||||
|  | @ -227,7 +264,7 @@ func (c *Cluster) waitPodLabelsReady() error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) Create() error { | func (c *Cluster) Create() error { | ||||||
| 	c.createEndPoint() | 	c.createEndpoint() | ||||||
| 	c.createService() | 	c.createService() | ||||||
| 	c.applySecrets() | 	c.applySecrets() | ||||||
| 	c.createStatefulSet() | 	c.createStatefulSet() | ||||||
|  | @ -268,7 +305,7 @@ func (c *Cluster) waitClusterReady() error { | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) Delete() error { | func (c *Cluster) Delete() error { | ||||||
| 	clusterName := (*c.cluster).Metadata.Name | 	clusterName := (*c.cluster).Metadata.Name | ||||||
| 	nameSpace := c.config.Namespace | 	namespace := c.config.Namespace | ||||||
| 	orphanDependents := false | 	orphanDependents := false | ||||||
| 	deleteOptions := &v1.DeleteOptions{ | 	deleteOptions := &v1.DeleteOptions{ | ||||||
| 		OrphanDependents: &orphanDependents, | 		OrphanDependents: &orphanDependents, | ||||||
|  | @ -280,54 +317,54 @@ func (c *Cluster) Delete() error { | ||||||
| 
 | 
 | ||||||
| 	kubeClient := c.config.KubeClient | 	kubeClient := c.config.KubeClient | ||||||
| 
 | 
 | ||||||
| 	podList, err := kubeClient.Pods(nameSpace).List(listOptions) | 	podList, err := kubeClient.Pods(namespace).List(listOptions) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("Can't get list of pods: %s", err) | 		return fmt.Errorf("Can't get list of pods: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = kubeClient.StatefulSets(nameSpace).Delete(clusterName, deleteOptions) | 	err = kubeClient.StatefulSets(namespace).Delete(clusterName, deleteOptions) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("Can't delete statefulset: %s", err) | 		return fmt.Errorf("Can't delete statefulset: %s", err) | ||||||
| 	} | 	} | ||||||
| 	c.logger.Infof("StatefulSet %s.%s has been deleted", nameSpace, clusterName) | 	c.logger.Infof("Statefulset '%s' has been deleted", util.FullObjectName(namespace, clusterName)) | ||||||
| 
 | 
 | ||||||
| 	for _, pod := range podList.Items { | 	for _, pod := range podList.Items { | ||||||
| 		err = kubeClient.Pods(nameSpace).Delete(pod.Name, deleteOptions) | 		err = kubeClient.Pods(namespace).Delete(pod.Name, deleteOptions) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("Error while deleting pod %s.%s: %s", pod.Name, pod.Namespace, err) | 			return fmt.Errorf("Error while deleting pod '%s': %s", util.FullObjectName(pod.Namespace, pod.Name), err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		c.logger.Infof("Pod %s.%s has been deleted", pod.Name, pod.Namespace) | 		c.logger.Infof("Pod '%s' has been deleted", util.FullObjectName(pod.Namespace, pod.Name)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	serviceList, err := kubeClient.Services(nameSpace).List(listOptions) | 	serviceList, err := kubeClient.Services(namespace).List(listOptions) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("Can't get list of the services: %s", err) | 		return fmt.Errorf("Can't get list of the services: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, service := range serviceList.Items { | 	for _, service := range serviceList.Items { | ||||||
| 		err = kubeClient.Services(nameSpace).Delete(service.Name, deleteOptions) | 		err = kubeClient.Services(namespace).Delete(service.Name, deleteOptions) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("Can't delete service %s.%s: %s", service.Name, service.Namespace, err) | 			return fmt.Errorf("Can't delete service '%s': %s", util.FullObjectName(service.Namespace, service.Name), err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		c.logger.Infof("Service %s.%s has been deleted", service.Name, service.Namespace) | 		c.logger.Infof("Service '%s' has been deleted", util.FullObjectName(service.Namespace, service.Name)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	secretsList, err := kubeClient.Secrets(nameSpace).List(listOptions) | 	secretsList, err := kubeClient.Secrets(namespace).List(listOptions) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	for _, secret := range secretsList.Items { | 	for _, secret := range secretsList.Items { | ||||||
| 		err = kubeClient.Secrets(nameSpace).Delete(secret.Name, deleteOptions) | 		err = kubeClient.Secrets(namespace).Delete(secret.Name, deleteOptions) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("Can't delete secret %s.%s: %s", secret.Name, secret.Namespace, err) | 			return fmt.Errorf("Can't delete secret '%s': %s", util.FullObjectName(secret.Namespace, secret.Name), err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		c.logger.Infof("Secret %s.%s has been deleted", secret.Name, secret.Namespace) | 		c.logger.Infof("Secret '%s' has been deleted", util.FullObjectName(secret.Namespace, secret.Name)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	c.waitPodsDestroy() | 	c.waitPodDelete() | ||||||
| 
 | 
 | ||||||
| 	etcdKey := fmt.Sprintf("/service/%s", clusterName) | 	etcdKey := fmt.Sprintf("/service/%s", clusterName) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,14 +10,16 @@ import ( | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) pgConnectionString() string { | func (c *Cluster) pgConnectionString() string { | ||||||
| 	hostname := fmt.Sprintf("%s.%s.svc.cluster.local", (*c.cluster).Metadata.Name, (*c.cluster).Metadata.Namespace) | 	hostname := fmt.Sprintf("%s.%s.svc.cluster.local", (*c.cluster).Metadata.Name, (*c.cluster).Metadata.Namespace) | ||||||
| 	password := c.pgUsers[superUsername].password | 	password := c.pgUsers[superuserName].password | ||||||
| 
 | 
 | ||||||
| 	return fmt.Sprintf("host='%s' dbname=postgres sslmode=require user=postgres password='%s'", | 	return fmt.Sprintf("host='%s' dbname=postgres sslmode=require user='%s' password='%s'", | ||||||
| 		hostname, | 		hostname, | ||||||
|  | 		superuserName, | ||||||
| 		strings.Replace(password, "$", "\\$", -1)) | 		strings.Replace(password, "$", "\\$", -1)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) initDbConn() error { | func (c *Cluster) initDbConn() error { | ||||||
|  | 	//TODO: concurrent safe?
 | ||||||
| 	if c.pgDb == nil { | 	if c.pgDb == nil { | ||||||
| 		c.mu.Lock() | 		c.mu.Lock() | ||||||
| 		defer c.mu.Unlock() | 		defer c.mu.Unlock() | ||||||
|  |  | ||||||
|  | @ -1,26 +1,23 @@ | ||||||
| package cluster | package cluster | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
| 	"k8s.io/client-go/pkg/api/resource" | 	"k8s.io/client-go/pkg/api/resource" | ||||||
| 	"k8s.io/client-go/pkg/api/v1" | 	"k8s.io/client-go/pkg/api/v1" | ||||||
| 	"k8s.io/client-go/pkg/apis/apps/v1beta1" | 	"k8s.io/client-go/pkg/apis/apps/v1beta1" | ||||||
| 	"k8s.io/client-go/pkg/util/intstr" | 	"k8s.io/client-go/pkg/util/intstr" | ||||||
| 
 | 
 | ||||||
| 	"fmt" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util" | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil" | ||||||
| 	"strings" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var createUserSQL = `DO $$ | var createUserSQL = `DO $$ | ||||||
| BEGIN | BEGIN | ||||||
|     SET local synchronous_commit = 'local'; |     SET LOCAL synchronous_commit = 'local'; | ||||||
|     PERFORM * FROM pg_authid WHERE rolname = '%s'; |     CREATE ROLE "%s" %s PASSWORD %s; | ||||||
|     IF FOUND THEN |  | ||||||
|         ALTER ROLE "%s" WITH %s PASSWORD '%s'; |  | ||||||
|     ELSE |  | ||||||
|         CREATE ROLE "%s" WITH %s PASSWORD '%s'; |  | ||||||
|     END IF; |  | ||||||
| END; | END; | ||||||
| $$` | $$` | ||||||
| 
 | 
 | ||||||
|  | @ -63,7 +60,7 @@ func (c *Cluster) createStatefulSet() { | ||||||
| 			ValueFrom: &v1.EnvVarSource{ | 			ValueFrom: &v1.EnvVarSource{ | ||||||
| 				SecretKeyRef: &v1.SecretKeySelector{ | 				SecretKeyRef: &v1.SecretKeySelector{ | ||||||
| 					LocalObjectReference: v1.LocalObjectReference{ | 					LocalObjectReference: v1.LocalObjectReference{ | ||||||
| 						Name: c.credentialSecretName("superuser"), | 						Name: c.credentialSecretName(superuserName), | ||||||
| 					}, | 					}, | ||||||
| 					Key: "password", | 					Key: "password", | ||||||
| 				}, | 				}, | ||||||
|  | @ -74,7 +71,7 @@ func (c *Cluster) createStatefulSet() { | ||||||
| 			ValueFrom: &v1.EnvVarSource{ | 			ValueFrom: &v1.EnvVarSource{ | ||||||
| 				SecretKeyRef: &v1.SecretKeySelector{ | 				SecretKeyRef: &v1.SecretKeySelector{ | ||||||
| 					LocalObjectReference: v1.LocalObjectReference{ | 					LocalObjectReference: v1.LocalObjectReference{ | ||||||
| 						Name: c.credentialSecretName("replication"), | 						Name: c.credentialSecretName(replicationUsername), | ||||||
| 					}, | 					}, | ||||||
| 					Key: "password", | 					Key: "password", | ||||||
| 				}, | 				}, | ||||||
|  | @ -82,7 +79,7 @@ func (c *Cluster) createStatefulSet() { | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:  "PAM_OAUTH2",               //TODO: get from the operator tpr spec
 | 			Name:  "PAM_OAUTH2",               //TODO: get from the operator tpr spec
 | ||||||
| 			Value: "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees", //space before uid is obligatory
 | 			Value: constants.PamConfiguration, //space before uid is obligatory
 | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name: "SPILO_CONFIGURATION", //TODO: get from the operator tpr spec
 | 			Name: "SPILO_CONFIGURATION", //TODO: get from the operator tpr spec
 | ||||||
|  | @ -93,10 +90,16 @@ bootstrap: | ||||||
|   initdb: |   initdb: | ||||||
|   - auth-host: md5 |   - auth-host: md5 | ||||||
|   - auth-local: trust |   - auth-local: trust | ||||||
|  |   users: | ||||||
|  |     %s: | ||||||
|  |       password: NULL | ||||||
|  |       options: | ||||||
|  |         - createdb | ||||||
|  |         - nologin | ||||||
|   pg_hba: |   pg_hba: | ||||||
|   - hostnossl all all all reject |   - hostnossl all all all reject | ||||||
|   - hostssl   all +%s all pam |   - hostssl   all +%s all pam | ||||||
|   - hostssl   all all all md5`, (*c.cluster.Spec).Version, constants.PamRoleName), |   - hostssl   all all all md5`, (*c.cluster.Spec).Version, constants.PamRoleName, constants.PamRoleName), | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -169,17 +172,24 @@ bootstrap: | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	c.config.KubeClient.StatefulSets(c.config.Namespace).Create(statefulSet) | 	_, err := c.config.KubeClient.StatefulSets(c.config.Namespace).Create(statefulSet) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.logger.Errorf("Can't create statefulset: %s", err) | ||||||
|  | 	} else { | ||||||
|  | 		c.logger.Infof("Statefulset has been created: '%s'", util.FullObjectNameFromMeta(statefulSet.ObjectMeta)) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) applySecrets() { | func (c *Cluster) applySecrets() { | ||||||
| 	//TODO: do not override current secrets
 |  | ||||||
| 
 |  | ||||||
| 	var err error | 	var err error | ||||||
| 	for userName, pgUser := range c.pgUsers { | 	for username, pgUser := range c.pgUsers { | ||||||
|  | 		//Skip users with no password i.e. human users (they'll be authenticated using pam)
 | ||||||
|  | 		if pgUser.password == "" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 		secret := v1.Secret{ | 		secret := v1.Secret{ | ||||||
| 			ObjectMeta: v1.ObjectMeta{ | 			ObjectMeta: v1.ObjectMeta{ | ||||||
| 				Name:   c.credentialSecretName(userName), | 				Name:   c.credentialSecretName(username), | ||||||
| 				Labels: c.labelsSet(), | 				Labels: c.labelsSet(), | ||||||
| 			}, | 			}, | ||||||
| 			Type: v1.SecretTypeOpaque, | 			Type: v1.SecretTypeOpaque, | ||||||
|  | @ -192,32 +202,24 @@ func (c *Cluster) applySecrets() { | ||||||
| 		if k8sutil.IsKubernetesResourceAlreadyExistError(err) { | 		if k8sutil.IsKubernetesResourceAlreadyExistError(err) { | ||||||
| 			c.logger.Infof("Skipping update of '%s'", secret.Name) | 			c.logger.Infof("Skipping update of '%s'", secret.Name) | ||||||
| 
 | 
 | ||||||
| 			curSecrets, err := c.config.KubeClient.Secrets(c.config.Namespace).Get(c.credentialSecretName(userName)) | 			curSecrets, err := c.config.KubeClient.Secrets(c.config.Namespace).Get(c.credentialSecretName(username)) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				c.logger.Errorf("Can't get current secret: %s", err) | 				c.logger.Errorf("Can't get current secret: %s", err) | ||||||
| 			} | 			} | ||||||
| 			user := pgUser | 			user := pgUser | ||||||
| 			user.password = string(curSecrets.Data["password"]) | 			user.password = string(curSecrets.Data["password"]) | ||||||
| 			c.pgUsers[userName] = user | 			c.pgUsers[username] = user | ||||||
| 			c.logger.Infof("Password fetched for user '%s' from the secrets", userName) | 			c.logger.Infof("Password fetched for user '%s' from the secrets", username) | ||||||
| 
 | 
 | ||||||
| 			continue | 			continue | ||||||
| 			//_, err = c.config.KubeClient.Secrets(c.config.Namespace).Update(&secret)
 |  | ||||||
| 			//if err != nil {
 |  | ||||||
| 			//	c.logger.Errorf("Error while updating secret: %+v", err)
 |  | ||||||
| 			//} else {
 |  | ||||||
| 			//	c.logger.Infof("Secret updated: %+v", secret)
 |  | ||||||
| 			//}
 |  | ||||||
| 		} else { | 		} else { | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				c.logger.Errorf("Error while creating secret: %+v", err) | 				c.logger.Errorf("Error while creating secret: %s", err) | ||||||
| 			} else { | 			} else { | ||||||
| 				c.logger.Infof("Secret created: %s", secret.Name) | 				c.logger.Infof("Secret created: '%s'", util.FullObjectNameFromMeta(secret.ObjectMeta)) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	//TODO: remove secrets of the deleted users
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) createService() { | func (c *Cluster) createService() { | ||||||
|  | @ -245,11 +247,11 @@ func (c *Cluster) createService() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.logger.Errorf("Error while creating service: %+v", err) | 		c.logger.Errorf("Error while creating service: %+v", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		c.logger.Infof("Service created: %s", service.Name) | 		c.logger.Infof("Service created: '%s'", util.FullObjectNameFromMeta(service.ObjectMeta)) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) createEndPoint() { | func (c *Cluster) createEndpoint() { | ||||||
| 	clusterName := (*c.cluster).Metadata.Name | 	clusterName := (*c.cluster).Metadata.Name | ||||||
| 
 | 
 | ||||||
| 	_, err := c.config.KubeClient.Endpoints(c.config.Namespace).Get(clusterName) | 	_, err := c.config.KubeClient.Endpoints(c.config.Namespace).Get(clusterName) | ||||||
|  | @ -258,18 +260,18 @@ func (c *Cluster) createEndPoint() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	endPoint := v1.Endpoints{ | 	endpoint := v1.Endpoints{ | ||||||
| 		ObjectMeta: v1.ObjectMeta{ | 		ObjectMeta: v1.ObjectMeta{ | ||||||
| 			Name:   clusterName, | 			Name:   clusterName, | ||||||
| 			Labels: c.labelsSet(), | 			Labels: c.labelsSet(), | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_, err = c.config.KubeClient.Endpoints(c.config.Namespace).Create(&endPoint) | 	_, err = c.config.KubeClient.Endpoints(c.config.Namespace).Create(&endpoint) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.logger.Errorf("Error while creating endpoint: %+v", err) | 		c.logger.Errorf("Error while creating endpoint: %+v", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		c.logger.Infof("Endpoint created: %s", endPoint.Name) | 		c.logger.Infof("Endpoint created: %s", endpoint.Name) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -279,28 +281,40 @@ func (c *Cluster) createUser(user pgUser) { | ||||||
| 
 | 
 | ||||||
| 	if user.password == "" { | 	if user.password == "" { | ||||||
| 		userType = "human" | 		userType = "human" | ||||||
| 		flags = append(flags, fmt.Sprintf("IN ROLE '%s'", constants.PamRoleName)) | 		flags = append(flags, fmt.Sprintf("IN ROLE \"%s\"", constants.PamRoleName)) | ||||||
| 	} else { | 	} else { | ||||||
| 		userType = "app" | 		userType = "app" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	addLoginFlag := true | ||||||
|  | 	for _, v := range flags { | ||||||
|  | 		if v == "NOLOGIN" { | ||||||
|  | 			addLoginFlag = false | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if addLoginFlag { | ||||||
|  | 		flags = append(flags, "LOGIN") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	userFlags := strings.Join(flags, " ") | 	userFlags := strings.Join(flags, " ") | ||||||
| 	query := fmt.Sprintf(createUserSQL, | 	userPassword := fmt.Sprintf("'%s'", user.password) | ||||||
| 		user.name, | 	if user.password == "" { | ||||||
| 		user.name, userFlags, user.password, | 		userPassword = "NULL" | ||||||
| 		user.name, userFlags, user.password) | 	} | ||||||
|  | 	query := fmt.Sprintf(createUserSQL, user.name, userFlags, userPassword) | ||||||
| 
 | 
 | ||||||
| 	_, err := c.pgDb.Query(query) | 	_, err := c.pgDb.Query(query) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.logger.Errorf("Can't create %s user '%s': %s", user.name, err) | 		c.logger.Errorf("Can't create %s user '%s': %s", user.name, err) | ||||||
| 	} else { | 	} else { | ||||||
| 		c.logger.Infof("%s user '%s' with flags %s has been created", userType, user.name, flags) | 		c.logger.Infof("Created %s user '%s' with %s flags", userType, user.name, flags) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) createUsers() error { | func (c *Cluster) createUsers() error { | ||||||
| 	for userName, user := range c.pgUsers { | 	for username, user := range c.pgUsers { | ||||||
| 		if userName == superUsername || userName == replicationUsername { | 		if username == superuserName || username == replicationUsername { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,8 +15,10 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/cluster" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/cluster" | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/spec" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/spec" | ||||||
|  | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util" | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" | ||||||
| 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil" | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil" | ||||||
|  | 	"github.bus.zalan.do/acid/postgres-operator/pkg/util/teams" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Config struct { | type Config struct { | ||||||
|  | @ -24,11 +26,11 @@ type Config struct { | ||||||
| 	KubeClient     *kubernetes.Clientset | 	KubeClient     *kubernetes.Clientset | ||||||
| 	RestClient     *rest.RESTClient | 	RestClient     *rest.RESTClient | ||||||
| 	EtcdClient     etcdclient.KeysAPI | 	EtcdClient     etcdclient.KeysAPI | ||||||
|  | 	TeamsAPIClient *teams.TeamsAPI | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Controller struct { | type Controller struct { | ||||||
| 	config      Config | 	config      Config | ||||||
| 
 |  | ||||||
| 	logger      *logrus.Entry | 	logger      *logrus.Entry | ||||||
| 	events      chan *Event | 	events      chan *Event | ||||||
| 	clusters    map[string]*cluster.Cluster | 	clusters    map[string]*cluster.Cluster | ||||||
|  | @ -60,7 +62,6 @@ func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) { | ||||||
| 	err := c.initEtcdClient() | 	err := c.initEtcdClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.logger.Errorf("Can't get etcd client: %s", err) | 		c.logger.Errorf("Can't get etcd client: %s", err) | ||||||
| 
 |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -76,9 +77,10 @@ func (c *Controller) watchTpr(stopCh <-chan struct{}) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Controller) createTPR() error { | func (c *Controller) createTPR() error { | ||||||
|  | 	TPRName := fmt.Sprintf("%s.%s", constants.TPRName, constants.TPRVendor) | ||||||
| 	tpr := &v1beta1extensions.ThirdPartyResource{ | 	tpr := &v1beta1extensions.ThirdPartyResource{ | ||||||
| 		ObjectMeta: v1.ObjectMeta{ | 		ObjectMeta: v1.ObjectMeta{ | ||||||
| 			Name: fmt.Sprintf("%s.%s", constants.TPRName, constants.TPRVendor), | 			Name: TPRName, | ||||||
| 		}, | 		}, | ||||||
| 		Versions: []v1beta1extensions.APIVersion{ | 		Versions: []v1beta1extensions.APIVersion{ | ||||||
| 			{Name: constants.TPRApiVersion}, | 			{Name: constants.TPRApiVersion}, | ||||||
|  | @ -87,15 +89,14 @@ func (c *Controller) createTPR() error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_, err := c.config.KubeClient.ExtensionsV1beta1().ThirdPartyResources().Create(tpr) | 	_, err := c.config.KubeClient.ExtensionsV1beta1().ThirdPartyResources().Create(tpr) | ||||||
| 
 |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if !k8sutil.IsKubernetesResourceAlreadyExistError(err) { | 		if !k8sutil.IsKubernetesResourceAlreadyExistError(err) { | ||||||
| 			return err | 			return err | ||||||
| 		} else { | 		} else { | ||||||
| 			c.logger.Info("ThirdPartyResource is already registered") | 			c.logger.Infof("ThirdPartyResource '%s' is already registered", TPRName) | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		c.logger.Info("ThirdPartyResource has been registered") | 		c.logger.Infof("ThirdPartyResource '%s' has been registered", TPRName) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	restClient := c.config.RestClient | 	restClient := c.config.RestClient | ||||||
|  | @ -109,6 +110,7 @@ func (c *Controller) makeClusterConfig() cluster.Config { | ||||||
| 		KubeClient:     c.config.KubeClient, | 		KubeClient:     c.config.KubeClient, | ||||||
| 		RestClient:     c.config.RestClient, | 		RestClient:     c.config.RestClient, | ||||||
| 		EtcdClient:     c.config.EtcdClient, | 		EtcdClient:     c.config.EtcdClient, | ||||||
|  | 		TeamsAPIClient: c.config.TeamsAPIClient, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -150,7 +152,7 @@ func (c *Controller) clusterAdd(obj interface{}) { | ||||||
| 	c.stopChMap[clusterName] = make(chan struct{}) | 	c.stopChMap[clusterName] = make(chan struct{}) | ||||||
| 	c.clusters[clusterName] = cl | 	c.clusters[clusterName] = cl | ||||||
| 
 | 
 | ||||||
| 	c.logger.Infof("Postgresql cluster %s.%s has been created", clusterName, (*pg).Metadata.Namespace) | 	c.logger.Infof("Postgresql cluster '%s' has been created", util.FullObjectNameFromMeta((*pg).Metadata)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Controller) clusterUpdate(prev, cur interface{}) { | func (c *Controller) clusterUpdate(prev, cur interface{}) { | ||||||
|  | @ -177,12 +179,12 @@ func (c *Controller) clusterDelete(obj interface{}) { | ||||||
| 	cluster := cluster.New(c.makeClusterConfig(), pg) | 	cluster := cluster.New(c.makeClusterConfig(), pg) | ||||||
| 	err := cluster.Delete() | 	err := cluster.Delete() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.logger.Errorf("Can't delete cluster '%s.%s': %s", clusterName, (*pg).Metadata.Namespace, err) | 		c.logger.Errorf("Can't delete cluster '%s': %s", util.FullObjectNameFromMeta((*pg).Metadata), err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	close(c.stopChMap[clusterName]) | 	close(c.stopChMap[clusterName]) | ||||||
| 	delete(c.clusters, clusterName) | 	delete(c.clusters, clusterName) | ||||||
| 
 | 
 | ||||||
| 	c.logger.Infof("Cluster delete: %s", (*pg).Metadata.Name) | 	c.logger.Infof("Cluster has been deleted: '%s'", util.FullObjectNameFromMeta((*pg).Metadata)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,9 +3,9 @@ package spec | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 
 | 
 | ||||||
| 	"k8s.io/client-go/pkg/api" |  | ||||||
| 	"k8s.io/client-go/pkg/api/meta" | 	"k8s.io/client-go/pkg/api/meta" | ||||||
| 	"k8s.io/client-go/pkg/api/unversioned" | 	"k8s.io/client-go/pkg/api/unversioned" | ||||||
|  | 	"k8s.io/client-go/pkg/api/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type MaintenanceWindow struct { | type MaintenanceWindow struct { | ||||||
|  | @ -55,7 +55,7 @@ type PostgresSpec struct { | ||||||
| 	NumberOfInstances   int32                `json:"numberOfInstances"` | 	NumberOfInstances   int32                `json:"numberOfInstances"` | ||||||
| 	Users               map[string]UserFlags `json:"users"` | 	Users               map[string]UserFlags `json:"users"` | ||||||
| 	MaintenanceWindows  []string             `json:"maintenanceWindows,omitempty"` | 	MaintenanceWindows  []string             `json:"maintenanceWindows,omitempty"` | ||||||
| 	PamUsersSecret      string               `json:"pamUsersSecret",omitempty` | 	PamUsersSecret      string               `json:"pamUsersSecret,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	EtcdHost    string | 	EtcdHost    string | ||||||
| 	DockerImage string | 	DockerImage string | ||||||
|  | @ -81,7 +81,7 @@ type PostgresStatus struct { | ||||||
| // PostgreSQL Third Party (resource) Object
 | // PostgreSQL Third Party (resource) Object
 | ||||||
| type Postgresql struct { | type Postgresql struct { | ||||||
| 	unversioned.TypeMeta `json:",inline"` | 	unversioned.TypeMeta `json:",inline"` | ||||||
| 	Metadata             api.ObjectMeta `json:"metadata"` | 	Metadata             v1.ObjectMeta `json:"metadata"` | ||||||
| 
 | 
 | ||||||
| 	Spec   *PostgresSpec   `json:"spec"` | 	Spec   *PostgresSpec   `json:"spec"` | ||||||
| 	Status *PostgresStatus `json:"status"` | 	Status *PostgresStatus `json:"status"` | ||||||
|  |  | ||||||
|  | @ -15,9 +15,12 @@ const ( | ||||||
| 	ResourceName = TPRName + "s" | 	ResourceName = TPRName + "s" | ||||||
| 	ResyncPeriod = 5 * time.Minute | 	ResyncPeriod = 5 * time.Minute | ||||||
| 
 | 
 | ||||||
| 	EtcdHost    = "etcd-client.default.svc.cluster.local:2379" //TODO: move to the operator spec
 | 	//TODO: move to the operator spec
 | ||||||
| 	SpiloImage  = "registry.opensource.zalan.do/acid/spilo-9.6:1.2-p11" | 	EtcdHost         = "etcd-client.default.svc.cluster.local:2379" | ||||||
|  | 	SpiloImage       = "registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12" | ||||||
| 	PamRoleName      = "zalandos" | 	PamRoleName      = "zalandos" | ||||||
|  | 	PamConfiguration = "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees" | ||||||
| 
 | 
 | ||||||
| 	PasswordLength = 64 | 	PasswordLength = 64 | ||||||
|  | 	TeamsAPIUrl    = "https://teams.example.com/api/" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,76 @@ | ||||||
|  | package teams | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type InfrastructureAccount struct { | ||||||
|  | 	Id          string `json:"id"` | ||||||
|  | 	Name        string `json:"name"` | ||||||
|  | 	Provider    string `json:"provider"` | ||||||
|  | 	Type        string `json:"type"` | ||||||
|  | 	Description string `json:"description"` | ||||||
|  | 	Owner       string `json:"owner"` | ||||||
|  | 	OwnerDn     string `json:"owner_dn"` | ||||||
|  | 	Disabled    bool   `json:"disabled"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Team struct { | ||||||
|  | 	Dn           string   `json:"dn"` | ||||||
|  | 	Id           string   `json:"id"` | ||||||
|  | 	TeamName     string   `json:"id_name"` | ||||||
|  | 	TeamId       string   `json:"team_id"` | ||||||
|  | 	Type         string   `json:"official"` | ||||||
|  | 	FullName     string   `json:"name"` | ||||||
|  | 	Aliases      []string `json:"alias"` | ||||||
|  | 	Mails        []string `json:"mail"` | ||||||
|  | 	Members      []string `json:"member"` | ||||||
|  | 	CostCenter   string   `json:"cost_center"` | ||||||
|  | 	DeliveryLead string   `json:"delivery_lead"` | ||||||
|  | 	ParentTeamId string   `json:"parent_team_id"` | ||||||
|  | 
 | ||||||
|  | 	InfrastructureAccounts []InfrastructureAccount `json:"infrastructure-accounts"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type TeamsAPI struct { | ||||||
|  | 	url        string | ||||||
|  | 	httpClient *http.Client | ||||||
|  | 	OauthToken string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewTeamsAPI(url string) *TeamsAPI { | ||||||
|  | 	t := TeamsAPI{ | ||||||
|  | 		url:        strings.TrimRight(url, "/"), | ||||||
|  | 		httpClient: &http.Client{}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (t *TeamsAPI) TeamInfo(teamId string) (*Team, error) { | ||||||
|  | 	url := fmt.Sprintf("%s/teams/%s", t.url, teamId) | ||||||
|  | 	req, err := http.NewRequest("GET", url, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req.Header.Add("Authorization", "Bearer "+t.OauthToken) | ||||||
|  | 	resp, err := t.httpClient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	teamInfo := &Team{} | ||||||
|  | 	d := json.NewDecoder(resp.Body) | ||||||
|  | 	err = d.Decode(teamInfo) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return teamInfo, nil | ||||||
|  | } | ||||||
|  | @ -1,8 +1,11 @@ | ||||||
| package util | package util | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"math/rand" | 	"math/rand" | ||||||
| 	"time" | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"k8s.io/client-go/pkg/api/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") | var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") | ||||||
|  | @ -19,3 +22,16 @@ func RandomPassword(n int) string { | ||||||
| 
 | 
 | ||||||
| 	return string(b) | 	return string(b) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func FullObjectNameFromMeta(meta v1.ObjectMeta) string { | ||||||
|  | 	return FullObjectName(meta.Namespace, meta.Name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //TODO: Remove in favour of FullObjectNameFromMeta
 | ||||||
|  | func FullObjectName(ns, name string) string { | ||||||
|  | 	if ns == "" { | ||||||
|  | 		ns = "default" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return fmt.Sprintf("%s / %s", ns, name) | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue