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) { | func (c *Cluster) getTeamMembers() ([]string, error) { | ||||||
| 	if c.Spec.TeamID == "" { | 	if c.Spec.TeamID == "" { | ||||||
| 		return nil, fmt.Errorf("no teamId specified") | 		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 { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("could not get team info: %v", err) | 		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 | 		logger.Level = logrus.DebugLevel | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	controllerConfig.TeamsAPIClient = teams.NewTeamsAPI(operatorConfig.TeamsAPIUrl, logger, operatorConfig.EnableTeamsAPI) | 	controllerConfig.TeamsAPIClient = teams.NewTeamsAPI(operatorConfig.TeamsAPIUrl, logger) | ||||||
|  | 
 | ||||||
| 	return &Controller{ | 	return &Controller{ | ||||||
| 		Config:   *controllerConfig, | 		Config:   *controllerConfig, | ||||||
| 		opConfig: operatorConfig, | 		opConfig: operatorConfig, | ||||||
|  | @ -78,7 +79,6 @@ func (c *Controller) initController() { | ||||||
| 		c.logger.Fatalf("could not register ThirdPartyResource: %v", err) | 		c.logger.Fatalf("could not register ThirdPartyResource: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	c.TeamsAPIClient.RefreshTokenAction = c.getOAuthToken |  | ||||||
| 	if infraRoles, err := c.getInfrastructureRoles(); err != nil { | 	if infraRoles, err := c.getInfrastructureRoles(); err != nil { | ||||||
| 		c.logger.Warningf("could not get infrastructure roles: %v", err) | 		c.logger.Warningf("could not get infrastructure roles: %v", err) | ||||||
| 	} else { | 	} 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 { | func thirdPartyResource(TPRName string) *extv1beta.ThirdPartyResource { | ||||||
| 	return &extv1beta.ThirdPartyResource{ | 	return &extv1beta.ThirdPartyResource{ | ||||||
| 		ObjectMeta: v1.ObjectMeta{ | 		ObjectMeta: v1.ObjectMeta{ | ||||||
|  |  | ||||||
|  | @ -41,31 +41,19 @@ type API struct { | ||||||
| 	url        string | 	url        string | ||||||
| 	httpClient *http.Client | 	httpClient *http.Client | ||||||
| 	logger     *logrus.Entry | 	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{ | 	t := API{ | ||||||
| 		url:        strings.TrimRight(url, "/"), | 		url:        strings.TrimRight(url, "/"), | ||||||
| 		httpClient: &http.Client{}, | 		httpClient: &http.Client{}, | ||||||
| 		logger:     log.WithField("pkg", "teamsapi"), | 		logger:     log.WithField("pkg", "teamsapi"), | ||||||
| 		enabled:    enabled, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &t | 	return &t | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (t *API) TeamInfo(teamID string) (*Team, error) { | func (t *API) TeamInfo(teamID, token 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 |  | ||||||
| 	} |  | ||||||
| 	url := fmt.Sprintf("%s/teams/%s", t.url, teamID) | 	url := fmt.Sprintf("%s/teams/%s", t.url, teamID) | ||||||
| 	t.logger.Debugf("Request url: %s", url) | 	t.logger.Debugf("Request url: %s", url) | ||||||
| 	req, err := http.NewRequest("GET", url, nil) | 	req, err := http.NewRequest("GET", url, nil) | ||||||
|  | @ -84,7 +72,7 @@ func (t *API) TeamInfo(teamID string) (*Team, error) { | ||||||
| 		d := json.NewDecoder(resp.Body) | 		d := json.NewDecoder(resp.Body) | ||||||
| 		err = d.Decode(&raw) | 		err = d.Decode(&raw) | ||||||
| 		if err != nil { | 		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 { | 		if errMessage, ok := raw["error"]; ok { | ||||||
|  | @ -97,7 +85,7 @@ func (t *API) TeamInfo(teamID string) (*Team, error) { | ||||||
| 	d := json.NewDecoder(resp.Body) | 	d := json.NewDecoder(resp.Body) | ||||||
| 	err = d.Decode(teamInfo) | 	err = d.Decode(teamInfo) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, fmt.Errorf("could not parse team API response: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return teamInfo, nil | 	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