diff --git a/pkg/account/downloader/downloader.go b/pkg/account/downloader/downloader.go index 98615fd98..08abf33a8 100644 --- a/pkg/account/downloader/downloader.go +++ b/pkg/account/downloader/downloader.go @@ -19,11 +19,13 @@ package downloader import ( "context" "fmt" + + "github.com/go-logr/logr" + "github.com/dynatrace/dynatrace-configuration-as-code-core/api/clients/accounts" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/account" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/account/downloader/internal/http" - "github.com/go-logr/logr" ) type Downloader struct { @@ -61,10 +63,16 @@ func (a *Downloader) DownloadResources(ctx context.Context) (*account.Resources, return nil, fmt.Errorf("failed to fetch users: %w", err) } + serviceUsers, err := a.serviceUsers(ctx, groups) + if err != nil { + return nil, fmt.Errorf("failed to fetch service users: %w", err) + } + r := account.Resources{ - Users: users.asAccountUsers(), - Groups: groups.asAccountGroups(), - Policies: policies.asAccountPolicies(), + Users: users.asAccountUsers(), + ServiceUsers: serviceUsers.asAccountServiceUsers(), + Groups: groups.asAccountGroups(), + Policies: policies.asAccountPolicies(), } return &r, nil diff --git a/pkg/account/downloader/downloader_test.go b/pkg/account/downloader/downloader_test.go index 0d236b366..2923d5c1c 100644 --- a/pkg/account/downloader/downloader_test.go +++ b/pkg/account/downloader/downloader_test.go @@ -18,16 +18,18 @@ package downloader_test import ( "context" + "testing" + + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + accountmanagement "github.com/dynatrace/dynatrace-configuration-as-code-core/gen/account_management" stringutils "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/strings" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/account" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/account/downloader" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/account/downloader/internal/http" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - "testing" ) func TestDownloader_DownloadConfiguration(t *testing.T) { @@ -579,6 +581,7 @@ func newMockDownloader(d mockData, t *testing.T) *downloader.Downloader { client.EXPECT().GetGroups(gomock.Any(), d.ai.AccountUUID).Return(d.groups, d.groupsError).MinTimes(0).MaxTimes(1) client.EXPECT().GetUsers(gomock.Any(), d.ai.AccountUUID).Return(d.users, d.usersError).MinTimes(0).MaxTimes(1) client.EXPECT().GetGroupsForUser(gomock.Any(), userEmail(d.users), d.ai.AccountUUID).Return(d.userGroups, d.groupsForUserError).AnyTimes() + client.EXPECT().GetServiceUsers(gomock.Any(), d.ai.AccountUUID).Return(d.userGroups, d.groupsForUserError).AnyTimes() return downloader.New4Test(d.ai, client) } diff --git a/pkg/account/downloader/http_client.go b/pkg/account/downloader/http_client.go index b2753d006..b6946ca88 100644 --- a/pkg/account/downloader/http_client.go +++ b/pkg/account/downloader/http_client.go @@ -18,12 +18,14 @@ package downloader import ( "context" + accountmanagement "github.com/dynatrace/dynatrace-configuration-as-code-core/gen/account_management" ) //go:generate mockgen -source http_client.go -package=http -destination=internal/http/client_mock.go type httpClient interface { GetUsers(ctx context.Context, accountUUID string) ([]accountmanagement.UsersDto, error) + GetServiceUsers(ctx context.Context, accountUUID string) ([]accountmanagement.ExternalServiceUserDto, error) GetGroupsForUser(ctx context.Context, userEmail string, accountUUID string) (*accountmanagement.GroupUserDto, error) GetPolicies(ctx context.Context, account string) ([]accountmanagement.PolicyOverview, error) GetPolicyDefinition(ctx context.Context, dto accountmanagement.PolicyOverview) (*accountmanagement.LevelPolicyDto, error) diff --git a/pkg/account/downloader/internal/http/client.go b/pkg/account/downloader/internal/http/client.go index 985b72102..560211d36 100644 --- a/pkg/account/downloader/internal/http/client.go +++ b/pkg/account/downloader/internal/http/client.go @@ -41,6 +41,29 @@ func (c *Client) GetUsers(ctx context.Context, accountUUID string) ([]accountman return r.Items, nil } +func (c *Client) GetServiceUsers(ctx context.Context, accountUUID string) ([]accountmanagement.ExternalServiceUserDto, error) { + serviceUsers := []accountmanagement.ExternalServiceUserDto{} + const pageSize = 10 + for page := 1; ; page++ { + r, resp, err := c.ServiceUserManagementAPI.GetServiceUsersFromAccount(ctx, accountUUID).Page(float32(page)).PageSize(pageSize).Execute() + defer closeResponseBody(resp) + if err = getErrorMessageFromResponse(resp, err); err != nil { + return nil, err + } + if r == nil { + return nil, errors.New("the received data are empty") + } + + serviceUsers = append(serviceUsers, r.Results...) + + if r.NextPageKey == nil { + break + } + } + + return serviceUsers, nil +} + func (c *Client) GetGroupsForUser(ctx context.Context, userEmail string, accountUUID string) (*accountmanagement.GroupUserDto, error) { r, resp, err := c.UserManagementAPI.GetUserGroups(ctx, accountUUID, userEmail).Execute() defer closeResponseBody(resp) diff --git a/pkg/account/downloader/service_users.go b/pkg/account/downloader/service_users.go new file mode 100644 index 000000000..104b7385f --- /dev/null +++ b/pkg/account/downloader/service_users.go @@ -0,0 +1,80 @@ +/* + * @license + * Copyright 2023 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package downloader + +import ( + "context" + "fmt" + + accountmanagement "github.com/dynatrace/dynatrace-configuration-as-code-core/gen/account_management" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/account" +) + +type ( + ServiceUsers []serviceUser + + serviceUser struct { + serviceUser *account.ServiceUser + dto *accountmanagement.ExternalServiceUserDto + dtoGroups *accountmanagement.GroupUserDto + } +) + +func (a *Downloader) serviceUsers(ctx context.Context, groups Groups) (ServiceUsers, error) { + log.WithCtxFields(ctx).Info("Downloading service users") + dtos, err := a.httpClient.GetServiceUsers(ctx, a.accountInfo.AccountUUID) + if err != nil { + return nil, fmt.Errorf("failed to get a list of service users for account %q from DT: %w", a.accountInfo, err) + } + + retVal := make(ServiceUsers, 0, len(dtos)) + for _, dto := range dtos { + log.WithCtxFields(ctx).Debug("Downloading details for user %q", dto.Name) + dtoGroups, err := a.httpClient.GetGroupsForUser(ctx, dto.Email, a.accountInfo.AccountUUID) + if err != nil { + return nil, fmt.Errorf("failed to get a list of bind groups for service user %q: %w", dto.Name, err) + } + if dtoGroups == nil { + return nil, fmt.Errorf("failed to get a list of bind groups for the service user %q", dto.Name) + } + + su := &account.ServiceUser{ + Name: dto.Name, + OriginObjectID: dtoGroups.Uid, + Description: dto.Description, + Groups: groups.refFromDTOs(dtoGroups.Groups), + } + + retVal = append(retVal, serviceUser{ + serviceUser: su, + dto: &dto, + dtoGroups: dtoGroups, + }) + } + + log.WithCtxFields(ctx).Info("Fetched %d service users", len(retVal)) + return retVal, nil +} + +func (sus ServiceUsers) asAccountServiceUsers() map[account.ServiceUserId]account.ServiceUser { + retVal := make(map[account.ServiceUserId]account.ServiceUser, len(sus)) + for _, su := range sus { + retVal[su.serviceUser.Name] = *su.serviceUser + } + return retVal +}