teams discovery API
This commit is contained in:
parent
22f990a709
commit
e3256869f8
@ -71,6 +71,7 @@ github.com/aws/aws-sdk-go v1.44.301/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8
|
||||
github.com/aws/aws-xray-sdk-go v1.8.1 h1:O4pXV+hnCskaamGsZnFpzHyAmgPGusBMN6i7nnsy0Fo=
|
||||
github.com/aws/aws-xray-sdk-go v1.8.1/go.mod h1:wMmVYzej3sykAttNBkXQHK/+clAPWTOrPiajEk7Cp3A=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@ -123,6 +124,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@ -225,6 +227,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@ -232,6 +235,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||
@ -306,6 +310,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
|
||||
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
@ -440,6 +445,7 @@ go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAf
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
@ -790,6 +796,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Code generated by "stringer -type=opStatus -linecomment"; DO NOT EDIT.
|
||||
// Code generated by "stringer -type=OpStatus -linecomment"; DO NOT EDIT.
|
||||
|
||||
package operations
|
||||
|
||||
@ -15,13 +15,13 @@ func _() {
|
||||
_ = x[NoData-4]
|
||||
}
|
||||
|
||||
const _opStatus_name = "Status UnknownIn ProgressCompletedFailedNo Data"
|
||||
const _OpStatus_name = "Status UnknownIn ProgressCompletedFailedNo Data"
|
||||
|
||||
var _opStatus_index = [...]uint8{0, 14, 25, 34, 40, 47}
|
||||
var _OpStatus_index = [...]uint8{0, 14, 25, 34, 40, 47}
|
||||
|
||||
func (i OpStatus) String() string {
|
||||
if i < 0 || i >= OpStatus(len(_opStatus_index)-1) {
|
||||
return "opStatus(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
if i < 0 || i >= OpStatus(len(_OpStatus_index)-1) {
|
||||
return "OpStatus(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _opStatus_name[_opStatus_index[i]:_opStatus_index[i+1]]
|
||||
return _OpStatus_name[_OpStatus_index[i]:_OpStatus_index[i+1]]
|
||||
}
|
||||
|
||||
@ -30,6 +30,8 @@ const (
|
||||
ExchangeMetadataService // exchangeMetadata
|
||||
OneDriveMetadataService // onedriveMetadata
|
||||
SharePointMetadataService // sharepointMetadata
|
||||
TeamsService // teams
|
||||
TeamsMetadataService // teamsMetadata
|
||||
)
|
||||
|
||||
func toServiceType(service string) ServiceType {
|
||||
|
||||
55
src/pkg/services/m365/api/team_test.go
Normal file
55
src/pkg/services/m365/api/team_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
"github.com/alcionai/corso/src/internal/tester"
|
||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||
"github.com/alcionai/corso/src/pkg/fault"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type TeamsUnitSuite struct {
|
||||
tester.Suite
|
||||
}
|
||||
|
||||
func TestTeamsUnitSuite(t *testing.T) {
|
||||
suite.Run(t, &TeamsUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||
}
|
||||
|
||||
type TeamsIntgSuite struct {
|
||||
tester.Suite
|
||||
its intgTesterSetup
|
||||
}
|
||||
|
||||
func TestTeamsIntgSuite(t *testing.T) {
|
||||
suite.Run(t, &TeamsIntgSuite{
|
||||
Suite: tester.NewIntegrationSuite(
|
||||
t,
|
||||
[][]string{tconfig.M365AcctCredEnvs}),
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *TeamsIntgSuite) SetupSuite() {
|
||||
suite.its = newIntegrationTesterSetup(suite.T())
|
||||
}
|
||||
|
||||
func (suite *TeamsIntgSuite) TestGetAll() {
|
||||
t := suite.T()
|
||||
|
||||
ctx, flush := tester.NewContext(t)
|
||||
defer flush()
|
||||
|
||||
teams, err := suite.its.ac.
|
||||
Groups().
|
||||
GetAll(ctx, true, fault.New(true))
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, len(teams), "must have at least one team")
|
||||
|
||||
for _, team := range teams {
|
||||
assert.NotEmpty(t, ptr.Val(team.GetDisplayName()), "must not return onedrive teams")
|
||||
}
|
||||
}
|
||||
161
src/pkg/services/m365/api/teams.go
Normal file
161
src/pkg/services/m365/api/teams.go
Normal file
@ -0,0 +1,161 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/alcionai/corso/src/internal/common/str"
|
||||
"github.com/alcionai/corso/src/internal/common/tform"
|
||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||
"github.com/alcionai/corso/src/pkg/fault"
|
||||
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
)
|
||||
|
||||
const teamService = "Team"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// controller
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (c Client) Groups() Groups {
|
||||
return Groups{c}
|
||||
}
|
||||
|
||||
// On creation of each Microsoft Teams a corrsponding group gets created from them.
|
||||
// Most of the information like events, drive and mail info will be fetched directly
|
||||
// from groups. So we pull in group and process only the once which are associated with
|
||||
// a team for further proccessing of teams.
|
||||
|
||||
// Teams is an interface-compliant provider of the client.
|
||||
type Groups struct {
|
||||
Client
|
||||
}
|
||||
|
||||
// GetAll retrieves all groups.
|
||||
func (c Groups) GetAll(
|
||||
ctx context.Context,
|
||||
filterTeams bool,
|
||||
errs *fault.Bus,
|
||||
) ([]models.Groupable, error) {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return getGroups(ctx, filterTeams, errs, service)
|
||||
}
|
||||
|
||||
// GetAll retrieves all groups.
|
||||
func getGroups(
|
||||
ctx context.Context,
|
||||
filterTeams bool,
|
||||
errs *fault.Bus,
|
||||
service graph.Servicer,
|
||||
) ([]models.Groupable, error) {
|
||||
|
||||
resp, err := service.Client().Groups().Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "getting all groups")
|
||||
}
|
||||
|
||||
iter, err := msgraphgocore.NewPageIterator[models.Groupable](
|
||||
resp,
|
||||
service.Adapter(),
|
||||
models.CreateTeamCollectionResponseFromDiscriminatorValue)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "creating groups iterator")
|
||||
}
|
||||
|
||||
var (
|
||||
groups = make([]models.Groupable, 0)
|
||||
el = errs.Local()
|
||||
)
|
||||
|
||||
iterator := func(item models.Groupable) bool {
|
||||
if el.Failure() != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
err := ValidateGroup(item)
|
||||
if err != nil {
|
||||
el.AddRecoverable(ctx, graph.Wrap(ctx, err, "validating groups"))
|
||||
} else {
|
||||
isTeam := IsTeam(item)
|
||||
if !filterTeams || isTeam {
|
||||
groups = append(groups, item)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if err := iter.Iterate(ctx, iterator); err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "iterating all groups")
|
||||
}
|
||||
|
||||
return groups, el.Failure()
|
||||
}
|
||||
|
||||
func IsTeam(g models.Groupable) bool {
|
||||
if g.GetAdditionalData()["resourceProvisioningOptions"] != nil {
|
||||
val, _ := tform.AnyValueToT[[]any]("resourceProvisioningOptions", g.GetAdditionalData())
|
||||
for _, v := range val {
|
||||
s, err := str.AnyToString(v)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if s == teamService {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetID retrieves team by groupID/teamID.
|
||||
func (c Groups) GetByID(
|
||||
ctx context.Context,
|
||||
identifier string,
|
||||
) (models.Groupable, error) {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := service.Client().Groups().ByGroupId(identifier).Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "getting group by ID")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := graph.Wrap(ctx, err, "getting teams by id")
|
||||
|
||||
// TODO: check if its applicable here
|
||||
if graph.IsErrItemNotFound(err) {
|
||||
err = clues.Stack(graph.ErrResourceOwnerNotFound, err)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ValidateGroup ensures the item is a Groupable, and contains the necessary
|
||||
// identifiers that we handle with all groups.
|
||||
func ValidateGroup(item models.Groupable) error {
|
||||
if item.GetId() == nil {
|
||||
return clues.New("missing ID")
|
||||
}
|
||||
|
||||
if item.GetDisplayName() == nil {
|
||||
return clues.New("missing principalName")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -312,6 +312,86 @@ func SitesMap(
|
||||
return idname.NewCache(itn), nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Teams
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Team is the minimal information required to identify and display a M365 Team.
|
||||
type Team struct {
|
||||
ID string
|
||||
|
||||
// DisplayName is the human-readable name of the team. Normally the plaintext name that the
|
||||
// user provided when they created the team or the updated name if it was changed.
|
||||
// Ex: displayName: "Testing Team"
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
// TeamsCompat returns a list of teams in the specified M365 tenant.
|
||||
func TeamsCompat(ctx context.Context, acct account.Account) ([]*Team, error) {
|
||||
errs := fault.New(true)
|
||||
|
||||
us, err := Teams(ctx, acct, errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return us, errs.Failure()
|
||||
}
|
||||
|
||||
// Teams returns a list of teams in the specified M365 tenant
|
||||
func Teams(ctx context.Context, acct account.Account, errs *fault.Bus) ([]*Team, error) {
|
||||
ac, err := makeAC(ctx, acct, path.TeamsService)
|
||||
if err != nil {
|
||||
return nil, clues.Stack(err).WithClues(ctx)
|
||||
}
|
||||
|
||||
return getAllTeams(ctx, ac.Groups())
|
||||
}
|
||||
|
||||
// parseUser extracts information from `models.Userable` we care about
|
||||
func parseTeam(item models.Groupable) (*Team, error) {
|
||||
if item.GetDisplayName() == nil {
|
||||
return nil, clues.New("Team missing display name").
|
||||
With("Team ID", ptr.Val(item.GetId()))
|
||||
}
|
||||
|
||||
u := &Team{
|
||||
ID: ptr.Val(item.GetId()),
|
||||
DisplayName: ptr.Val(item.GetDisplayName()),
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
type getAllGroupers interface {
|
||||
GetAll(ctx context.Context, filterTeams bool, errs *fault.Bus) ([]models.Groupable, error)
|
||||
}
|
||||
|
||||
func getAllTeams(ctx context.Context, gas getAllGroupers) ([]*Team, error) {
|
||||
teams, err := gas.GetAll(ctx, true, fault.New(true))
|
||||
// TODO: check this. Label has to be changed
|
||||
if err != nil {
|
||||
if clues.HasLabel(err, graph.LabelsNoSharePointLicense) {
|
||||
return nil, clues.Stack(graph.ErrServiceNotEnabled, err)
|
||||
}
|
||||
|
||||
return nil, clues.Wrap(err, "retrieving teams")
|
||||
}
|
||||
|
||||
ret := make([]*Team, 0, len(teams))
|
||||
|
||||
for _, team := range teams {
|
||||
t, err := parseTeam(team)
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "parsing teams")
|
||||
}
|
||||
|
||||
ret = append(ret, t)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user