diff --git a/.gitignore b/.gitignore index 979da68..0ac30f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.local.yaml specs +.idea diff --git a/.golangci.yml b/.golangci.yml index 6799ee0..dcb74cc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -89,9 +89,6 @@ linters-settings: gofmt: simplify: false # gofmt with `-s` option, true by default - goimports: - local-prefixes: go.ytsaurus.tech - goconst: # minimal length of string constant, 3 by default min-len: 3 diff --git a/app.go b/app.go index 2d92cde..5aca535 100644 --- a/app.go +++ b/app.go @@ -6,12 +6,18 @@ import ( "syscall" "time" + "github.com/pkg/errors" + "k8s.io/utils/clock" ) -type Azure interface { - GetUsers() ([]AzureUser, error) - GetGroupsWithMembers() ([]AzureGroupWithMembers, error) +type ObjectID = string + +type Source interface { + GetUsers() ([]SourceUser, error) + GetGroupsWithMembers() ([]SourceGroupWithMembers, error) + CreateUserFromRaw(raw map[string]any) (SourceUser, error) + CreateGroupFromRaw(raw map[string]any) (SourceGroup, error) } type App struct { @@ -22,7 +28,7 @@ type App struct { banDuration time.Duration ytsaurus *Ytsaurus - azure Azure + source Source stopCh chan struct{} sigCh chan os.Signal @@ -30,17 +36,21 @@ type App struct { } func NewApp(cfg *Config, logger appLoggerType) (*App, error) { - azure, err := NewAzureReal(cfg.Azure, logger) + if cfg.Azure == nil { + return nil, errors.New("one and only one source should be specified") + } + + source, err := NewAzureReal(cfg.Azure, logger) if err != nil { return nil, err } - return NewAppCustomized(cfg, logger, azure, clock.RealClock{}) + return NewAppCustomized(cfg, logger, source, clock.RealClock{}) } // NewAppCustomized used in tests. -func NewAppCustomized(cfg *Config, logger appLoggerType, azure Azure, clock clock.PassiveClock) (*App, error) { - yt, err := NewYtsaurus(cfg.Ytsaurus, logger, clock) +func NewAppCustomized(cfg *Config, logger appLoggerType, source Source, clock clock.PassiveClock) (*App, error) { + yt, err := NewYtsaurus(&cfg.Ytsaurus, logger, clock) if err != nil { return nil, err } @@ -56,7 +66,7 @@ func NewAppCustomized(cfg *Config, logger appLoggerType, azure Azure, clock cloc banDuration: cfg.App.BanBeforeRemoveDuration, ytsaurus: yt, - azure: azure, + source: source, stopCh: make(chan struct{}), sigCh: sigCh, @@ -83,7 +93,7 @@ func (a *App) Start() { } } else { a.logger.Info( - "app.sync_interval config variable is not greater than zero, " + + "app.sync_interval config variable is not specified or is not greater than zero, " + "auto sync is disabled. Send SIGUSR1 for manual sync.", ) for { diff --git a/app_test.go b/app_test.go index a97c82f..704a714 100644 --- a/app_test.go +++ b/app_test.go @@ -23,11 +23,11 @@ type testCase struct { appConfig *AppConfig testTime time.Time - azureUsersSetUp []AzureUser + azureUsersSetUp []SourceUser ytUsersSetUp []YtsaurusUser ytUsersExpected []YtsaurusUser - azureGroupsSetUp []AzureGroupWithMembers + azureGroupsSetUp []SourceGroupWithMembers ytGroupsSetUp []YtsaurusGroupWithMembers ytGroupsExpected []YtsaurusGroupWithMembers } @@ -98,94 +98,123 @@ var ( } aliceYtsaurus = YtsaurusUser{ - Username: "alice", - AzureID: aliceAzure.AzureID, - PrincipalName: aliceAzure.PrincipalName, - Email: aliceAzure.Email, - FirstName: aliceAzure.FirstName, - LastName: aliceAzure.LastName, - DisplayName: aliceAzure.DisplayName, + Username: "alice", + SourceRaw: map[string]any{ + "id": aliceAzure.AzureID, + "principal_name": aliceAzure.PrincipalName, + "email": aliceAzure.Email, + "first_name": aliceAzure.FirstName, + "last_name": aliceAzure.LastName, + "display_name": aliceAzure.DisplayName, + }, } bobYtsaurus = YtsaurusUser{ - Username: "bob", - AzureID: bobAzure.AzureID, - PrincipalName: bobAzure.PrincipalName, - Email: bobAzure.Email, - FirstName: bobAzure.FirstName, - LastName: bobAzure.LastName, - DisplayName: bobAzure.DisplayName, + Username: "bob", + SourceRaw: map[string]any{ + "id": bobAzure.AzureID, + "principal_name": bobAzure.PrincipalName, + "email": bobAzure.Email, + "first_name": bobAzure.FirstName, + "last_name": bobAzure.LastName, + "display_name": bobAzure.DisplayName, + }, } carolYtsaurus = YtsaurusUser{ - Username: "carol", - AzureID: carolAzure.AzureID, - PrincipalName: carolAzure.PrincipalName, - Email: carolAzure.Email, - FirstName: carolAzure.FirstName, - LastName: carolAzure.LastName, - DisplayName: carolAzure.DisplayName, + Username: "carol", + SourceRaw: map[string]any{ + "id": carolAzure.AzureID, + "principal_name": carolAzure.PrincipalName, + "email": carolAzure.Email, + "first_name": carolAzure.FirstName, + "last_name": carolAzure.LastName, + "display_name": carolAzure.DisplayName, + }, } aliceYtsaurusChangedLastName = YtsaurusUser{ - Username: aliceYtsaurus.Username, - AzureID: aliceYtsaurus.AzureID, - PrincipalName: aliceYtsaurus.PrincipalName, - Email: aliceYtsaurus.Email, - FirstName: aliceYtsaurus.FirstName, - LastName: aliceAzureChangedLastName.LastName, - DisplayName: aliceYtsaurus.DisplayName, + Username: aliceYtsaurus.Username, + SourceRaw: map[string]any{ + "id": aliceYtsaurus.SourceRaw["id"], + "principal_name": aliceYtsaurus.SourceRaw["principal_name"], + "email": aliceYtsaurus.SourceRaw["email"], + "first_name": aliceYtsaurus.SourceRaw["first_name"], + "last_name": aliceAzureChangedLastName.LastName, + "display_name": aliceYtsaurus.SourceRaw["display_name"], + }, } bobYtsaurusChangedEmail = YtsaurusUser{ - Username: "bobby:example.com", - AzureID: bobYtsaurus.AzureID, - PrincipalName: bobAzureChangedEmail.PrincipalName, - Email: bobAzureChangedEmail.Email, - FirstName: bobYtsaurus.FirstName, - LastName: bobYtsaurus.LastName, - DisplayName: bobYtsaurus.DisplayName, + Username: "bobby:example.com", + SourceRaw: map[string]any{ + "id": bobYtsaurus.SourceRaw["id"], + "principal_name": bobAzureChangedEmail.PrincipalName, + "email": bobAzureChangedEmail.Email, + "first_name": bobYtsaurus.SourceRaw["first_name"], + "last_name": bobYtsaurus.SourceRaw["last_name"], + "display_name": bobYtsaurus.SourceRaw["display_name"], + }, } bobYtsaurusBanned = YtsaurusUser{ - Username: bobYtsaurus.Username, - AzureID: bobYtsaurus.AzureID, - PrincipalName: bobYtsaurus.PrincipalName, - Email: bobYtsaurus.Email, - FirstName: bobYtsaurus.FirstName, - LastName: bobYtsaurus.LastName, - DisplayName: bobYtsaurus.DisplayName, - BannedSince: initialTestTime, + Username: bobYtsaurus.Username, + SourceRaw: map[string]any{ + "id": bobYtsaurus.SourceRaw["id"], + "principal_name": bobYtsaurus.SourceRaw["principal_name"], + "email": bobYtsaurus.SourceRaw["email"], + "first_name": bobYtsaurus.SourceRaw["first_name"], + "last_name": bobYtsaurus.SourceRaw["last_name"], + "display_name": bobYtsaurus.SourceRaw["display_name"], + }, + BannedSince: initialTestTime, } carolYtsaurusBanned = YtsaurusUser{ - Username: carolYtsaurus.Username, - AzureID: carolYtsaurus.AzureID, - PrincipalName: carolYtsaurus.PrincipalName, - Email: carolYtsaurus.Email, - FirstName: carolYtsaurus.FirstName, - LastName: carolYtsaurus.LastName, - DisplayName: carolYtsaurus.DisplayName, - BannedSince: initialTestTime.Add(40 * time.Hour), + Username: carolYtsaurus.Username, + SourceRaw: map[string]any{ + "id": carolYtsaurus.SourceRaw["id"], + "principal_name": carolYtsaurus.SourceRaw["principal_name"], + "email": carolYtsaurus.SourceRaw["email"], + "first_name": carolYtsaurus.SourceRaw["first_name"], + "last_name": carolYtsaurus.SourceRaw["last_name"], + "display_name": carolYtsaurus.SourceRaw["display_name"], + }, + BannedSince: initialTestTime.Add(40 * time.Hour), } devsYtsaurusGroup = YtsaurusGroup{ - Name: "acme.devs", - AzureID: devsAzureGroup.AzureID, - DisplayName: "acme.devs|all", + Name: "acme.devs", + SourceRaw: map[string]any{ + "id": devsAzureGroup.AzureID, + "display_name": "acme.devs|all", + "identity": "acme.devs|all", + }, } qaYtsaurusGroup = YtsaurusGroup{ - Name: "acme.qa", - AzureID: "fake-az-acme.qa", - DisplayName: "acme.qa|all", + Name: "acme.qa", + SourceRaw: map[string]any{ + "id": "fake-az-acme.qa", + "display_name": "acme.qa|all", + "identity": "acme.qa", + }, } hqYtsaurusGroup = YtsaurusGroup{ - Name: "acme.hq", - AzureID: hqAzureGroup.AzureID, - DisplayName: "acme.hq", + Name: "acme.hq", + SourceRaw: map[string]any{ + "id": hqAzureGroup.AzureID, + "display_name": "acme.hq", + "identity": "acme.hq", + }, } devsYtsaurusGroupChangedDisplayName = YtsaurusGroup{ - Name: "acme.developers", - AzureID: devsAzureGroup.AzureID, - DisplayName: "acme.developers|all", + Name: "acme.developers", + SourceRaw: map[string]any{ + "id": devsAzureGroup.AzureID, + "display_name": "acme.developers|all", + "identity": "acme.developers|all", + }, } hqYtsaurusGroupChangedBackwardCompatible = YtsaurusGroup{ - Name: "acme.hq", - AzureID: hqAzureGroup.AzureID, - DisplayName: "acme.hq|all", + Name: "acme.hq", + SourceRaw: map[string]any{ + "id": hqAzureGroup.AzureID, + "display_name": "acme.hq|all", + "identity": "acme.hq|all", + }, } defaultUsernameReplacements = []ReplacementPair{ @@ -205,7 +234,7 @@ var ( testCases = []testCase{ { name: "a-skip-b-create-c-remove", - azureUsersSetUp: []AzureUser{ + azureUsersSetUp: []SourceUser{ aliceAzure, bobAzure, }, @@ -229,7 +258,7 @@ var ( aliceYtsaurus, bobYtsaurus, }, - azureUsersSetUp: []AzureUser{ + azureUsersSetUp: []SourceUser{ aliceAzure, }, ytUsersExpected: []YtsaurusUser{ @@ -253,7 +282,7 @@ var ( bobYtsaurusBanned, carolYtsaurusBanned, }, - azureUsersSetUp: []AzureUser{ + azureUsersSetUp: []SourceUser{ aliceAzure, carolAzure, }, @@ -269,7 +298,7 @@ var ( GroupnameReplacements: defaultGroupnameReplacements, RemoveLimit: 3, }, - azureUsersSetUp: []AzureUser{}, + azureUsersSetUp: []SourceUser{}, ytUsersSetUp: []YtsaurusUser{ aliceYtsaurus, bobYtsaurus, @@ -289,7 +318,7 @@ var ( GroupnameReplacements: defaultGroupnameReplacements, RemoveLimit: 3, }, - azureGroupsSetUp: []AzureGroupWithMembers{}, + azureGroupsSetUp: []SourceGroupWithMembers{}, ytGroupsSetUp: []YtsaurusGroupWithMembers{ NewEmptyYtsaurusGroupWithMembers(devsYtsaurusGroup), NewEmptyYtsaurusGroupWithMembers(qaYtsaurusGroup), @@ -304,7 +333,7 @@ var ( }, { name: "a-changed-name-b-changed-email", - azureUsersSetUp: []AzureUser{ + azureUsersSetUp: []SourceUser{ aliceAzureChangedLastName, bobAzureChangedEmail, }, @@ -319,7 +348,7 @@ var ( }, { name: "skip-create-remove-group-no-members-change-correct-name-replace", - azureUsersSetUp: []AzureUser{ + azureUsersSetUp: []SourceUser{ aliceAzure, bobAzure, carolAzure, @@ -344,14 +373,14 @@ var ( Members: NewStringSetFromItems(bobYtsaurus.Username), }, }, - azureGroupsSetUp: []AzureGroupWithMembers{ + azureGroupsSetUp: []SourceGroupWithMembers{ { - AzureGroup: devsAzureGroup, - Members: NewStringSetFromItems(aliceAzure.AzureID), + SourceGroup: devsAzureGroup, + Members: NewStringSetFromItems(aliceAzure.AzureID), }, { - AzureGroup: hqAzureGroup, - Members: NewStringSetFromItems(carolAzure.AzureID), + SourceGroup: hqAzureGroup, + Members: NewStringSetFromItems(carolAzure.AzureID), }, }, ytGroupsExpected: []YtsaurusGroupWithMembers{ @@ -367,7 +396,7 @@ var ( }, { name: "memberships-add-remove", - azureUsersSetUp: []AzureUser{ + azureUsersSetUp: []SourceUser{ aliceAzure, bobAzure, carolAzure, @@ -391,9 +420,9 @@ var ( ), }, }, - azureGroupsSetUp: []AzureGroupWithMembers{ + azureGroupsSetUp: []SourceGroupWithMembers{ { - AzureGroup: devsAzureGroup, + SourceGroup: devsAzureGroup, Members: NewStringSetFromItems( aliceAzure.AzureID, carolAzure.AzureID, @@ -412,7 +441,7 @@ var ( }, { name: "display-name-changes", - azureUsersSetUp: []AzureUser{ + azureUsersSetUp: []SourceUser{ aliceAzure, bobAzure, carolAzure, @@ -443,10 +472,10 @@ var ( ), }, }, - azureGroupsSetUp: []AzureGroupWithMembers{ + azureGroupsSetUp: []SourceGroupWithMembers{ { // This group should be updated. - AzureGroup: devsAzureGroupChangedDisplayName, + SourceGroup: devsAzureGroupChangedDisplayName, // Members list are also updated. Members: NewStringSetFromItems( aliceAzure.AzureID, @@ -454,9 +483,9 @@ var ( ), }, { - // for this group only displayName should be updated - AzureGroup: hqAzureGroupChangedBackwardCompatible, - // members also changed + // For this group only displayName should be updated. + SourceGroup: hqAzureGroupChangedBackwardCompatible, + // Members also changed. Members: NewStringSetFromItems( aliceAzure.AzureID, carolAzure.AzureID, @@ -532,14 +561,15 @@ func TestAppSync(t *testing.T) { } app, err := NewAppCustomized( &Config{ - App: tc.appConfig, + App: *tc.appConfig, Azure: &AzureConfig{}, - Ytsaurus: &YtsaurusConfig{ - Proxy: ytLocal.GetProxy(), - ApplyUserChanges: true, - ApplyGroupChanges: true, - ApplyMemberChanges: true, - LogLevel: "DEBUG", + Ytsaurus: YtsaurusConfig{ + Proxy: ytLocal.GetProxy(), + ApplyUserChanges: true, + ApplyGroupChanges: true, + ApplyMemberChanges: true, + LogLevel: "DEBUG", + SourceAttributeName: "azure", }, }, getDevelopmentLogger(), azure, @@ -615,16 +645,18 @@ func TestManageUnmanagedUsersIsForbidden(t *testing.T) { "Prevented attempt to change manual managed user", ) require.ErrorContains(t, - ytsaurus.UpdateUser(username, YtsaurusUser{Username: username, Email: "dummy@acme.com"}), + ytsaurus.UpdateUser(username, YtsaurusUser{Username: username, SourceRaw: map[string]any{ + "email": "dummy@acme.com", + }}), "Prevented attempt to change manual managed user", ) } } func getAllYtsaurusObjects(t *testing.T, client yt.Client) (users []YtsaurusUser, groups []YtsaurusGroupWithMembers) { - allUsers, err := doGetAllYtsaurusUsers(context.Background(), client) + allUsers, err := doGetAllYtsaurusUsers(context.Background(), client, "azure") require.NoError(t, err) - allGroups, err := doGetAllYtsaurusGroupsWithMembers(context.Background(), client) + allGroups, err := doGetAllYtsaurusGroupsWithMembers(context.Background(), client, "azure") require.NoError(t, err) return allUsers, allGroups } @@ -637,7 +669,7 @@ func setupYtsaurusObjects(t *testing.T, client yt.Client, users []YtsaurusUser, context.Background(), client, user.Username, - buildUserAttributes(user), + buildUserAttributes(user, "azure"), ) require.NoError(t, err) } @@ -648,7 +680,7 @@ func setupYtsaurusObjects(t *testing.T, client yt.Client, users []YtsaurusUser, context.Background(), client, group.Name, - buildGroupAttributes(group.YtsaurusGroup), + buildGroupAttributes(group.YtsaurusGroup, "azure"), ) require.NoError(t, err) for member := range group.Members.Iter() { diff --git a/config.example.yaml b/azure_config.example.yaml similarity index 100% rename from config.example.yaml rename to azure_config.example.yaml diff --git a/azure_fake.go b/azure_fake.go index 2ac1e06..528e8b6 100644 --- a/azure_fake.go +++ b/azure_fake.go @@ -1,26 +1,34 @@ package main type AzureFake struct { - users []AzureUser - groups []AzureGroupWithMembers + users []SourceUser + groups []SourceGroupWithMembers } func NewAzureFake() *AzureFake { return &AzureFake{} } -func (a *AzureFake) setUsers(users []AzureUser) { +func (a *AzureFake) setUsers(users []SourceUser) { a.users = users } -func (a *AzureFake) setGroups(groups []AzureGroupWithMembers) { +func (a *AzureFake) setGroups(groups []SourceGroupWithMembers) { a.groups = groups } -func (a *AzureFake) GetUsers() ([]AzureUser, error) { +func (a *AzureFake) CreateUserFromRaw(raw map[string]any) (SourceUser, error) { + return NewAzureUser(raw) +} + +func (a *AzureFake) CreateGroupFromRaw(raw map[string]any) (SourceGroup, error) { + return NewAzureGroup(raw) +} + +func (a *AzureFake) GetUsers() ([]SourceUser, error) { return a.users, nil } -func (a *AzureFake) GetGroupsWithMembers() ([]AzureGroupWithMembers, error) { +func (a *AzureFake) GetGroupsWithMembers() ([]SourceGroupWithMembers, error) { return a.groups, nil } diff --git a/azure_models.go b/azure_models.go new file mode 100644 index 0000000..07d7caf --- /dev/null +++ b/azure_models.go @@ -0,0 +1,98 @@ +package main + +import ( + "go.ytsaurus.tech/yt/go/yson" +) + +type AzureUser struct { + // PrincipalName is unique human-readable Azure user field, used (possibly with changes) + // for the corresponding YTsaurus user's `name` attribute. + PrincipalName string `yson:"principal_name"` + + AzureID ObjectID `yson:"id"` + Email string `yson:"email"` + FirstName string `yson:"first_name"` + LastName string `yson:"last_name"` + DisplayName string `yson:"display_name"` +} + +func NewAzureUser(attributes map[string]any) (*AzureUser, error) { + bytes, err := yson.Marshal(attributes) + if err != nil { + return nil, err + } + + var azureUser AzureUser + err = yson.Unmarshal(bytes, &azureUser) + if err != nil { + return nil, err + } + return &azureUser, nil +} + +func (au AzureUser) GetID() ObjectID { + return au.AzureID +} + +func (au AzureUser) GetName() string { + return au.PrincipalName +} + +func (au AzureUser) GetRaw() (map[string]any, error) { + bytes, err := yson.Marshal(au) + if err != nil { + return nil, err + } + + raw := make(map[string]any) + err = yson.Unmarshal(bytes, &raw) + if err != nil { + return nil, err + } + return raw, nil +} + +type AzureGroup struct { + // Identity is unique human-readable Source user field, used (possibly with changes) + // for the corresponding YTsaurus user's `name` attribute. + Identity string `yson:"identity"` + + AzureID ObjectID `yson:"id"` + DisplayName string `yson:"display_name"` +} + +func NewAzureGroup(attributes map[string]any) (*AzureGroup, error) { + bytes, err := yson.Marshal(attributes) + if err != nil { + return nil, err + } + + var azureGroup AzureGroup + err = yson.Unmarshal(bytes, &azureGroup) + if err != nil { + return nil, err + } + return &azureGroup, nil +} + +func (ag AzureGroup) GetID() ObjectID { + return ag.AzureID +} + +func (ag AzureGroup) GetName() string { + return ag.Identity +} + +func (ag AzureGroup) GetRaw() (map[string]any, error) { + bytes, err := yson.Marshal(ag) + if err != nil { + return nil, err + } + + raw := make(map[string]any) + err = yson.Unmarshal(bytes, &raw) + if err != nil { + return nil, err + } + return raw, nil +} diff --git a/azure_real.go b/azure_real.go index 4b9c19c..a3e29ea 100644 --- a/azure_real.go +++ b/azure_real.go @@ -18,13 +18,11 @@ import ( const ( scope = "https://graph.microsoft.com/.default" + msgraphExpandLimit = 20 defaultAzureTimeout = 3 * time.Second defaultAzureSecretEnvVar = "AZURE_CLIENT_SECRET" - msgraphExpandLimit = 20 ) -type AzureID = string - var ( defaultUserFieldsToSelect = []string{ "userPrincipalName", @@ -41,33 +39,6 @@ var ( } ) -type AzureUser struct { - // PrincipalName is unique human-readable Azure user field, used (possibly with changes) - // for the corresponding YTsaurus user's `name` attribute. - PrincipalName string - - AzureID AzureID - Email string - FirstName string - LastName string - DisplayName string -} - -type AzureGroup struct { - // Identity is unique human-readable Azure user field, used (possibly with changes) - // for the corresponding YTsaurus user's `name` attribute. - Identity string - - AzureID AzureID - DisplayName string -} - -type AzureGroupWithMembers struct { - AzureGroup - // Members is a set of strings, representing users' AzureID. - Members StringSet -} - type AzureReal struct { graphClient *msgraphsdk.GraphServiceClient @@ -129,7 +100,15 @@ func handleNil[T any](s *T) T { return result } -func (a *AzureReal) GetUsers() ([]AzureUser, error) { +func (a *AzureReal) CreateUserFromRaw(raw map[string]any) (SourceUser, error) { + return NewAzureUser(raw) +} + +func (a *AzureReal) CreateGroupFromRaw(raw map[string]any) (SourceGroup, error) { + return NewAzureGroup(raw) +} + +func (a *AzureReal) GetUsers() ([]SourceUser, error) { ctx, cancel := context.WithTimeout(context.Background(), a.timeout) defer cancel() @@ -139,7 +118,7 @@ func (a *AzureReal) GetUsers() ([]AzureUser, error) { } usersSkipped := 0 - var users []AzureUser + var users []SourceUser for _, user := range usersRaw { principalName := handleNil(user.GetUserPrincipalName()) id := handleNil(user.GetId()) @@ -177,7 +156,7 @@ func (a *AzureReal) GetUsers() ([]AzureUser, error) { return users, nil } -func (a *AzureReal) GetGroupsWithMembers() ([]AzureGroupWithMembers, error) { +func (a *AzureReal) GetGroupsWithMembers() ([]SourceGroupWithMembers, error) { ctx, cancel := context.WithTimeout(context.Background(), a.timeout) defer cancel() @@ -187,7 +166,7 @@ func (a *AzureReal) GetGroupsWithMembers() ([]AzureGroupWithMembers, error) { } groupsSkipped := 0 - var groups []AzureGroupWithMembers + var groups []SourceGroupWithMembers for _, group := range groupsRaw { displayName := handleNil(group.GetDisplayName()) id := handleNil(group.GetId()) @@ -225,8 +204,8 @@ func (a *AzureReal) GetGroupsWithMembers() ([]AzureGroupWithMembers, error) { a.maybePrintDebugLogs(id, "azure_members_count", len(memberIDs.ToSlice())) groups = append(groups, - AzureGroupWithMembers{ - AzureGroup: AzureGroup{ + SourceGroupWithMembers{ + SourceGroup: AzureGroup{ Identity: displayName, AzureID: id, DisplayName: displayName, @@ -239,7 +218,7 @@ func (a *AzureReal) GetGroupsWithMembers() ([]AzureGroupWithMembers, error) { return groups, nil } -func (a *AzureReal) maybePrintDebugLogs(id AzureID, args ...any) { +func (a *AzureReal) maybePrintDebugLogs(id ObjectID, args ...any) { args = append([]any{"id", id}, args...) for _, debugID := range a.debugAzureIDs { if id == debugID { diff --git a/azure_real_integration_test.go b/azure_real_integration_test.go index 18037b2..0027f14 100644 --- a/azure_real_integration_test.go +++ b/azure_real_integration_test.go @@ -86,7 +86,7 @@ func TestPrintAzureGroupsIntegrationRaw(t *testing.T) { cfg, err := loadConfig("config.local.yaml") require.NoError(t, err) - logger, err := configureLogger(cfg.Logging) + logger, err := configureLogger(&cfg.Logging) require.NoError(t, err) azure, err := NewAzureReal(cfg.Azure, logger) require.NoError(t, err) diff --git a/config.go b/config.go index 00a2835..08bbf22 100644 --- a/config.go +++ b/config.go @@ -5,19 +5,19 @@ import ( ) type Config struct { - App *AppConfig `yaml:"app"` - Azure *AzureConfig `yaml:"azure"` - Ytsaurus *YtsaurusConfig `yaml:"ytsaurus"` - Logging *LoggingConfig `yaml:"logging"` + App AppConfig `yaml:"app"` + Ytsaurus YtsaurusConfig `yaml:"ytsaurus"` + Logging LoggingConfig `yaml:"logging"` + + Azure *AzureConfig `yaml:"azure,omitempty"` } type AppConfig struct { // SyncInterval is the interval between full synchronizations. - // Zero value means that auto-sync disabled (sync can be invoked only manually). + // If it is not speciied or value is zero than auto-sync disabled (sync can be invoked only manually). SyncInterval time.Duration `yaml:"sync_interval"` - // UsernameReplacements is a list of replaces which will be applied to the userPrincipalName Azure field before - // using as username in Ytsaurus. + // UsernameReplacements is a list of replaces which will be applied to a username for source (Azure). // For example, you may use it to strip off characters like @ which are not recommended for use // in usernames as they are required to be escaped in YPath. UsernameReplacements []ReplacementPair `yaml:"username_replacements"` @@ -25,10 +25,11 @@ type AppConfig struct { // If count users or groups for planned delete in on sync cycle reaches RemoveLimit // app will fail that sync cycle. - RemoveLimit int `yaml:"remove_limit"` + // No limit if it is not specified. + RemoveLimit int `yaml:"remove_limit,omitempty"` // BanBeforeRemoveDuration is a duration of a graceful ban before finally removing the user from YTsaurus. - // Default value is 0s, which means remove straight after user was found to be missing from Azure,. + // If it is not specified, user will be removed straight after user was found to be missing from source (Azure). BanBeforeRemoveDuration time.Duration `yaml:"ban_before_remove_duration"` } @@ -41,12 +42,14 @@ type AzureConfig struct { Tenant string `yaml:"tenant"` ClientID string `yaml:"client_id"` ClientSecretEnvVar string `yaml:"client_secret_env_var"` // default: "AZURE_CLIENT_SECRET" + // UsersFilter is MS Graph $filter value used for user fetching requests. // See https://learn.microsoft.com/en-us/graph/api/user-list?#optional-query-parameters UsersFilter string `yaml:"users_filter"` // GroupsFilter is MS Graph $filter value used for group fetching requests. // See https://learn.microsoft.com/en-us/graph/api/group-list GroupsFilter string `yaml:"groups_filter"` + // GroupsDisplayNameSuffixPostFilter applied to the fetched groups display names. GroupsDisplayNameSuffixPostFilter string `yaml:"groups_display_name_suffix_post_filter"` Timeout time.Duration `yaml:"timeout"` @@ -73,6 +76,8 @@ type YtsaurusConfig struct { DebugUsernames []string `yaml:"debug_usernames"` // DebugGroupnames is a list of YTsaurus groupnames for which app will print more debug info in logs. DebugGroupnames []string `yaml:"debug_groupnames"` + // The attribute name of user/group object in YTsaurus. + SourceAttributeName string `yaml:"source_attribute_name"` } type LoggingConfig struct { diff --git a/config_test.go b/config_test.go index 53c502e..b54fdad 100644 --- a/config_test.go +++ b/config_test.go @@ -8,11 +8,11 @@ import ( "github.com/stretchr/testify/require" ) -//go:embed config.example.yaml +//go:embed azure_config.example.yaml var _ embed.FS -func TestConfig(t *testing.T) { - configPath := "config.example.yaml" +func TestAzureConfig(t *testing.T) { + configPath := "azure_config.example.yaml" cfg, err := loadConfig(configPath) require.NoError(t, err) @@ -45,7 +45,7 @@ func TestConfig(t *testing.T) { require.Equal(t, "WARN", cfg.Logging.Level) require.Equal(t, true, cfg.Logging.IsProduction) - logger, err := configureLogger(cfg.Logging) + logger, err := configureLogger(&cfg.Logging) require.NoError(t, err) logger.Debugw("test logging message", "key", "val") } diff --git a/diff.go b/diff.go index d5802c0..d8927f7 100644 --- a/diff.go +++ b/diff.go @@ -1,14 +1,35 @@ package main import ( + "bytes" "fmt" "strings" "time" + "go.ytsaurus.tech/yt/go/yson" + "github.com/pkg/errors" "go.uber.org/zap" ) +type SourceUser interface { + GetID() ObjectID + GetName() string + GetRaw() (map[string]any, error) +} + +type SourceGroup interface { + GetID() ObjectID + GetName() string + GetRaw() (map[string]any, error) +} + +type SourceGroupWithMembers struct { + SourceGroup SourceGroup + // Members is a set of strings, representing users' ObjectID. + Members StringSet +} + func (a *App) syncOnce() { a.logger.Info("Start syncing") defer a.logger.Info("Finish syncing") @@ -31,30 +52,27 @@ func (a *App) isRemoveLimitReached(objectsCount int) bool { return objectsCount >= a.removeLimit } -// syncUsers syncs AD users with YTsaurus cluster and returns /actual/ map[AzureID]YtsaurusUser +// syncUsers syncs AD users with YTsaurus cluster and returns /actual/ map[ObjectID]YtsaurusUser // after applying changes. -func (a *App) syncUsers() (map[AzureID]YtsaurusUser, error) { +func (a *App) syncUsers() (map[ObjectID]YtsaurusUser, error) { a.logger.Info("Start syncing users") - azureUsers, err := a.azure.GetUsers() + var err error + var sourceUsers []SourceUser + + sourceUsers, err = a.source.GetUsers() if err != nil { - return nil, errors.Wrap(err, "failed to get Azure users") + return nil, errors.Wrap(err, "failed to get Source users") } + ytUsers, err := a.ytsaurus.GetUsers() if err != nil { return nil, errors.Wrap(err, "failed to get YTsaurus users") } - azureUsersMap := make(map[AzureID]AzureUser) - ytUsersMap := make(map[AzureID]YtsaurusUser) - - for _, user := range azureUsers { - azureUsersMap[user.AzureID] = user - } - for _, user := range ytUsers { - ytUsersMap[user.AzureID] = user + diff, err := a.diffUsers(sourceUsers, ytUsers) + if err != nil { + return nil, errors.Wrap(err, "failed to calculate users diff") } - - diff := a.diffUsers(azureUsersMap, ytUsersMap) if a.isRemoveLimitReached(len(diff.remove)) { return nil, fmt.Errorf("delete limit in one cycle reached: %d %v", len(diff.remove), diff) } @@ -73,8 +91,6 @@ func (a *App) syncUsers() (map[AzureID]YtsaurusUser, error) { if wasRemoved { removedCount++ } - // Actualizing user map for group sync later. - delete(ytUsersMap, user.AzureID) } for _, user := range diff.create { err = a.ytsaurus.CreateUser(user) @@ -82,8 +98,6 @@ func (a *App) syncUsers() (map[AzureID]YtsaurusUser, error) { createErrCount++ a.logger.Errorw("failed to create user", zap.Error(err), "user", user) } - // Actualizing user map for group sync later. - ytUsersMap[user.AzureID] = user } for _, updatedUser := range diff.update { err = a.ytsaurus.UpdateUser(updatedUser.OldUsername, updatedUser.YtsaurusUser) @@ -91,8 +105,6 @@ func (a *App) syncUsers() (map[AzureID]YtsaurusUser, error) { updateErrCount++ a.logger.Errorw("failed to update user", zap.Error(err), "user", updatedUser) } - // Actualizing user map for group sync later. - ytUsersMap[updatedUser.AzureID] = updatedUser.YtsaurusUser } a.logger.Infow("Finish syncing users", "created", len(diff.create)-createErrCount, @@ -103,21 +115,24 @@ func (a *App) syncUsers() (map[AzureID]YtsaurusUser, error) { "banned", bannedCount, "ban_or_remove_errors", banOrremoveErrCount, ) - return ytUsersMap, nil + return diff.result, nil } -func (a *App) syncGroups(usersMap map[AzureID]YtsaurusUser) error { +func (a *App) syncGroups(usersMap map[ObjectID]YtsaurusUser) error { a.logger.Info("Start syncing groups") - azureGroups, err := a.azure.GetGroupsWithMembers() + azureGroups, err := a.source.GetGroupsWithMembers() if err != nil { - return errors.Wrap(err, "failed to get Azure groups") + return errors.Wrap(err, "failed to get Source groups") } ytGroups, err := a.ytsaurus.GetGroupsWithMembers() if err != nil { return errors.Wrap(err, "failed to get YTsaurus groups") } - diff := a.diffGroups(azureGroups, ytGroups, usersMap) + diff, err := a.diffGroups(azureGroups, ytGroups, usersMap) + if err != nil { + return errors.Wrap(err, "failed to calculate groups diff") + } if a.isRemoveLimitReached(len(diff.groupsToRemove)) { return fmt.Errorf("delete limit in one cycle reached: %d %v", len(diff.groupsToRemove), diff) } @@ -171,6 +186,7 @@ func (a *App) syncGroups(usersMap map[AzureID]YtsaurusUser) error { // TODO: alerts } } + a.logger.Infow("Finish syncing group memberships", "added", len(diff.membersToAdd)-addMemberErrCount, "add_errors", addMemberErrCount, @@ -189,30 +205,37 @@ type groupDiff struct { } func (a *App) diffGroups( - azureGroups []AzureGroupWithMembers, + sourceGroups []SourceGroupWithMembers, ytGroups []YtsaurusGroupWithMembers, - usersMap map[AzureID]YtsaurusUser, -) groupDiff { + usersMap map[ObjectID]YtsaurusUser, +) (*groupDiff, error) { var groupsToCreate, groupsToRemove []YtsaurusGroup var groupsToUpdate []UpdatedYtsaurusGroup var membersToAdd, membersToRemove []YtsaurusMembership - azureGroupsWithMembersMap := make(map[AzureID]AzureGroupWithMembers) - ytGroupsWithMembersMap := make(map[AzureID]YtsaurusGroupWithMembers) - - for _, group := range azureGroups { - azureGroupsWithMembersMap[group.AzureID] = group + sourceGroupsWithMembersMap := make(map[ObjectID]SourceGroupWithMembers) + for _, group := range sourceGroups { + sourceGroupsWithMembersMap[group.SourceGroup.GetID()] = group } + + ytGroupsWithMembersMap := make(map[ObjectID]YtsaurusGroupWithMembers) for _, group := range ytGroups { - ytGroupsWithMembersMap[group.AzureID] = group + sourceGroup, err := a.buildSourceGroup(&group) + if err != nil { + return nil, errors.Wrap(err, "failed to create azure group from source") + } + ytGroupsWithMembersMap[sourceGroup.GetID()] = group } - // Collecting groups to create (the ones that exist in Azure but not in YTsaurus). - for azureID, azureGroupWithMembers := range azureGroupsWithMembersMap { - if _, ok := ytGroupsWithMembersMap[azureID]; !ok { - newYtsaurusGroup := a.buildYtsaurusGroup(azureGroupWithMembers.AzureGroup) + // Collecting groups to create (the ones that exist in Source but not in YTsaurus). + for objectID, sourceGroupWithMembers := range sourceGroupsWithMembersMap { + if _, ok := ytGroupsWithMembersMap[objectID]; !ok { + newYtsaurusGroup, err := a.buildYtsaurusGroup(sourceGroupWithMembers.SourceGroup) + if err != nil { + return nil, errors.Wrap(err, "failed to build Ytsaurus group") + } groupsToCreate = append(groupsToCreate, newYtsaurusGroup) - for username := range a.buildYtsaurusGroupMembers(azureGroupWithMembers, usersMap).Iter() { + for username := range a.buildYtsaurusGroupMembers(sourceGroupWithMembers, usersMap).Iter() { membersToAdd = append(membersToAdd, YtsaurusMembership{ GroupName: newYtsaurusGroup.Name, Username: username, @@ -221,17 +244,20 @@ func (a *App) diffGroups( } } - for azureID, ytGroupWithMembers := range ytGroupsWithMembersMap { + for objectID, ytGroupWithMembers := range ytGroupsWithMembersMap { // Collecting groups to remove (the ones that exist in YTsaurus and not in Azure). - azureGroupWithMembers, ok := azureGroupsWithMembersMap[azureID] + sourceGroupWithMembers, ok := sourceGroupsWithMembersMap[objectID] if !ok { groupsToRemove = append(groupsToRemove, ytGroupWithMembers.YtsaurusGroup) continue } - // Collecting groups with changed Azure fields (actually we have only displayName for now which + // Collecting groups with changed Source fields (actually we have only displayName for now which // should change, though we still handle that just in case). - groupChanged, updatedYtGroup := a.isGroupChanged(azureGroupWithMembers.AzureGroup, ytGroupWithMembers.YtsaurusGroup) + groupChanged, updatedYtGroup, err := a.isGroupChanged(sourceGroupWithMembers.SourceGroup, ytGroupWithMembers.YtsaurusGroup) + if err != nil { + return nil, errors.Wrap(err, "failed to check if group is changed") + } // Group name can change after update, so we ensure that correct one is used for membership updates. actualGroupname := ytGroupWithMembers.YtsaurusGroup.Name if groupChanged { @@ -244,7 +270,7 @@ func (a *App) diffGroups( actualGroupname = updatedYtGroup.YtsaurusGroup.Name } - membersCreate, membersRemove := a.isGroupMembersChanged(azureGroupWithMembers, ytGroupWithMembers, usersMap) + membersCreate, membersRemove := a.isGroupMembersChanged(sourceGroupWithMembers, ytGroupWithMembers, usersMap) for _, username := range membersCreate { membersToAdd = append(membersToAdd, YtsaurusMembership{ GroupName: actualGroupname, @@ -259,95 +285,149 @@ func (a *App) diffGroups( } } - return groupDiff{ + return &groupDiff{ groupsToCreate: groupsToCreate, groupsToUpdate: groupsToUpdate, groupsToRemove: groupsToRemove, membersToAdd: membersToAdd, membersToRemove: membersToRemove, - } + }, nil } type usersDiff struct { create []YtsaurusUser update []UpdatedYtsaurusUser remove []YtsaurusUser + result map[ObjectID]YtsaurusUser } func (a *App) diffUsers( - azureUsersMap map[AzureID]AzureUser, - ytUsersMap map[AzureID]YtsaurusUser, -) usersDiff { + sourceUsers []SourceUser, + ytUsers []YtsaurusUser, +) (*usersDiff, error) { + sourceUsersMap := make(map[ObjectID]SourceUser) + for _, user := range sourceUsers { + sourceUsersMap[user.GetID()] = user + } + + ytUsersMap := make(map[ObjectID]YtsaurusUser) + resultUsersMap := make(map[ObjectID]YtsaurusUser) + for _, user := range ytUsers { + sourceUser, err := a.buildSourceUser(&user) + if err != nil { + return nil, errors.Wrap(err, "failed to build source user") + } + ytUsersMap[sourceUser.GetID()] = user + resultUsersMap[sourceUser.GetID()] = user + } + var create, remove []YtsaurusUser var update []UpdatedYtsaurusUser - for azureID, azureUser := range azureUsersMap { - if _, ok := ytUsersMap[azureID]; !ok { - create = append(create, a.buildYtsaurusUser(azureUser)) + for objectID, sourceUser := range sourceUsersMap { + if _, ok := ytUsersMap[objectID]; !ok { + ytUser, err := a.buildYtsaurusUser(sourceUser) + if err != nil { + return nil, errors.Wrap(err, "failed to create Ytsaurus user from source user") + } + create = append(create, ytUser) + resultUsersMap[objectID] = ytUser } } - for azureID, ytUser := range ytUsersMap { - azureUser, ok := azureUsersMap[azureID] + + for objectID, ytUser := range ytUsersMap { + sourceUser, ok := sourceUsersMap[objectID] if !ok { remove = append(remove, ytUser) + delete(resultUsersMap, objectID) continue } - userChanged, updatedYtUser := a.isUserChanged(azureUser, ytUser) + newYtUser, err := a.buildYtsaurusUser(sourceUser) + if err != nil { + return nil, errors.Wrap(err, "failed to create Ytsaurus user from source user") + } + userChanged, updatedYtUser, err := a.isUserChanged(newYtUser, ytUser) + if err != nil { + return nil, errors.Wrap(err, "failed to check if user was changed") + } if !userChanged { continue } update = append(update, updatedYtUser) + resultUsersMap[objectID] = updatedYtUser.YtsaurusUser } - return usersDiff{ + return &usersDiff{ create: create, update: update, remove: remove, - } + result: resultUsersMap, + }, nil } -func (a *App) buildUsername(azureUser AzureUser) string { - username := azureUser.PrincipalName - for _, replace := range a.usernameReplaces { - username = strings.Replace(username, replace.From, replace.To, -1) +func (a *App) buildUsername(sourceUser SourceUser) string { + username := sourceUser.GetName() + if a.usernameReplaces != nil { + for _, replace := range a.usernameReplaces { + username = strings.Replace(username, replace.From, replace.To, -1) + } } username = strings.ToLower(username) return username } -func (a *App) buildGroupName(azureGroup AzureGroup) string { - name := azureGroup.Identity - for _, replace := range a.groupnameReplaces { - name = strings.Replace(name, replace.From, replace.To, -1) +func (a *App) buildGroupName(sourceGroup SourceGroup) string { + name := sourceGroup.GetName() + if a.groupnameReplaces != nil { + for _, replace := range a.groupnameReplaces { + name = strings.Replace(name, replace.From, replace.To, -1) + } } name = strings.ToLower(name) return name } -func (a *App) buildYtsaurusUser(azureUser AzureUser) YtsaurusUser { +func (a *App) buildSourceUser(ytUser *YtsaurusUser) (SourceUser, error) { + if ytUser.IsManuallyManaged() { + return nil, errors.New("user is manually managed and can't be converted to source user") + } + return a.source.CreateUserFromRaw(ytUser.SourceRaw) +} + +func (a *App) buildSourceGroup(ytGroup *YtsaurusGroupWithMembers) (SourceUser, error) { + if ytGroup.IsManuallyManaged() { + return nil, errors.New("user is manually managed and can't be converted to source user") + } + return a.source.CreateGroupFromRaw(ytGroup.SourceRaw) +} + +func (a *App) buildYtsaurusUser(sourceUser SourceUser) (YtsaurusUser, error) { + sourceRaw, err := sourceUser.GetRaw() + if err != nil { + return YtsaurusUser{}, err + } return YtsaurusUser{ - Username: a.buildUsername(azureUser), - AzureID: azureUser.AzureID, - PrincipalName: azureUser.PrincipalName, - Email: azureUser.Email, - FirstName: azureUser.FirstName, - LastName: azureUser.LastName, - DisplayName: azureUser.DisplayName, - // If we have Azure user —> he is not banned. + Username: a.buildUsername(sourceUser), + SourceRaw: sourceRaw, + // If we have Source user —> he is not banned. BannedSince: time.Time{}, - } + }, nil } -func (a *App) buildYtsaurusGroup(azureGroup AzureGroup) YtsaurusGroup { - return YtsaurusGroup{ - Name: a.buildGroupName(azureGroup), - AzureID: azureGroup.AzureID, - DisplayName: azureGroup.DisplayName, +func (a *App) buildYtsaurusGroup(sourceGroup SourceGroup) (YtsaurusGroup, error) { + sourceRaw, err := sourceGroup.GetRaw() + if err != nil { + return YtsaurusGroup{}, err } + + return YtsaurusGroup{ + Name: a.buildGroupName(sourceGroup), + SourceRaw: sourceRaw, + }, nil } -func (a *App) buildYtsaurusGroupMembers(azureGroupWithMembers AzureGroupWithMembers, usersMap map[AzureID]YtsaurusUser) StringSet { +func (a *App) buildYtsaurusGroupMembers(sourceGroupWithMembers SourceGroupWithMembers, usersMap map[ObjectID]YtsaurusUser) StringSet { members := NewStringSet() - for azureID := range azureGroupWithMembers.Members.Iter() { + for azureID := range sourceGroupWithMembers.Members.Iter() { ytUser, ok := usersMap[azureID] if !ok { // User is unknown to the YTsaurus (can be accountEnabled=false). @@ -366,12 +446,19 @@ type UpdatedYtsaurusUser struct { } // If isUserChanged detects that user is changed, it returns UpdatedYtsaurusUser. -func (a *App) isUserChanged(azureUser AzureUser, ytUser YtsaurusUser) (bool, UpdatedYtsaurusUser) { - newUser := a.buildYtsaurusUser(azureUser) - if newUser == ytUser { - return false, UpdatedYtsaurusUser{} +func (a *App) isUserChanged(newYtUser YtsaurusUser, ytUser YtsaurusUser) (bool, UpdatedYtsaurusUser, error) { + newSourceRaw, err := yson.Marshal(newYtUser.SourceRaw) + if err != nil { + return false, UpdatedYtsaurusUser{}, err + } + oldSourceRaw, err := yson.Marshal(ytUser.SourceRaw) + if err != nil { + return false, UpdatedYtsaurusUser{}, err + } + if newYtUser.Username == ytUser.Username && bytes.Equal(newSourceRaw, oldSourceRaw) && newYtUser.BannedSince == ytUser.BannedSince { + return false, UpdatedYtsaurusUser{}, nil } - return true, UpdatedYtsaurusUser{YtsaurusUser: newUser, OldUsername: ytUser.Username} + return true, UpdatedYtsaurusUser{YtsaurusUser: newYtUser, OldUsername: ytUser.Username}, nil } // UpdatedYtsaurusGroup is a wrapper for YtsaurusGroup, because it is handy to store old groupname for update, @@ -382,17 +469,28 @@ type UpdatedYtsaurusGroup struct { } // If isGroupChanged detects that group itself (not members) is changed, it returns UpdatedYtsaurusGroup. -func (a *App) isGroupChanged(azureGroup AzureGroup, ytGroup YtsaurusGroup) (bool, UpdatedYtsaurusGroup) { - newGroup := a.buildYtsaurusGroup(azureGroup) - if newGroup.Name == ytGroup.Name && newGroup.DisplayName == ytGroup.DisplayName { - return false, UpdatedYtsaurusGroup{} +func (a *App) isGroupChanged(sourceGroup SourceGroup, ytGroup YtsaurusGroup) (bool, UpdatedYtsaurusGroup, error) { + newGroup, err := a.buildYtsaurusGroup(sourceGroup) + if err != nil { + return false, UpdatedYtsaurusGroup{}, errors.Wrap(err, "failed to build Ytsaurus group") + } + newSourceRaw, err := yson.Marshal(newGroup.SourceRaw) + if err != nil { + return false, UpdatedYtsaurusGroup{}, err + } + oldSourceRaw, err := yson.Marshal(ytGroup.SourceRaw) + if err != nil { + return false, UpdatedYtsaurusGroup{}, err + } + if bytes.Equal(newSourceRaw, oldSourceRaw) { + return false, UpdatedYtsaurusGroup{}, nil } - return true, UpdatedYtsaurusGroup{YtsaurusGroup: newGroup, OldName: ytGroup.Name} + return true, UpdatedYtsaurusGroup{YtsaurusGroup: newGroup, OldName: ytGroup.Name}, nil } // If isGroupMembersChanged detects that group members are changed, it returns lists of usernames to create and remove. -func (a *App) isGroupMembersChanged(azureGroup AzureGroupWithMembers, ytGroup YtsaurusGroupWithMembers, usersMap map[AzureID]YtsaurusUser) (create, remove []string) { - newMembers := a.buildYtsaurusGroupMembers(azureGroup, usersMap) +func (a *App) isGroupMembersChanged(sourceGroup SourceGroupWithMembers, ytGroup YtsaurusGroupWithMembers, usersMap map[ObjectID]YtsaurusUser) (create, remove []string) { + newMembers := a.buildYtsaurusGroupMembers(sourceGroup, usersMap) oldMembers := ytGroup.Members create = newMembers.Difference(oldMembers).ToSlice() @@ -406,7 +504,7 @@ func (a *App) banOrRemoveUser(user YtsaurusUser) (wasBanned, wasRemoved bool, er return false, true, a.ytsaurus.RemoveUser(user.Username) } // If user is not already banned we should do it. - if !user.IsBanned() && a.banDuration != 0 { + if !user.IsBanned() { return true, false, a.ytsaurus.BanUser(user.Username) } // If user was banned longer than setting permits, we remove it. diff --git a/go.mod b/go.mod index f340080..baf7029 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,16 @@ go 1.20 require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 github.com/deckarep/golang-set/v2 v2.3.1 - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 github.com/jessevdk/go-flags v1.5.0 github.com/microsoft/kiota-abstractions-go v1.3.0 github.com/microsoftgraph/msgraph-sdk-go v1.24.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 - github.com/testcontainers/testcontainers-go v0.26.0 + github.com/testcontainers/testcontainers-go v0.28.0 go.uber.org/zap v1.26.0 + go.ytsaurus.tech/library/go/ptr v0.0.1 go.ytsaurus.tech/yt/go v0.0.13 gopkg.in/yaml.v3 v3.0.1 k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 @@ -26,18 +27,19 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.1 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cjlapao/common-go v0.0.39 // indirect - github.com/containerd/containerd v1.7.7 // indirect + github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.6+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/docker v25.0.2+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -47,7 +49,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/tink/go v1.7.0 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect @@ -62,23 +64,24 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect - github.com/opencontainers/runc v1.1.5 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/shirou/gopsutil/v3 v3.23.9 // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/std-uritemplate/std-uritemplate/go v0.0.42 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect @@ -87,18 +90,17 @@ require ( go.ytsaurus.tech/library/go/blockcodecs v0.0.2 // indirect go.ytsaurus.tech/library/go/core/log v0.0.3 // indirect go.ytsaurus.tech/library/go/core/xerrors v0.0.3 // indirect - go.ytsaurus.tech/library/go/ptr v0.0.1 // indirect go.ytsaurus.tech/library/go/x/xreflect v0.0.2 // indirect go.ytsaurus.tech/library/go/x/xruntime v0.0.3 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.10.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect - google.golang.org/grpc v1.57.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.3 // indirect google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/go.sum b/go.sum index 2eb4fa1..795c6e1 100644 --- a/go.sum +++ b/go.sum @@ -11,46 +11,39 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.1 h1:hJ3s7GbWlGK4YVV92sO88BQSyF4ZLVy7/awqOlPxFbA= -github.com/Microsoft/hcsshim v0.11.1/go.mod h1:nFJmaO4Zr5Y7eADdFOpYswDDlNVbvcIJJNJLECr5JQg= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/containerd v1.7.7 h1:QOC2K4A42RQpcrZyptP6z9EJZnlHfHJUfZrAAHe15q4= -github.com/containerd/containerd v1.7.7/go.mod h1:3c4XZv6VeT9qgf9GMTxNTMFxGJrGpI2vz1yk4ye+YY8= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A= github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= -github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/docker v25.0.2+incompatible h1:/OaKeauroa10K4Nqavw4zlhcDq/WBcPMc5DbjOGgozY= +github.com/docker/docker v25.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= 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= @@ -59,8 +52,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -72,25 +63,23 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= @@ -122,22 +111,18 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= @@ -151,16 +136,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= -github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/std-uritemplate/std-uritemplate/go v0.0.42 h1:rG+XlE4drkVWs2NLfGS15N+vg+CUcjXElQKvJ0fctlI= @@ -168,33 +149,34 @@ github.com/std-uritemplate/std-uritemplate/go v0.0.42/go.mod h1:Qov4Ay4U83j37Xjg github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/testcontainers/testcontainers-go v0.26.0 h1:uqcYdoOHBy1ca7gKODfBd9uTHVK3a7UL848z09MVZ0c= -github.com/testcontainers/testcontainers-go v0.26.0/go.mod h1:ICriE9bLX5CLxL9OFQ2N+2N+f+803LNJ1utJb1+Inx0= +github.com/testcontainers/testcontainers-go v0.28.0 h1:1HLm9qm+J5VikzFDYhOd+Zw12NtOl+8drH2E8nTY1r8= +github.com/testcontainers/testcontainers-go v0.28.0/go.mod h1:COlDpUXbwW3owtpMkEB1zo9gwb1CoKVKlyrVPejF4AU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= @@ -220,8 +202,8 @@ go.ytsaurus.tech/yt/go v0.0.13/go.mod h1:r3puwhqLgS/TD9VZ8/3rhuxtdD02bQ+yuZWncNu golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -232,7 +214,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -241,56 +222,47 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= -google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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= diff --git a/main.go b/main.go index 67f97af..f0330da 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func run(configFilePath string) error { return errors.Wrapf(err, "failed to load config %s", configFilePath) } - logger, err := configureLogger(cfg.Logging) + logger, err := configureLogger(&cfg.Logging) if err != nil { return errors.Wrapf(err, "failed to configure logging %+v", cfg.Logging) } diff --git a/migration_integration_test.go b/migration_integration_test.go index d54e763..9050876 100644 --- a/migration_integration_test.go +++ b/migration_integration_test.go @@ -61,7 +61,7 @@ func TestUsersMigration(t *testing.T) { t.Log("Got", len(ytUsersToUpdate), "users to update") for username, user := range ytUsersToUpdate { - attrValue := buildUserAzureAttributeValue(user) + attrValue := user.SourceUser if dryRun { t.Log("[DRY-RUN] will set @azure=", attrValue, "for", username) @@ -73,6 +73,7 @@ func TestUsersMigration(t *testing.T) { context.Background(), yt.client, username, + "azure", attrValue, ) require.NoError(t, err) @@ -125,7 +126,7 @@ func TestGroupsMigration(t *testing.T) { t.Log("Got", len(ytGroupsToUpdate), "groups to update") for groupname, group := range ytGroupsToUpdate { - attrValue := buildGroupAzureAttributeValue(group) + attrValue := group if dryRun { t.Log("[DRY-RUN] will set @azure=", attrValue, "for", groupname) continue @@ -136,6 +137,7 @@ func TestGroupsMigration(t *testing.T) { context.Background(), yt.client, groupname, + "azure", attrValue, ) require.NoError(t, err) diff --git a/ytsaurus.go b/ytsaurus.go index 20ae4d6..b38a4c8 100644 --- a/ytsaurus.go +++ b/ytsaurus.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "os" "time" @@ -16,6 +17,7 @@ import ( const ( defaultYtsaurusTimeout = 3 * time.Second defaultYtsaurusSecretEnvVar = "YT_TOKEN" + defaultSourceAttributeName = "source" ) type Ytsaurus struct { @@ -31,6 +33,8 @@ type Ytsaurus struct { debugUsernames []string debugGroupnames []string + + sourceAttributeName string } func NewYtsaurus(cfg *YtsaurusConfig, logger appLoggerType, clock clock.PassiveClock) (*Ytsaurus, error) { @@ -60,6 +64,9 @@ func NewYtsaurus(cfg *YtsaurusConfig, logger appLoggerType, clock clock.PassiveC if cfg.Timeout == 0 { cfg.Timeout = defaultYtsaurusTimeout } + if cfg.SourceAttributeName == "" { + cfg.SourceAttributeName = defaultSourceAttributeName + } return &Ytsaurus{ client: client, dryRunUsers: !cfg.ApplyUserChanges, @@ -70,8 +77,9 @@ func NewYtsaurus(cfg *YtsaurusConfig, logger appLoggerType, clock clock.PassiveC timeout: cfg.Timeout, clock: clock, - debugUsernames: cfg.DebugUsernames, - debugGroupnames: cfg.DebugGroupnames, + debugUsernames: cfg.DebugUsernames, + debugGroupnames: cfg.DebugGroupnames, + sourceAttributeName: cfg.SourceAttributeName, }, nil } @@ -79,7 +87,7 @@ func (y *Ytsaurus) GetUsers() ([]YtsaurusUser, error) { ctx, cancel := context.WithTimeout(context.Background(), y.timeout) defer cancel() - users, err := doGetAllYtsaurusUsers(ctx, y.client) + users, err := doGetAllYtsaurusUsers(ctx, y.client, y.sourceAttributeName) if err != nil { return nil, errors.Wrap(err, "failed to get ytsaurus users") } @@ -109,12 +117,13 @@ func (y *Ytsaurus) CreateUser(user YtsaurusUser) error { defer cancel() y.maybePrintExtraLogs(user.Username, "create_user", "user", user) + return doCreateYtsaurusUser( ctx, y.client, user.Username, map[string]any{ - "azure": buildUserAzureAttributeValue(user), + y.sourceAttributeName: user.SourceRaw, }, ) } @@ -142,7 +151,7 @@ func (y *Ytsaurus) UpdateUser(username string, user YtsaurusUser) error { ctx, y.client, username, - buildUserAttributes(user), + buildUserAttributes(user, y.sourceAttributeName), ) } @@ -198,7 +207,7 @@ func (y *Ytsaurus) GetGroupsWithMembers() ([]YtsaurusGroupWithMembers, error) { ctx, cancel := context.WithTimeout(context.Background(), y.timeout) defer cancel() - groups, err := doGetAllYtsaurusGroupsWithMembers(ctx, y.client) + groups, err := doGetAllYtsaurusGroupsWithMembers(ctx, y.client, y.sourceAttributeName) if err != nil { return nil, errors.Wrap(err, "failed to get ytsaurus groups") } @@ -233,7 +242,7 @@ func (y *Ytsaurus) CreateGroup(group YtsaurusGroup) error { y.client, group.Name, map[string]any{ - "azure": buildGroupAzureAttributeValue(group), + y.sourceAttributeName: group.SourceRaw, }, ) } @@ -260,7 +269,7 @@ func (y *Ytsaurus) UpdateGroup(groupname string, group YtsaurusGroup) error { ctx, y.client, groupname, - buildGroupAttributes(group), + buildGroupAttributes(group, y.sourceAttributeName), ) } @@ -332,12 +341,11 @@ func (y *Ytsaurus) isUserManaged(username string) (bool, error) { ctx, cancel := context.WithTimeout(context.Background(), y.timeout) defer cancel() - attrExists, err := y.client.NodeExists( + return y.client.NodeExists( ctx, - ypath.Path("//sys/users/"+username+"/@azure"), + ypath.Path(fmt.Sprintf("//sys/users/%v/@%v", username, y.sourceAttributeName)), nil, ) - return attrExists, err } func (y *Ytsaurus) ensureUserManaged(username string) error { @@ -355,12 +363,11 @@ func (y *Ytsaurus) isGroupManaged(name string) (bool, error) { ctx, cancel := context.WithTimeout(context.Background(), y.timeout) defer cancel() - attrExists, err := y.client.NodeExists( + return y.client.NodeExists( ctx, - ypath.Path("//sys/groups/"+name+"/@azure"), + ypath.Path(fmt.Sprintf("//sys/groups/%v/@%v", name, y.sourceAttributeName)), nil, ) - return attrExists, err } func (y *Ytsaurus) ensureGroupManaged(groupname string) error { diff --git a/ytsaurus_helpers.go b/ytsaurus_helpers.go index 6b23953..667435c 100644 --- a/ytsaurus_helpers.go +++ b/ytsaurus_helpers.go @@ -12,12 +12,17 @@ import ( // Lower level functions for reusing in tests. -func doGetAllYtsaurusUsers(ctx context.Context, client yt.Client) ([]YtsaurusUser, error) { +const ( + bannedSinceAttributeName = "banned_since" + bannedAttributeName = "banned" + membersAttributeName = "members" + nameAttributeName = "name" +) + +func doGetAllYtsaurusUsers(ctx context.Context, client yt.Client, sourceAttributeName string) ([]YtsaurusUser, error) { type YtsaurusUserResponse struct { - Name string `yson:",value"` - Azure map[string]string `yson:"azure,attr"` - Banned bool `yson:"banned,attr"` - BannedSince string `yson:"banned_since,attr"` + Name string `yson:",value"` + Attrs map[string]any `yson:",attrs"` } var response []YtsaurusUserResponse @@ -27,9 +32,9 @@ func doGetAllYtsaurusUsers(ctx context.Context, client yt.Client) ([]YtsaurusUse &response, &yt.ListNodeOptions{ Attributes: []string{ - "azure", - "banned", - "banned_since", + bannedAttributeName, + bannedSinceAttributeName, + sourceAttributeName, }, }, ) @@ -39,32 +44,31 @@ func doGetAllYtsaurusUsers(ctx context.Context, client yt.Client) ([]YtsaurusUse var users []YtsaurusUser for _, ytUser := range response { - var bannedSince time.Time - if ytUser.BannedSince != "" { - bannedSince, err = time.Parse(appTimeFormat, ytUser.BannedSince) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse @banned_since. %v", ytUser) + user := YtsaurusUser{ + Username: ytUser.Name, + } + + if ytUser.Attrs != nil { + if bannedSinceRaw, ok := ytUser.Attrs[bannedSinceAttributeName]; ok && bannedSinceRaw != "" { + user.BannedSince, err = time.Parse(appTimeFormat, bannedSinceRaw.(string)) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse @banned_since. %v", ytUser) + } + } + if sourceRaw, ok := ytUser.Attrs[sourceAttributeName]; ok { + user.SourceRaw = sourceRaw.(map[string]any) } } - users = append(users, YtsaurusUser{ - Username: ytUser.Name, - AzureID: ytUser.Azure["id"], - PrincipalName: ytUser.Azure["principal_name"], - Email: ytUser.Azure["email"], - FirstName: ytUser.Azure["first_name"], - LastName: ytUser.Azure["last_name"], - DisplayName: ytUser.Azure["display_name"], - BannedSince: bannedSince, - }) + + users = append(users, user) } return users, nil } -func doGetAllYtsaurusGroupsWithMembers(ctx context.Context, client yt.Client) ([]YtsaurusGroupWithMembers, error) { +func doGetAllYtsaurusGroupsWithMembers(ctx context.Context, client yt.Client, sourceAttributeName string) ([]YtsaurusGroupWithMembers, error) { type YtsaurusGroupReponse struct { - Name string `yson:",value"` - Azure map[string]string `yson:"azure,attr"` - Members []string `yson:"members,attr"` + Name string `yson:",value"` + Attrs map[string]any `yson:",attrs"` } var response []YtsaurusGroupReponse @@ -73,36 +77,47 @@ func doGetAllYtsaurusGroupsWithMembers(ctx context.Context, client yt.Client) ([ ypath.Path("//sys/groups"), &response, &yt.ListNodeOptions{ - Attributes: []string{"members", "azure"}, + Attributes: []string{ + membersAttributeName, + sourceAttributeName, + }, }, ) if err != nil { return nil, err } - var users []YtsaurusGroupWithMembers + var groups []YtsaurusGroupWithMembers for _, ytGroup := range response { members := NewStringSet() - for _, m := range ytGroup.Members { - members.Add(m) + + group := YtsaurusGroup{Name: ytGroup.Name} + + if ytGroup.Attrs != nil { + if membersRaw, ok := ytGroup.Attrs[membersAttributeName]; ok { + for _, m := range membersRaw.([]interface{}) { + members.Add(m.(string)) + } + } + + if sourceRaw, ok := ytGroup.Attrs[sourceAttributeName]; ok { + group.SourceRaw = sourceRaw.(map[string]any) + } } - users = append(users, YtsaurusGroupWithMembers{ - YtsaurusGroup: YtsaurusGroup{ - Name: ytGroup.Name, - AzureID: ytGroup.Azure["id"], - DisplayName: ytGroup.Azure["display_name"], - }, - Members: members, + + groups = append(groups, YtsaurusGroupWithMembers{ + YtsaurusGroup: group, + Members: members, }) } - return users, nil + return groups, nil } func doCreateYtsaurusUser(ctx context.Context, client yt.Client, username string, attrs map[string]any) error { if attrs == nil { attrs = make(map[string]any) } - attrs["name"] = username + attrs[nameAttributeName] = username _, err := client.CreateObject( ctx, yt.NodeUser, @@ -115,7 +130,7 @@ func doCreateYtsaurusGroup(ctx context.Context, client yt.Client, name string, a if attrs == nil { attrs = make(map[string]any) } - attrs["name"] = name + attrs[nameAttributeName] = name _, err := client.CreateObject( ctx, yt.NodeGroup, @@ -142,48 +157,27 @@ func doRemoveMemberYtsaurusGroup(ctx context.Context, client yt.Client, username ) } -func buildUserAzureAttributeValue(user YtsaurusUser) map[string]string { - return map[string]string{ - "id": user.AzureID, - "email": user.Email, - "principal_name": user.PrincipalName, - "first_name": user.FirstName, - "last_name": user.LastName, - "display_name": user.DisplayName, - } -} - -func buildUserAttributes(user YtsaurusUser) map[string]any { +func buildUserAttributes(user YtsaurusUser, sourceAttributeName string) map[string]any { return map[string]any{ - "azure": buildUserAzureAttributeValue(user), - "name": user.Username, - "banned_since": user.BannedSinceString(), - "banned": user.IsBanned(), - } -} - -func buildGroupAzureAttributeValue(group YtsaurusGroup) map[string]string { - return map[string]string{ - "id": group.AzureID, - "display_name": group.DisplayName, + nameAttributeName: user.Username, + bannedSinceAttributeName: user.BannedSinceString(), + bannedAttributeName: user.IsBanned(), + sourceAttributeName: user.SourceRaw, } } -func buildGroupAttributes(group YtsaurusGroup) map[string]any { +func buildGroupAttributes(group YtsaurusGroup, sourceAttributeName string) map[string]any { return map[string]any{ - "azure": map[string]string{ - "id": group.AzureID, - "display_name": group.DisplayName, - }, - "name": group.Name, + sourceAttributeName: group.SourceRaw, + nameAttributeName: group.Name, } } // nolint: unused -func doSetAzureAttributeForYtsaurusUser(ctx context.Context, client yt.Client, username string, attrValue map[string]string) error { +func doSetAzureAttributeForYtsaurusUser(ctx context.Context, client yt.Client, username string, attrName string, attrValue any) error { return client.SetNode( ctx, - ypath.Path("//sys/users/"+username+"/@azure"), + ypath.Path("//sys/users/"+username+"/@"+attrName), attrValue, nil, ) @@ -192,7 +186,7 @@ func doSetAzureAttributeForYtsaurusUser(ctx context.Context, client yt.Client, u func doSetAttributesForYtsaurusUser(ctx context.Context, client yt.Client, username string, attrs map[string]any) error { attrsCopy := make(map[string]any) for key, value := range attrs { - if key == "name" && value == username { + if key == nameAttributeName && value == username { // multiset_attributes returns an error: // `setting builtin attribute "name" ... user ... already exists` // on attempt to set same name for the existing user. @@ -210,10 +204,16 @@ func doSetAttributesForYtsaurusUser(ctx context.Context, client yt.Client, usern } // nolint: unused -func doSetAzureAttributeForYtsaurusGroup(ctx context.Context, client yt.Client, groupname string, attrValue map[string]string) error { +func doSetAzureAttributeForYtsaurusGroup( + ctx context.Context, + client yt.Client, + groupname string, + attrName string, + attrValue map[string]string, +) error { return client.SetNode( ctx, - ypath.Path("//sys/groups/"+groupname+"/@azure"), + ypath.Path("//sys/groups/"+groupname+"/@"+attrName), attrValue, nil, ) diff --git a/ytsaurus_models.go b/ytsaurus_models.go index 86585e2..dc34e12 100644 --- a/ytsaurus_models.go +++ b/ytsaurus_models.go @@ -6,23 +6,14 @@ import ( type YtsaurusUser struct { // Username is a unique @name attribute of a user. - Username string - // AzureID is non-human readable string like 2cd8a70c-9044-4488-b06a-c8461c39b296. - AzureID string - // PrincipalName is a unique human-readable login. - // It could be in form of email, but doesn't give guarantee that such email exists. - PrincipalName string - // Email is filled if Azure user has email. - Email string - FirstName string - LastName string - DisplayName string + Username string + SourceRaw map[string]any BannedSince time.Time } // IsManuallyManaged true if user doesn't have @azure attribute (system or manually created user). func (u YtsaurusUser) IsManuallyManaged() bool { - return u.AzureID == "" + return u.SourceRaw == nil } func (u YtsaurusUser) IsBanned() bool { @@ -38,9 +29,13 @@ func (u YtsaurusUser) BannedSinceString() string { type YtsaurusGroup struct { // Name is a unique @name attribute of a group. - Name string - AzureID string - DisplayName string + Name string + SourceRaw map[string]any +} + +// IsManuallyManaged true if group doesn't have @azure attribute (system or manually created group). +func (g YtsaurusGroup) IsManuallyManaged() bool { + return g.SourceRaw == nil } type YtsaurusGroupWithMembers struct { @@ -57,8 +52,3 @@ type YtsaurusMembership struct { GroupName string Username string } - -// IsManuallyManaged true if group doesn't have @azure attribute (system or manually created group). -func (u YtsaurusGroup) IsManuallyManaged() bool { - return u.AzureID == "" -} diff --git a/ytsaurus_test.go b/ytsaurus_test.go index 00af797..9482229 100644 --- a/ytsaurus_test.go +++ b/ytsaurus_test.go @@ -18,12 +18,13 @@ func getYtsaurus(t *testing.T, ytLocal *YtsaurusLocal) *Ytsaurus { require.NoError(t, os.Setenv("YT_TOKEN", ytDevToken)) yt, err := NewYtsaurus( &YtsaurusConfig{ - Proxy: ytLocal.GetProxy(), - Timeout: 10 * time.Minute, - LogLevel: "DEBUG", - ApplyUserChanges: true, - ApplyGroupChanges: true, - ApplyMemberChanges: true, + Proxy: ytLocal.GetProxy(), + Timeout: 10 * time.Minute, + LogLevel: "DEBUG", + ApplyUserChanges: true, + ApplyGroupChanges: true, + ApplyMemberChanges: true, + SourceAttributeName: "azure", }, getDevelopmentLogger(), clock.RealClock{}, ) @@ -41,15 +42,23 @@ func TestUpdateUserFirstName(t *testing.T) { defer func() { require.NoError(t, ytLocal.Stop()) }() yt := getYtsaurus(t, ytLocal) + const azureID = "fake-az-id-old" + managedOleg := YtsaurusUser{ - Username: "oleg", - AzureID: "fake-az-id-oleg", - FirstName: "Lego", + Username: "oleg", + SourceRaw: map[string]any{ + "id": azureID, + "first_name": "Lego", + }, } err := yt.CreateUser(managedOleg) require.NoError(t, err) - managedOleg.FirstName = "Oleg" + managedOleg.SourceRaw = map[string]any{ + "id": azureID, + "first_name": "Oleg", + } + updErr := yt.UpdateUser(managedOleg.Username, managedOleg) ytClient, err := ytLocal.GetClient() @@ -79,15 +88,19 @@ func TestGroups(t *testing.T) { managedOleg := YtsaurusUser{ Username: "oleg", - AzureID: "fake-az-id-oleg", + SourceRaw: map[string]any{ + "id": "fake-az-id-oleg", + }, } err = yt.CreateUser(managedOleg) require.NoError(t, err) managedOlegsGroup := YtsaurusGroup{ - Name: "olegs", - AzureID: "fake-az-id-olegs", - DisplayName: "This is group is for Olegs only", + Name: "olegs", + SourceRaw: map[string]any{ + "id": "fake-az-id-olegs", + "display_name": "This is group is for Olegs only", + }, } err = yt.CreateGroup(managedOlegsGroup) require.NoError(t, err) @@ -102,9 +115,11 @@ func TestGroups(t *testing.T) { require.Equal(t, []YtsaurusGroupWithMembers{ { YtsaurusGroup: YtsaurusGroup{ - Name: managedOlegsGroup.Name, - AzureID: managedOlegsGroup.AzureID, - DisplayName: managedOlegsGroup.DisplayName, + Name: managedOlegsGroup.Name, + SourceRaw: map[string]any{ + "id": managedOlegsGroup.SourceRaw["id"], + "display_name": managedOlegsGroup.SourceRaw["display_name"], + }, }, Members: members, }, @@ -118,9 +133,11 @@ func TestGroups(t *testing.T) { require.Equal(t, []YtsaurusGroupWithMembers{ { YtsaurusGroup: YtsaurusGroup{ - Name: managedOlegsGroup.Name, - AzureID: managedOlegsGroup.AzureID, - DisplayName: managedOlegsGroup.DisplayName, + Name: managedOlegsGroup.Name, + SourceRaw: map[string]any{ + "id": managedOlegsGroup.SourceRaw["id"], + "display_name": managedOlegsGroup.SourceRaw["display_name"], + }, }, Members: NewStringSet(), },