Refactor teams API package
This commit is contained in:
		
							parent
							
								
									f7aaf8863d
								
							
						
					
					
						commit
						1fb05212a9
					
				|  | @ -105,11 +105,41 @@ func (c *Cluster) logVolumeChanges(old, new spec.Volume, reason string) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (c *Cluster) getOAuthToken() (string, error) { | ||||
| 	//TODO: we can move this function to the Controller in case it will be needed there. As for now we use it only in the Cluster
 | ||||
| 	// Temporary getting postgresql-operator secret from the NamespaceDefault
 | ||||
| 	credentialsSecret, err := c.KubeClient. | ||||
| 		Secrets(c.OpConfig.OAuthTokenSecretName.Namespace). | ||||
| 		Get(c.OpConfig.OAuthTokenSecretName.Name) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		c.logger.Debugf("Oauth token secret name: %s", c.OpConfig.OAuthTokenSecretName) | ||||
| 		return "", fmt.Errorf("could not get credentials secret: %v", err) | ||||
| 	} | ||||
| 	data := credentialsSecret.Data | ||||
| 
 | ||||
| 	if string(data["read-only-token-type"]) != "Bearer" { | ||||
| 		return "", fmt.Errorf("wrong token type: %v", data["read-only-token-type"]) | ||||
| 	} | ||||
| 
 | ||||
| 	return string(data["read-only-token-secret"]), nil | ||||
| } | ||||
| 
 | ||||
| func (c *Cluster) getTeamMembers() ([]string, error) { | ||||
| 	if c.Spec.TeamID == "" { | ||||
| 		return nil, fmt.Errorf("no teamId specified") | ||||
| 	} | ||||
| 	teamInfo, err := c.TeamsAPIClient.TeamInfo(c.Spec.TeamID) | ||||
| 	if !c.OpConfig.EnableTeamsAPI { | ||||
| 		c.logger.Debug("Team API is disabled, returning empty list of members") | ||||
| 		return []string{}, nil | ||||
| 	} | ||||
| 
 | ||||
| 	token, err := c.getOAuthToken() | ||||
| 	if err != nil { | ||||
| 		return []string{}, fmt.Errorf("could not get oauth token: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	teamInfo, err := c.TeamsAPIClient.TeamInfo(c.Spec.TeamID, token) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("could not get team info: %v", err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -47,7 +47,8 @@ func New(controllerConfig *Config, operatorConfig *config.Config) *Controller { | |||
| 		logger.Level = logrus.DebugLevel | ||||
| 	} | ||||
| 
 | ||||
| 	controllerConfig.TeamsAPIClient = teams.NewTeamsAPI(operatorConfig.TeamsAPIUrl, logger, operatorConfig.EnableTeamsAPI) | ||||
| 	controllerConfig.TeamsAPIClient = teams.NewTeamsAPI(operatorConfig.TeamsAPIUrl, logger) | ||||
| 
 | ||||
| 	return &Controller{ | ||||
| 		Config:   *controllerConfig, | ||||
| 		opConfig: operatorConfig, | ||||
|  | @ -78,7 +79,6 @@ func (c *Controller) initController() { | |||
| 		c.logger.Fatalf("could not register ThirdPartyResource: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	c.TeamsAPIClient.RefreshTokenAction = c.getOAuthToken | ||||
| 	if infraRoles, err := c.getInfrastructureRoles(); err != nil { | ||||
| 		c.logger.Warningf("could not get infrastructure roles: %v", err) | ||||
| 	} else { | ||||
|  |  | |||
|  | @ -29,25 +29,6 @@ func (c *Controller) makeClusterConfig() cluster.Config { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (c *Controller) getOAuthToken() (string, error) { | ||||
| 	// Temporary getting postgresql-operator secret from the NamespaceDefault
 | ||||
| 	credentialsSecret, err := c.KubeClient. | ||||
| 		Secrets(c.opConfig.OAuthTokenSecretName.Namespace). | ||||
| 		Get(c.opConfig.OAuthTokenSecretName.Name) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		c.logger.Debugf("Oauth token secret name: %s", c.opConfig.OAuthTokenSecretName) | ||||
| 		return "", fmt.Errorf("could not get credentials secret: %v", err) | ||||
| 	} | ||||
| 	data := credentialsSecret.Data | ||||
| 
 | ||||
| 	if string(data["read-only-token-type"]) != "Bearer" { | ||||
| 		return "", fmt.Errorf("wrong token type: %v", data["read-only-token-type"]) | ||||
| 	} | ||||
| 
 | ||||
| 	return string(data["read-only-token-secret"]), nil | ||||
| } | ||||
| 
 | ||||
| func thirdPartyResource(TPRName string) *extv1beta.ThirdPartyResource { | ||||
| 	return &extv1beta.ThirdPartyResource{ | ||||
| 		ObjectMeta: v1.ObjectMeta{ | ||||
|  |  | |||
|  | @ -41,31 +41,19 @@ type API struct { | |||
| 	url        string | ||||
| 	httpClient *http.Client | ||||
| 	logger     *logrus.Entry | ||||
| 	RefreshTokenAction func() (string, error) | ||||
| 	enabled            bool | ||||
| } | ||||
| 
 | ||||
| func NewTeamsAPI(url string, log *logrus.Logger, enabled bool) *API { | ||||
| func NewTeamsAPI(url string, log *logrus.Logger) *API { | ||||
| 	t := API{ | ||||
| 		url:        strings.TrimRight(url, "/"), | ||||
| 		httpClient: &http.Client{}, | ||||
| 		logger:     log.WithField("pkg", "teamsapi"), | ||||
| 		enabled:    enabled, | ||||
| 	} | ||||
| 
 | ||||
| 	return &t | ||||
| } | ||||
| 
 | ||||
| func (t *API) TeamInfo(teamID string) (*Team, error) { | ||||
| 	// TODO: avoid getting a new token on every call to the Teams API.
 | ||||
| 	if !t.enabled { | ||||
| 		t.logger.Debug("Team API is disabled, returning empty list of members") | ||||
| 		return &Team{}, nil | ||||
| 	} | ||||
| 	token, err := t.RefreshTokenAction() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| func (t *API) TeamInfo(teamID, token string) (*Team, error) { | ||||
| 	url := fmt.Sprintf("%s/teams/%s", t.url, teamID) | ||||
| 	t.logger.Debugf("Request url: %s", url) | ||||
| 	req, err := http.NewRequest("GET", url, nil) | ||||
|  | @ -84,7 +72,7 @@ func (t *API) TeamInfo(teamID string) (*Team, error) { | |||
| 		d := json.NewDecoder(resp.Body) | ||||
| 		err = d.Decode(&raw) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 			return nil, fmt.Errorf("team API query failed with status code %d and malformed response: %v", resp.StatusCode, err) | ||||
| 		} | ||||
| 
 | ||||
| 		if errMessage, ok := raw["error"]; ok { | ||||
|  | @ -97,7 +85,7 @@ func (t *API) TeamInfo(teamID string) (*Team, error) { | |||
| 	d := json.NewDecoder(resp.Body) | ||||
| 	err = d.Decode(teamInfo) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return nil, fmt.Errorf("could not parse team API response: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return teamInfo, nil | ||||
|  |  | |||
|  | @ -0,0 +1,182 @@ | |||
| package teams | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/Sirupsen/logrus" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	logger = logrus.New() | ||||
| 	token  = "ec45b1cfbe7100c6315d183a3eb6cec0M2U1LWJkMzEtZDgzNzNmZGQyNGM3IiwiYXV0aF90aW1lIjoxNDkzNzMwNzQ1LCJpc3MiOiJodHRwcz" | ||||
| ) | ||||
| 
 | ||||
| var teamsAPItc = []struct { | ||||
| 	in     string | ||||
| 	inCode int | ||||
| 	out    *Team | ||||
| 	err    error | ||||
| }{ | ||||
| 	{`{ | ||||
| "dn": "cn=100100,ou=official,ou=foobar,dc=zalando,dc=net", | ||||
| "id": "acid", | ||||
| "id_name": "ACID", | ||||
| "team_id": "111222", | ||||
| "type": "official", | ||||
| "name": "Acid team name", | ||||
| "mail": [ | ||||
| "email1@example.com", | ||||
| "email2@example.com" | ||||
| ], | ||||
| "alias": [ | ||||
| "acid" | ||||
| ], | ||||
| "member": [ | ||||
|   "member1", | ||||
|   "member2", | ||||
|   "member3" | ||||
| ], | ||||
| "infrastructure-accounts": [ | ||||
| { | ||||
|   "id": "1234512345", | ||||
|   "name": "acid", | ||||
|   "provider": "aws", | ||||
|   "type": "aws", | ||||
|   "description": "", | ||||
|   "owner": "acid", | ||||
|   "owner_dn": "cn=100100,ou=official,ou=foobar,dc=zalando,dc=net", | ||||
|   "disabled": false | ||||
| }, | ||||
| { | ||||
|   "id": "5432154321", | ||||
|   "name": "db", | ||||
|   "provider": "aws", | ||||
|   "type": "aws", | ||||
|   "description": "", | ||||
|   "owner": "acid", | ||||
|   "owner_dn": "cn=100100,ou=official,ou=foobar,dc=zalando,dc=net", | ||||
|   "disabled": false | ||||
| } | ||||
| ], | ||||
| "cost_center": "00099999", | ||||
| "delivery_lead": "member4", | ||||
| "parent_team_id": "111221" | ||||
| }`, | ||||
| 		200, | ||||
| 		&Team{ | ||||
| 			Dn:           "cn=100100,ou=official,ou=foobar,dc=zalando,dc=net", | ||||
| 			ID:           "acid", | ||||
| 			TeamName:     "ACID", | ||||
| 			TeamID:       "111222", | ||||
| 			Type:         "official", | ||||
| 			FullName:     "Acid team name", | ||||
| 			Aliases:      []string{"acid"}, | ||||
| 			Mails:        []string{"email1@example.com", "email2@example.com"}, | ||||
| 			Members:      []string{"member1", "member2", "member3"}, | ||||
| 			CostCenter:   "00099999", | ||||
| 			DeliveryLead: "member4", | ||||
| 			ParentTeamID: "111221", | ||||
| 			InfrastructureAccounts: []InfrastructureAccount{ | ||||
| 				{ | ||||
| 					ID:          "1234512345", | ||||
| 					Name:        "acid", | ||||
| 					Provider:    "aws", | ||||
| 					Type:        "aws", | ||||
| 					Description: "", | ||||
| 					Owner:       "acid", | ||||
| 					OwnerDn:     "cn=100100,ou=official,ou=foobar,dc=zalando,dc=net", | ||||
| 					Disabled:    false}, | ||||
| 				{ | ||||
| 					ID:          "5432154321", | ||||
| 					Name:        "db", | ||||
| 					Provider:    "aws", | ||||
| 					Type:        "aws", | ||||
| 					Description: "", | ||||
| 					Owner:       "acid", | ||||
| 					OwnerDn:     "cn=100100,ou=official,ou=foobar,dc=zalando,dc=net", | ||||
| 					Disabled:    false}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		nil}, { | ||||
| 		`{"error": "Access Token not valid"}`, | ||||
| 		401, | ||||
| 		nil, | ||||
| 		fmt.Errorf(`team API query failed with status code 401 and message: '"Access Token not valid"'`), | ||||
| 	}, | ||||
| 	{ | ||||
| 		`{"status": "I'm a teapot'"}`, | ||||
| 		418, | ||||
| 		nil, | ||||
| 		fmt.Errorf(`team API query failed with status code 418`), | ||||
| 	}, | ||||
| 	{ | ||||
| 		`{"status": "I'm a teapot`, | ||||
| 		418, | ||||
| 		nil, | ||||
| 		fmt.Errorf(`team API query failed with status code 418 and malformed response: unexpected EOF`), | ||||
| 	}, | ||||
| 	{ | ||||
| 		`{"status": "I'm a teapot`, | ||||
| 		200, | ||||
| 		nil, | ||||
| 		fmt.Errorf(`could not parse team API response: unexpected EOF`), | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| var requestsURLtc = []struct { | ||||
| 	url string | ||||
| 	err error | ||||
| }{ | ||||
| 	{ | ||||
| 		"coffee://localhost/", | ||||
| 		fmt.Errorf(`Get coffee://localhost/teams/acid: unsupported protocol scheme "coffee"`), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"http://192.168.0.%31/", | ||||
| 		fmt.Errorf(`parse http://192.168.0.%%31/teams/acid: invalid URL escape "%%31"`), | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| func TestInfo(t *testing.T) { | ||||
| 	for _, tc := range teamsAPItc { | ||||
| 		func() { | ||||
| 			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 				if r.Header.Get("Authorization") != "Bearer " + token { | ||||
| 					t.Errorf("Authorization token is wrong or not provided") | ||||
| 				} | ||||
| 				w.WriteHeader(tc.inCode) | ||||
| 				fmt.Fprint(w, tc.in) | ||||
| 			})) | ||||
| 			defer ts.Close() | ||||
| 			api := NewTeamsAPI(ts.URL, logger) | ||||
| 
 | ||||
| 			actual, err := api.TeamInfo("acid", token) | ||||
| 			if err != nil && err.Error() != tc.err.Error() { | ||||
| 				t.Errorf("Expected error: %v, got: %v", tc.err, err) | ||||
| 				return | ||||
| 			} | ||||
| 
 | ||||
| 			if !reflect.DeepEqual(actual, tc.out) { | ||||
| 				t.Errorf("Expected %#v, got: %#v", tc.out, actual) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestRequest(t *testing.T) { | ||||
| 	for _, tc := range requestsURLtc { | ||||
| 		api := NewTeamsAPI(tc.url, logger) | ||||
| 		resp, err := api.TeamInfo("acid", token) | ||||
| 		if resp != nil { | ||||
| 			t.Errorf("Response expected to be nil") | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if err.Error() != tc.err.Error() { | ||||
| 			t.Errorf("Expected error: %v, got: %v", tc.err, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in New Issue