diff --git a/pkg/cluster/util.go b/pkg/cluster/util.go index c8a3ee18d..2302e1d47 100644 --- a/pkg/cluster/util.go +++ b/pkg/cluster/util.go @@ -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) } diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 0f74774ae..b4801d28c 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -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 { diff --git a/pkg/controller/util.go b/pkg/controller/util.go index f33f7ca7e..fcf15ec06 100644 --- a/pkg/controller/util.go +++ b/pkg/controller/util.go @@ -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{ diff --git a/pkg/util/teams/teams.go b/pkg/util/teams/teams.go index 993db8178..e2309065e 100644 --- a/pkg/util/teams/teams.go +++ b/pkg/util/teams/teams.go @@ -38,34 +38,22 @@ type Team struct { } type API struct { - url string - httpClient *http.Client - logger *logrus.Entry - RefreshTokenAction func() (string, error) - enabled bool + url string + httpClient *http.Client + logger *logrus.Entry } -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 diff --git a/pkg/util/teams/teams_test.go b/pkg/util/teams/teams_test.go new file mode 100644 index 000000000..0b95524fb --- /dev/null +++ b/pkg/util/teams/teams_test.go @@ -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) + } + } +}