From e6837ef7a19a07e993c792135c6fd6611e822f1a Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Sat, 6 Aug 2022 23:09:49 -0700 Subject: [PATCH 01/26] register users clean --- cmd/src/users.go | 1 + cmd/src/users_clean.go | 81 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 cmd/src/users_clean.go diff --git a/cmd/src/users.go b/cmd/src/users.go index c189ae1b45..feb7ff0393 100644 --- a/cmd/src/users.go +++ b/cmd/src/users.go @@ -20,6 +20,7 @@ The commands are: get gets a user create creates a user account delete deletes a user account + clean deletes inactive users tag add/remove a tag on a user Use "src users [command] -h" for more information about a command. diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go new file mode 100644 index 0000000000..5f01c64a7e --- /dev/null +++ b/cmd/src/users_clean.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "flag" + "fmt" + + "github.com/sourcegraph/src-cli/internal/api" +) + +func init() { + usage := ` +Examples: + + $ src users clean -d 182 + +` + + flagSet := flag.NewFlagSet("clean", flag.ExitOnError) + usageFunc := func() { + fmt.Fprintf(flag.CommandLine.Output(), "Usage of 'src users %s':\n", flagSet.Name()) + flagSet.PrintDefaults() + fmt.Println(usage) + } + var ( + days = flagSet.Int("d", 1000, "Returns the first n users from the list. (use -1 for unlimited)") + apiFlags = api.NewFlags(flagSet) + ) + + handler := func(args []string) error { + flagSet.Parse(args) + + ctx := context.Background() + client := cfg.apiClient(apiFlags, flagSet.Output()) + + tmpl, err := parseTemplate("{{.Username}} {{.SiteAdmin}}") + if err != nil { + return err + } + vars := map[string]interface{}{ + "-d": api.NullInt(*days), + } + + query := `query Users( + $first: Int, + $query: String, +) { + users( +first: $first, + query: $query, + ) { + nodes { + ...UserFields + } + } +}` + userFragment + + var result struct { + Users struct { + Nodes []User + } + } + if ok, err := client.NewRequest(query, vars).Do(ctx, &result); err != nil || !ok { + return err + } + + for _, user := range result.Users.Nodes { + if err := execTemplate(tmpl, user); err != nil { + return err + } + } + return nil + } + + // Register the command. + usersCommands = append(usersCommands, &command{ + flagSet: flagSet, + handler: handler, + usageFunc: usageFunc, + }) +} From 4104aaee681e3425eec84f5aef5ee06fb6bebf5d Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Sun, 7 Aug 2022 00:48:52 -0700 Subject: [PATCH 02/26] added usage statistics to User Node type --- cmd/src/users.go | 14 ++++++++++++-- cmd/src/users_clean.go | 43 ++++++++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/cmd/src/users.go b/cmd/src/users.go index feb7ff0393..391203b204 100644 --- a/cmd/src/users.go +++ b/cmd/src/users.go @@ -60,6 +60,10 @@ fragment UserFields on User { email verified } + usageStatistics { + lastActiveTime + lastActiveCodeHostIntegrationTime + } url } ` @@ -72,11 +76,17 @@ type User struct { Organizations struct { Nodes []Org } - Emails []UserEmail - URL string + Emails []UserEmail + UsageStatistics UserUsageStatistics + URL string } type UserEmail struct { Email string Verified bool } + +type UserUsageStatistics struct { + LastActiveTime string + LastActiveCodeHostIntegrationTime string +} diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 5f01c64a7e..4cc116e6f5 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -33,7 +33,7 @@ Examples: ctx := context.Background() client := cfg.apiClient(apiFlags, flagSet.Output()) - tmpl, err := parseTemplate("{{.Username}} {{.SiteAdmin}}") + tmpl, err := parseTemplate("{{.Username}} {{.SiteAdmin}} {{(index .Emails 0).Email}} {{.UsageStatistics.LastActiveTime}}") if err != nil { return err } @@ -41,19 +41,34 @@ Examples: "-d": api.NullInt(*days), } - query := `query Users( - $first: Int, - $query: String, -) { - users( -first: $first, - query: $query, - ) { - nodes { - ...UserFields - } - } -}` + userFragment + query := ` +query Users($first: Int, $query: String) { + users(first: $first, query: $query) { + nodes { + id + username + displayName + siteAdmin + organizations { + nodes { + id + name + displayName + } + } + emails { + email + verified + } + usageStatistics { + lastActiveTime + lastActiveCodeHostIntegrationTime + } + url + } + } +} +` var result struct { Users struct { From 5bb1f27ccff35171f908df5cf8fb794811bbe942 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Sun, 7 Aug 2022 18:55:58 -0700 Subject: [PATCH 03/26] mark --- cmd/src/users.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/src/users.go b/cmd/src/users.go index 391203b204..f94dfbc2da 100644 --- a/cmd/src/users.go +++ b/cmd/src/users.go @@ -58,7 +58,7 @@ fragment UserFields on User { } emails { email - verified + verified } usageStatistics { lastActiveTime From 74a3d4384290cd44b8bc7a53fab56604c497b149 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Sun, 7 Aug 2022 19:16:56 -0700 Subject: [PATCH 04/26] define worker function for user removal --- cmd/src/users_clean.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 4cc116e6f5..58f5d63246 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -80,11 +80,12 @@ query Users($first: Int, $query: String) { } for _, user := range result.Users.Nodes { + delete_users_not_active_in(user.UsageStatistics.LastActiveTime, *days) if err := execTemplate(tmpl, user); err != nil { return err } } - return nil + return err } // Register the command. @@ -94,3 +95,8 @@ query Users($first: Int, $query: String) { usageFunc: usageFunc, }) } + +func delete_users_not_active_in(users_data string, days_threshold int) error { + fmt.Printf("%s -- %d\n", users_data, days_threshold) + return nil +} From 3b0aa520733a0548fb10392981609578cf77cf37 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Mon, 8 Aug 2022 10:35:47 -0700 Subject: [PATCH 05/26] before implementing time.Parse(time.RFC3339, payload.UsageStatistics.LastActiveTime) --- cmd/src/users_clean.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 58f5d63246..960c1d9a95 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "time" "github.com/sourcegraph/src-cli/internal/api" ) @@ -33,7 +34,7 @@ Examples: ctx := context.Background() client := cfg.apiClient(apiFlags, flagSet.Output()) - tmpl, err := parseTemplate("{{.Username}} {{.SiteAdmin}} {{(index .Emails 0).Email}} {{.UsageStatistics.LastActiveTime}}") + tmpl, err := parseTemplate("{{.Username}} {{.SiteAdmin}} {{(index .Emails 0).Email}}") if err != nil { return err } @@ -97,6 +98,21 @@ query Users($first: Int, $query: String) { } func delete_users_not_active_in(users_data string, days_threshold int) error { + timeNow := time.Now() + + fmt.Printf("Time now: %s\nLast Active: %s", timeNow, users_data) + query := `mutation DeleteUser( + $user: ID! +) { + deleteUser( + user: $user + ) { + alwaysNil + } +}` fmt.Printf("%s -- %d\n", users_data, days_threshold) + if days_threshold == 0 { + fmt.Println(query) + } return nil } From c8b390c786f08a8393a3b316bdb92ac7ccf5f2ab Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Mon, 8 Aug 2022 23:50:39 -0700 Subject: [PATCH 06/26] now computes time since last active --- cmd/src/users_clean.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 960c1d9a95..07c9776f29 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -97,10 +97,18 @@ query Users($first: Int, $query: String) { }) } -func delete_users_not_active_in(users_data string, days_threshold int) error { +func delete_users_not_active_in(usage string, days_threshold int) error { timeNow := time.Now() + timeLast, err := time.Parse(time.RFC3339, usage) + if err != nil { + fmt.Printf("failed to parse lastActive time: %s", err) + } + timeDiff := timeNow.Sub(timeLast) + if err != nil { + fmt.Printf("failed to diff lastActive to current time: %s", err) + } - fmt.Printf("Time now: %s\nLast Active: %s", timeNow, users_data) + fmt.Printf("Time now: %s\nLast active: %s\nTime diff: %d\n\n", timeNow, timeLast, int(timeDiff.Hours()/24)) query := `mutation DeleteUser( $user: ID! ) { @@ -110,7 +118,7 @@ func delete_users_not_active_in(users_data string, days_threshold int) error { alwaysNil } }` - fmt.Printf("%s -- %d\n", users_data, days_threshold) + fmt.Printf("%s -- %d\n", usage, days_threshold) if days_threshold == 0 { fmt.Println(query) } From 8941c36d1c7a137205d64e9add1d62c4541e511d Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Tue, 9 Aug 2022 02:27:23 -0700 Subject: [PATCH 07/26] added utility function structure --- cmd/src/users_clean.go | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 07c9776f29..4dc7910c83 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -24,8 +24,10 @@ Examples: fmt.Println(usage) } var ( - days = flagSet.Int("d", 1000, "Returns the first n users from the list. (use -1 for unlimited)") - apiFlags = api.NewFlags(flagSet) + days = flagSet.Int("d", 365, "Returns the first n users from the list. (use -1 for unlimited)") + noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") + sendEmail = flagSet.Bool("email", false, "send removed users an email") + apiFlags = api.NewFlags(flagSet) ) handler := func(args []string) error { @@ -81,7 +83,14 @@ query Users($first: Int, $query: String) { } for _, user := range result.Users.Nodes { - delete_users_not_active_in(user.UsageStatistics.LastActiveTime, *days) + fmt.Printf("PRINT: %v, %v", *noAdmin, *sendEmail) + daysSinceLastUse, err := timeSinceLastUse(user, *days) + if err != nil { + fmt.Print(err) + } + if daysSinceLastUse >= *days { + removeUser(user) + } if err := execTemplate(tmpl, user); err != nil { return err } @@ -97,18 +106,26 @@ query Users($first: Int, $query: String) { }) } -func delete_users_not_active_in(usage string, days_threshold int) error { +func timeSinceLastUse(user User, daysToDelete int) (int, error) { timeNow := time.Now() - timeLast, err := time.Parse(time.RFC3339, usage) + if user.UsageStatistics.LastActiveTime == "" { + fmt.Printf("%s at (%s) has no lastActive value\n", user.Username, user.Emails[0].Email) + return 0, nil + } + timeLast, err := time.Parse(time.RFC3339, user.UsageStatistics.LastActiveTime) if err != nil { fmt.Printf("failed to parse lastActive time: %s", err) } - timeDiff := timeNow.Sub(timeLast) + timeDiff := int(timeNow.Sub(timeLast).Hours() / 24) if err != nil { fmt.Printf("failed to diff lastActive to current time: %s", err) } - fmt.Printf("Time now: %s\nLast active: %s\nTime diff: %d\n\n", timeNow, timeLast, int(timeDiff.Hours()/24)) + fmt.Printf("Time now: %s\nLast active: %s\nTime diff: %d\n\n", timeNow, timeLast, timeDiff) + return timeDiff, err +} + +func removeUser(user User) error { query := `mutation DeleteUser( $user: ID! ) { @@ -118,9 +135,11 @@ func delete_users_not_active_in(usage string, days_threshold int) error { alwaysNil } }` - fmt.Printf("%s -- %d\n", usage, days_threshold) - if days_threshold == 0 { - fmt.Println(query) - } + fmt.Printf("Deleted user: %s\n%s", user.Username, query) + return nil +} + +func sendEmail(user *User) error { + fmt.Printf("This sent an email to %s", user.Emails[0].Email) return nil } From 3a5d5c5067402832a09a9072f50230d7196485a2 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Wed, 10 Aug 2022 02:02:25 -0700 Subject: [PATCH 08/26] initialize array to store users to be deleted --- cmd/src/users_clean.go | 55 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 4dc7910c83..0f675cd804 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "reflect" "time" "github.com/sourcegraph/src-cli/internal/api" @@ -24,10 +25,10 @@ Examples: fmt.Println(usage) } var ( - days = flagSet.Int("d", 365, "Returns the first n users from the list. (use -1 for unlimited)") - noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") - sendEmail = flagSet.Bool("email", false, "send removed users an email") - apiFlags = api.NewFlags(flagSet) + daysToDelete = flagSet.Int("d", 365, "Returns the first n users from the list. (use -1 for unlimited)") + noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") + toEmail = flagSet.Bool("email", false, "send removed users an email") + apiFlags = api.NewFlags(flagSet) ) handler := func(args []string) error { @@ -36,12 +37,12 @@ Examples: ctx := context.Background() client := cfg.apiClient(apiFlags, flagSet.Output()) - tmpl, err := parseTemplate("{{.Username}} {{.SiteAdmin}} {{(index .Emails 0).Email}}") - if err != nil { - return err - } + //tmpl, err := parseTemplate("{{.Username}} {{.SiteAdmin}} {{(index .Emails 0).Email}}") + //if err != nil { + // return err + //} vars := map[string]interface{}{ - "-d": api.NullInt(*days), + "-d": api.NullInt(*daysToDelete), } query := ` @@ -78,24 +79,31 @@ query Users($first: Int, $query: String) { Nodes []User } } + if ok, err := client.NewRequest(query, vars).Do(ctx, &result); err != nil || !ok { return err } + usersToDelete := make([]User, 0) for _, user := range result.Users.Nodes { - fmt.Printf("PRINT: %v, %v", *noAdmin, *sendEmail) - daysSinceLastUse, err := timeSinceLastUse(user, *days) + daysSinceLastUse, err := computeDaysSinceLastUse(user) if err != nil { - fmt.Print(err) + return err } - if daysSinceLastUse >= *days { - removeUser(user) + if daysSinceLastUse >= *daysToDelete { + usersToDelete = append(usersToDelete, user) + fmt.Printf("\nAdding %s to remove list: %d days since last active, remove after %d days inactive\n", user.Username, daysSinceLastUse, *daysToDelete) } - if err := execTemplate(tmpl, user); err != nil { - return err + } + for _, user := range usersToDelete { + removeUser(user) + if *toEmail { + sendEmail(user) } } - return err + fmt.Print(noAdmin) + fmt.Print(toEmail) + return nil } // Register the command. @@ -106,11 +114,12 @@ query Users($first: Int, $query: String) { }) } -func timeSinceLastUse(user User, daysToDelete int) (int, error) { +func computeDaysSinceLastUse(user User) (int, error) { timeNow := time.Now() + //TODO handle for null lastActiveTime = null if user.UsageStatistics.LastActiveTime == "" { - fmt.Printf("%s at (%s) has no lastActive value\n", user.Username, user.Emails[0].Email) - return 0, nil + fmt.Printf("\n%s has no lastActive value\n", user.Username) + return 9999, nil } timeLast, err := time.Parse(time.RFC3339, user.UsageStatistics.LastActiveTime) if err != nil { @@ -121,7 +130,6 @@ func timeSinceLastUse(user User, daysToDelete int) (int, error) { fmt.Printf("failed to diff lastActive to current time: %s", err) } - fmt.Printf("Time now: %s\nLast active: %s\nTime diff: %d\n\n", timeNow, timeLast, timeDiff) return timeDiff, err } @@ -135,11 +143,12 @@ func removeUser(user User) error { alwaysNil } }` - fmt.Printf("Deleted user: %s\n%s", user.Username, query) + reflect.TypeOf(query) + fmt.Printf("\nDeleted user: %s\n", user.Username) return nil } -func sendEmail(user *User) error { +func sendEmail(user User) error { fmt.Printf("This sent an email to %s", user.Emails[0].Email) return nil } From 25301b8553f540ed28953d0a63519319ae02d96d Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Fri, 12 Aug 2022 11:42:26 -0700 Subject: [PATCH 09/26] working query to remove users and flags --- cmd/src/users_clean.go | 80 +++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 0f675cd804..0f4b9cf8c3 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -4,7 +4,6 @@ import ( "context" "flag" "fmt" - "reflect" "time" "github.com/sourcegraph/src-cli/internal/api" @@ -25,10 +24,11 @@ Examples: fmt.Println(usage) } var ( - daysToDelete = flagSet.Int("d", 365, "Returns the first n users from the list. (use -1 for unlimited)") - noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") - toEmail = flagSet.Bool("email", false, "send removed users an email") - apiFlags = api.NewFlags(flagSet) + daysToDelete = flagSet.Int("d", 365, "Returns the first n users from the list. (use -1 for unlimited)") + noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") + toEmail = flagSet.Bool("email", false, "send removed users an email") + removeNoLastActive = flagSet.Bool("removeNeverActive", false, "removes users with null lastActive value") + apiFlags = api.NewFlags(flagSet) ) handler := func(args []string) error { @@ -49,30 +49,11 @@ Examples: query Users($first: Int, $query: String) { users(first: $first, query: $query) { nodes { - id - username - displayName - siteAdmin - organizations { - nodes { - id - name - displayName - } - } - emails { - email - verified - } - usageStatistics { - lastActiveTime - lastActiveCodeHostIntegrationTime - } - url + ...UserFields } } } -` +` + userFragment var result struct { Users struct { @@ -86,23 +67,32 @@ query Users($first: Int, $query: String) { usersToDelete := make([]User, 0) for _, user := range result.Users.Nodes { - daysSinceLastUse, err := computeDaysSinceLastUse(user) + daysSinceLastUse, wasLastActive, err := computeDaysSinceLastUse(user) if err != nil { return err } - if daysSinceLastUse >= *daysToDelete { - usersToDelete = append(usersToDelete, user) - fmt.Printf("\nAdding %s to remove list: %d days since last active, remove after %d days inactive\n", user.Username, daysSinceLastUse, *daysToDelete) + if !wasLastActive && !*removeNoLastActive { + continue } + if *noAdmin && user.SiteAdmin { + continue + } + if daysSinceLastUse <= *daysToDelete { + continue + } + + usersToDelete = append(usersToDelete, user) + fmt.Printf("\nAdding %s to remove list: %d days since last active, remove after %d days inactive\n", user.Username, daysSinceLastUse, *daysToDelete) } for _, user := range usersToDelete { - removeUser(user) + if err := removeUser(user, client, ctx); err != nil { + return err + } if *toEmail { sendEmail(user) } } - fmt.Print(noAdmin) - fmt.Print(toEmail) + return nil } @@ -114,26 +104,25 @@ query Users($first: Int, $query: String) { }) } -func computeDaysSinceLastUse(user User) (int, error) { +func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ error) { timeNow := time.Now() //TODO handle for null lastActiveTime = null if user.UsageStatistics.LastActiveTime == "" { fmt.Printf("\n%s has no lastActive value\n", user.Username) - return 9999, nil + wasLastActive = false + return 0, wasLastActive, nil } timeLast, err := time.Parse(time.RFC3339, user.UsageStatistics.LastActiveTime) if err != nil { fmt.Printf("failed to parse lastActive time: %s", err) + return 0, false, err } - timeDiff := int(timeNow.Sub(timeLast).Hours() / 24) - if err != nil { - fmt.Printf("failed to diff lastActive to current time: %s", err) - } + timeDiff = int(timeNow.Sub(timeLast).Hours() / 24) - return timeDiff, err + return timeDiff, true, err } -func removeUser(user User) error { +func removeUser(user User, client api.Client, ctx context.Context) error { query := `mutation DeleteUser( $user: ID! ) { @@ -143,8 +132,13 @@ func removeUser(user User) error { alwaysNil } }` - reflect.TypeOf(query) - fmt.Printf("\nDeleted user: %s\n", user.Username) + vars := map[string]interface{}{ + "user": user.ID, + } + if ok, err := client.NewRequest(query, vars).Do(ctx, nil); err != nil || !ok { + return err + } + fmt.Printf("\nDeleted user %s: %s\n", user.ID, user.Username) return nil } From c6d6bc8ea1a923ff57afa5846d7dfb0ae33b5060 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 18 Aug 2022 00:18:02 -0700 Subject: [PATCH 10/26] added a user verification to command --- cmd/src/users_clean.go | 46 ++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 0f4b9cf8c3..3abfe42d85 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "strings" "time" "github.com/sourcegraph/src-cli/internal/api" @@ -24,7 +25,7 @@ Examples: fmt.Println(usage) } var ( - daysToDelete = flagSet.Int("d", 365, "Returns the first n users from the list. (use -1 for unlimited)") + daysToDelete = flagSet.Int("d", 365, "Day threshold on which to remove users, defaults to 365") noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") toEmail = flagSet.Bool("email", false, "send removed users an email") removeNoLastActive = flagSet.Bool("removeNeverActive", false, "removes users with null lastActive value") @@ -37,10 +38,6 @@ Examples: ctx := context.Background() client := cfg.apiClient(apiFlags, flagSet.Output()) - //tmpl, err := parseTemplate("{{.Username}} {{.SiteAdmin}} {{(index .Emails 0).Email}}") - //if err != nil { - // return err - //} vars := map[string]interface{}{ "-d": api.NullInt(*daysToDelete), } @@ -55,12 +52,12 @@ query Users($first: Int, $query: String) { } ` + userFragment + // get users to delete var result struct { Users struct { Nodes []User } } - if ok, err := client.NewRequest(query, vars).Do(ctx, &result); err != nil || !ok { return err } @@ -84,12 +81,20 @@ query Users($first: Int, $query: String) { usersToDelete = append(usersToDelete, user) fmt.Printf("\nAdding %s to remove list: %d days since last active, remove after %d days inactive\n", user.Username, daysSinceLastUse, *daysToDelete) } - for _, user := range usersToDelete { - if err := removeUser(user, client, ctx); err != nil { - return err - } - if *toEmail { - sendEmail(user) + + // confirm and remove users + if confirmed, _ := confirmUserRemoval(usersToDelete); !confirmed { + fmt.Println("Aborting removal") + return nil + } else { + fmt.Println("REMOVING USERS") + for _, user := range usersToDelete { + if err := removeUser(user, client, ctx); err != nil { + return err + } + if *toEmail { + sendEmail(user) + } } } @@ -106,7 +111,7 @@ query Users($first: Int, $query: String) { func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ error) { timeNow := time.Now() - //TODO handle for null lastActiveTime = null + // handle for null lastActiveTime returned from if user.UsageStatistics.LastActiveTime == "" { fmt.Printf("\n%s has no lastActive value\n", user.Username) wasLastActive = false @@ -142,6 +147,21 @@ func removeUser(user User, client api.Client, ctx context.Context) error { return nil } +func confirmUserRemoval(usersToRemove []User) (bool, error) { + fmt.Printf("The following users will be removed from your Sourcegraph instance:\n") + for _, user := range usersToRemove { + fmt.Printf("\t%s %s %s\n", user.Username, user.DisplayName, user.Emails[0].Email) + } + input := "" + for strings.ToLower(input) != "y" && strings.ToLower(input) != "n" { + fmt.Printf("Do you wish to proceed with user removal [y/N]: ") + if _, err := fmt.Scanln(&input); err != nil { + return false, err + } + } + return strings.ToLower(input) == "y", nil +} + func sendEmail(user User) error { fmt.Printf("This sent an email to %s", user.Emails[0].Email) return nil From a4cfba491a9cb5fee3c2c162d71aca48a9728736 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 18 Aug 2022 01:12:00 -0700 Subject: [PATCH 11/26] corrects logic around removeNeverActive flag --- cmd/src/users_clean.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 3abfe42d85..42080abe2f 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -74,7 +74,7 @@ query Users($first: Int, $query: String) { if *noAdmin && user.SiteAdmin { continue } - if daysSinceLastUse <= *daysToDelete { + if daysSinceLastUse <= *daysToDelete && wasLastActive { continue } @@ -113,7 +113,6 @@ func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ err timeNow := time.Now() // handle for null lastActiveTime returned from if user.UsageStatistics.LastActiveTime == "" { - fmt.Printf("\n%s has no lastActive value\n", user.Username) wasLastActive = false return 0, wasLastActive, nil } @@ -150,7 +149,11 @@ func removeUser(user User, client api.Client, ctx context.Context) error { func confirmUserRemoval(usersToRemove []User) (bool, error) { fmt.Printf("The following users will be removed from your Sourcegraph instance:\n") for _, user := range usersToRemove { - fmt.Printf("\t%s %s %s\n", user.Username, user.DisplayName, user.Emails[0].Email) + if len(user.Emails) > 0 { + fmt.Printf("\t%s %s %s\n", user.Username, user.DisplayName, user.Emails[0].Email) + } else { + fmt.Printf("\t%s %s\n", user.Username, user.DisplayName) + } } input := "" for strings.ToLower(input) != "y" && strings.ToLower(input) != "n" { From 1093e14a68e0c10767758f1398e92f7e8b80f776 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 18 Aug 2022 01:23:22 -0700 Subject: [PATCH 12/26] better warning messaging --- cmd/src/users_clean.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 42080abe2f..01303c9234 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -147,12 +147,12 @@ func removeUser(user User, client api.Client, ctx context.Context) error { } func confirmUserRemoval(usersToRemove []User) (bool, error) { - fmt.Printf("The following users will be removed from your Sourcegraph instance:\n") + fmt.Printf("Users to remove from instance at %s:\n", cfg.Endpoint) for _, user := range usersToRemove { if len(user.Emails) > 0 { - fmt.Printf("\t%s %s %s\n", user.Username, user.DisplayName, user.Emails[0].Email) + fmt.Printf("\t\t%s %s %s\n", user.Username, user.DisplayName, user.Emails[0].Email) } else { - fmt.Printf("\t%s %s\n", user.Username, user.DisplayName) + fmt.Printf("\t\t%s %s\n", user.Username, user.DisplayName) } } input := "" From 7f399eb0124cbc8aa5f0a354582eb32db594135c Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 18 Aug 2022 01:57:42 -0700 Subject: [PATCH 13/26] formating warning --- cmd/src/users_clean.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 01303c9234..3e35b07136 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -62,7 +62,7 @@ query Users($first: Int, $query: String) { return err } - usersToDelete := make([]User, 0) + usersToDelete := make([]UserToDelete, 0) for _, user := range result.Users.Nodes { daysSinceLastUse, wasLastActive, err := computeDaysSinceLastUse(user) if err != nil { @@ -77,9 +77,10 @@ query Users($first: Int, $query: String) { if daysSinceLastUse <= *daysToDelete && wasLastActive { continue } + deleteUser := UserToDelete{user, daysSinceLastUse} - usersToDelete = append(usersToDelete, user) - fmt.Printf("\nAdding %s to remove list: %d days since last active, remove after %d days inactive\n", user.Username, daysSinceLastUse, *daysToDelete) + usersToDelete = append(usersToDelete, deleteUser) + //fmt.Printf("\nAdding %s to remove list: %d days since last active, remove after %d days inactive\n", user.Username, daysSinceLastUse, *daysToDelete) } // confirm and remove users @@ -89,11 +90,11 @@ query Users($first: Int, $query: String) { } else { fmt.Println("REMOVING USERS") for _, user := range usersToDelete { - if err := removeUser(user, client, ctx); err != nil { + if err := removeUser(user.User, client, ctx); err != nil { return err } if *toEmail { - sendEmail(user) + sendEmail(user.User) } } } @@ -142,17 +143,21 @@ func removeUser(user User, client api.Client, ctx context.Context) error { if ok, err := client.NewRequest(query, vars).Do(ctx, nil); err != nil || !ok { return err } - fmt.Printf("\nDeleted user %s: %s\n", user.ID, user.Username) return nil } -func confirmUserRemoval(usersToRemove []User) (bool, error) { - fmt.Printf("Users to remove from instance at %s:\n", cfg.Endpoint) +type UserToDelete struct { + User User + DaysSinceLastUse int +} + +func confirmUserRemoval(usersToRemove []UserToDelete) (bool, error) { + fmt.Printf("Users to remove from instance at %s \n\t\t(Username|DisplayName|Email|DaysSinceLastActive)\n", cfg.Endpoint) for _, user := range usersToRemove { - if len(user.Emails) > 0 { - fmt.Printf("\t\t%s %s %s\n", user.Username, user.DisplayName, user.Emails[0].Email) + if len(user.User.Emails) > 0 { + fmt.Printf("\t\t%s %s %s %d\n", user.User.Username, user.User.DisplayName, user.User.Emails[0].Email, user.DaysSinceLastUse) } else { - fmt.Printf("\t\t%s %s\n", user.Username, user.DisplayName) + fmt.Printf("\t\t%s %s %d\n", user.User.Username, user.User.DisplayName, user.DaysSinceLastUse) } } input := "" From febdc29fab033b5d2b128d40ac12d12772282e0c Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Wed, 24 Aug 2022 01:15:42 -0700 Subject: [PATCH 14/26] add flag to skip verify check --- cmd/src/users_clean.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 3e35b07136..ea3c64e6cf 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -29,11 +29,14 @@ Examples: noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") toEmail = flagSet.Bool("email", false, "send removed users an email") removeNoLastActive = flagSet.Bool("removeNeverActive", false, "removes users with null lastActive value") + skipConfirmation = flagSet.Bool("skip-conf", false, "skips user confirmation step allowing programmatic use") apiFlags = api.NewFlags(flagSet) ) handler := func(args []string) error { - flagSet.Parse(args) + if err := flagSet.Parse(args); err != nil { + fmt.Printf("failed to parse command args with error: %s", err) + } ctx := context.Background() client := cfg.apiClient(apiFlags, flagSet.Output()) @@ -83,6 +86,20 @@ query Users($first: Int, $query: String) { //fmt.Printf("\nAdding %s to remove list: %d days since last active, remove after %d days inactive\n", user.Username, daysSinceLastUse, *daysToDelete) } + if *skipConfirmation { + for _, user := range usersToDelete { + if err := removeUser(user.User, client, ctx); err != nil { + return err + } + if *toEmail { + if err := sendEmail(user.User); err != nil { + fmt.Printf("failer to send email to %s with error: %s", user.User.Emails[0].Email, err) + } + } + } + return nil + } + // confirm and remove users if confirmed, _ := confirmUserRemoval(usersToDelete); !confirmed { fmt.Println("Aborting removal") @@ -94,7 +111,9 @@ query Users($first: Int, $query: String) { return err } if *toEmail { - sendEmail(user.User) + if err := sendEmail(user.User); err != nil { + fmt.Printf("failer to send email to %s with error: %s", user.User.Emails[0].Email, err) + } } } } From 9fad30c075f20fea25e38126d4399f246b79a5c2 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Wed, 24 Aug 2022 01:26:18 -0700 Subject: [PATCH 15/26] commented out placeholder code and added TODO comments --- cmd/src/users_clean.go | 43 ++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index ea3c64e6cf..2ea7b2ee9c 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -25,12 +25,15 @@ Examples: fmt.Println(usage) } var ( - daysToDelete = flagSet.Int("d", 365, "Day threshold on which to remove users, defaults to 365") - noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") - toEmail = flagSet.Bool("email", false, "send removed users an email") + daysToDelete = flagSet.Int("d", 365, "Day threshold on which to remove users, defaults to 365") + noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") + // TODO: Add an email functionality to email removed users, open editor like in git commit + // toEmail = flagSet.Bool("email", false, "send removed users an email") removeNoLastActive = flagSet.Bool("removeNeverActive", false, "removes users with null lastActive value") skipConfirmation = flagSet.Bool("skip-conf", false, "skips user confirmation step allowing programmatic use") - apiFlags = api.NewFlags(flagSet) + // TODO: Write json file containing users to be deleted, last active usage, site-admin status, and emails + // json = flagSet.Bool("json", false, "Write json file containing users to be deleted, last active usage, site-admin status, and emails") + apiFlags = api.NewFlags(flagSet) ) handler := func(args []string) error { @@ -91,11 +94,12 @@ query Users($first: Int, $query: String) { if err := removeUser(user.User, client, ctx); err != nil { return err } - if *toEmail { - if err := sendEmail(user.User); err != nil { - fmt.Printf("failer to send email to %s with error: %s", user.User.Emails[0].Email, err) - } - } + // TODO: see flag toEmail + //if *toEmail { + // if err := sendEmail(user.User); err != nil { + // fmt.Printf("failed to send email to %s with error: %s", user.User.Emails[0].Email, err) + // } + //} } return nil } @@ -110,11 +114,12 @@ query Users($first: Int, $query: String) { if err := removeUser(user.User, client, ctx); err != nil { return err } - if *toEmail { - if err := sendEmail(user.User); err != nil { - fmt.Printf("failer to send email to %s with error: %s", user.User.Emails[0].Email, err) - } - } + // TODO: see flag toEmail + //if *toEmail { + // if err := sendEmail(user.User); err != nil { + // fmt.Printf("failed to send email to %s with error: %s", user.User.Emails[0].Email, err) + // } + //} } } @@ -170,6 +175,7 @@ type UserToDelete struct { DaysSinceLastUse int } +// TODO: improve formatting of list output func confirmUserRemoval(usersToRemove []UserToDelete) (bool, error) { fmt.Printf("Users to remove from instance at %s \n\t\t(Username|DisplayName|Email|DaysSinceLastActive)\n", cfg.Endpoint) for _, user := range usersToRemove { @@ -189,7 +195,8 @@ func confirmUserRemoval(usersToRemove []UserToDelete) (bool, error) { return strings.ToLower(input) == "y", nil } -func sendEmail(user User) error { - fmt.Printf("This sent an email to %s", user.Emails[0].Email) - return nil -} +// TODO: function to execute email +//func sendEmail(user User) error { +// fmt.Printf("This sent an email to %s", user.Emails[0].Email) +// return nil +//} From 7a6ea3a765dae41ba375c921487e62bcd21e9c4a Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Wed, 31 Aug 2022 23:59:51 -0700 Subject: [PATCH 16/26] addressed many review concerns, added lower bound on -days flag, made better table --- cmd/src/users_clean.go | 71 ++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 2ea7b2ee9c..332574d7fc 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -4,18 +4,24 @@ import ( "context" "flag" "fmt" + "os" "strings" "time" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/sourcegraph/src-cli/internal/api" ) func init() { usage := ` +This command removes users from a Sourcegraph instance who have been inactive for 60 or more days. + Examples: - $ src users clean -d 182 - + $ src users clean -days 182 + + $ src users clean -noAdmin -removeNeverActive ` flagSet := flag.NewFlagSet("clean", flag.ExitOnError) @@ -25,29 +31,25 @@ Examples: fmt.Println(usage) } var ( - daysToDelete = flagSet.Int("d", 365, "Day threshold on which to remove users, defaults to 365") - noAdmin = flagSet.Bool("no-admin", false, "Omit admin accounts from cleanup") - // TODO: Add an email functionality to email removed users, open editor like in git commit - // toEmail = flagSet.Bool("email", false, "send removed users an email") + daysToDelete = flagSet.Int("days", 60, "Days threshold on which to remove users, must be 60 days or greater and defaults to this value ") + noAdmin = flagSet.Bool("noAdmin", false, "Omit admin accounts from cleanup") removeNoLastActive = flagSet.Bool("removeNeverActive", false, "removes users with null lastActive value") - skipConfirmation = flagSet.Bool("skip-conf", false, "skips user confirmation step allowing programmatic use") - // TODO: Write json file containing users to be deleted, last active usage, site-admin status, and emails - // json = flagSet.Bool("json", false, "Write json file containing users to be deleted, last active usage, site-admin status, and emails") - apiFlags = api.NewFlags(flagSet) + skipConfirmation = flagSet.Bool("force", false, "skips user confirmation step allowing programmatic use") + apiFlags = api.NewFlags(flagSet) ) handler := func(args []string) error { if err := flagSet.Parse(args); err != nil { - fmt.Printf("failed to parse command args with error: %s", err) + return err + } + if *daysToDelete < 60 { + fmt.Println("-days flag must be set to 60 or greater") + return nil } ctx := context.Background() client := cfg.apiClient(apiFlags, flagSet.Output()) - vars := map[string]interface{}{ - "-d": api.NullInt(*daysToDelete), - } - query := ` query Users($first: Int, $query: String) { users(first: $first, query: $query) { @@ -64,7 +66,7 @@ query Users($first: Int, $query: String) { Nodes []User } } - if ok, err := client.NewRequest(query, vars).Do(ctx, &result); err != nil || !ok { + if ok, err := client.NewRequest(query, nil).Do(ctx, &result); err != nil || !ok { return err } @@ -86,7 +88,6 @@ query Users($first: Int, $query: String) { deleteUser := UserToDelete{user, daysSinceLastUse} usersToDelete = append(usersToDelete, deleteUser) - //fmt.Printf("\nAdding %s to remove list: %d days since last active, remove after %d days inactive\n", user.Username, daysSinceLastUse, *daysToDelete) } if *skipConfirmation { @@ -94,12 +95,6 @@ query Users($first: Int, $query: String) { if err := removeUser(user.User, client, ctx); err != nil { return err } - // TODO: see flag toEmail - //if *toEmail { - // if err := sendEmail(user.User); err != nil { - // fmt.Printf("failed to send email to %s with error: %s", user.User.Emails[0].Email, err) - // } - //} } return nil } @@ -114,12 +109,6 @@ query Users($first: Int, $query: String) { if err := removeUser(user.User, client, ctx); err != nil { return err } - // TODO: see flag toEmail - //if *toEmail { - // if err := sendEmail(user.User); err != nil { - // fmt.Printf("failed to send email to %s with error: %s", user.User.Emails[0].Email, err) - // } - //} } } @@ -134,6 +123,7 @@ query Users($first: Int, $query: String) { }) } +// computes days since last usage from current day and time and UsageStatistics.LastActiveTime, uses time.Parse func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ error) { timeNow := time.Now() // handle for null lastActiveTime returned from @@ -143,7 +133,6 @@ func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ err } timeLast, err := time.Parse(time.RFC3339, user.UsageStatistics.LastActiveTime) if err != nil { - fmt.Printf("failed to parse lastActive time: %s", err) return 0, false, err } timeDiff = int(timeNow.Sub(timeLast).Hours() / 24) @@ -151,6 +140,7 @@ func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ err return timeDiff, true, err } +// Issue graphQL api request to remove user func removeUser(user User, client api.Client, ctx context.Context) error { query := `mutation DeleteUser( $user: ID! @@ -175,16 +165,23 @@ type UserToDelete struct { DaysSinceLastUse int } -// TODO: improve formatting of list output +// Verify user wants to remove users with table of users and a command prompt for [y/N] func confirmUserRemoval(usersToRemove []UserToDelete) (bool, error) { - fmt.Printf("Users to remove from instance at %s \n\t\t(Username|DisplayName|Email|DaysSinceLastActive)\n", cfg.Endpoint) + fmt.Printf("Users to remove from instance at %s\n", cfg.Endpoint) + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Username", "Email", "Days Since Last Active"}) for _, user := range usersToRemove { if len(user.User.Emails) > 0 { - fmt.Printf("\t\t%s %s %s %d\n", user.User.Username, user.User.DisplayName, user.User.Emails[0].Email, user.DaysSinceLastUse) + t.AppendRow([]interface{}{user.User.Username, user.User.Emails[0].Email, user.DaysSinceLastUse}) + t.AppendSeparator() } else { - fmt.Printf("\t\t%s %s %d\n", user.User.Username, user.User.DisplayName, user.DaysSinceLastUse) + t.AppendRow([]interface{}{user.User.Username, "", user.DaysSinceLastUse}) + t.AppendSeparator() } } + t.SetStyle(table.StyleRounded) + t.Render() input := "" for strings.ToLower(input) != "y" && strings.ToLower(input) != "n" { fmt.Printf("Do you wish to proceed with user removal [y/N]: ") @@ -194,9 +191,3 @@ func confirmUserRemoval(usersToRemove []UserToDelete) (bool, error) { } return strings.ToLower(input) == "y", nil } - -// TODO: function to execute email -//func sendEmail(user User) error { -// fmt.Printf("This sent an email to %s", user.Emails[0].Email) -// return nil -//} From e11fede1b06215d755351694a606ae342fe87568 Mon Sep 17 00:00:00 2001 From: Warren Gifford Date: Thu, 1 Sep 2022 00:04:38 -0700 Subject: [PATCH 17/26] Update cmd/src/users.go Co-authored-by: Thorsten Ball --- cmd/src/users.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/src/users.go b/cmd/src/users.go index f94dfbc2da..f79d9e3bbc 100644 --- a/cmd/src/users.go +++ b/cmd/src/users.go @@ -58,12 +58,12 @@ fragment UserFields on User { } emails { email - verified + verified + } + usageStatistics { + lastActiveTime + lastActiveCodeHostIntegrationTime } - usageStatistics { - lastActiveTime - lastActiveCodeHostIntegrationTime - } url } ` From be8a1c5dd23704870c1e8b7d152602cdd3e209ad Mon Sep 17 00:00:00 2001 From: Warren Gifford Date: Thu, 1 Sep 2022 09:47:45 -0700 Subject: [PATCH 18/26] Update cmd/src/users_clean.go Co-authored-by: Thorsten Ball --- cmd/src/users_clean.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 332574d7fc..7c9969ebab 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -142,12 +142,8 @@ func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ err // Issue graphQL api request to remove user func removeUser(user User, client api.Client, ctx context.Context) error { - query := `mutation DeleteUser( - $user: ID! -) { - deleteUser( - user: $user - ) { + query := `mutation DeleteUser($user: ID!) { + deleteUser(user: $user) { alwaysNil } }` From 01300f6219042039a0fda1ccb06a0c2ba448db15 Mon Sep 17 00:00:00 2001 From: Warren Gifford Date: Thu, 1 Sep 2022 15:57:37 -0700 Subject: [PATCH 19/26] Update cmd/src/users_clean.go Co-authored-by: Thorsten Ball --- cmd/src/users_clean.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 7c9969ebab..2fb32c1aa4 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -125,17 +125,16 @@ query Users($first: Int, $query: String) { // computes days since last usage from current day and time and UsageStatistics.LastActiveTime, uses time.Parse func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ error) { - timeNow := time.Now() // handle for null lastActiveTime returned from if user.UsageStatistics.LastActiveTime == "" { wasLastActive = false return 0, wasLastActive, nil } - timeLast, err := time.Parse(time.RFC3339, user.UsageStatistics.LastActiveTime) + lastActive, err := time.Parse(time.RFC3339, user.UsageStatistics.LastActiveTime) if err != nil { return 0, false, err } - timeDiff = int(timeNow.Sub(timeLast).Hours() / 24) + timeDiff = int(time.Since(timeLast).Hours() / 24) return timeDiff, true, err } From c64202486671dda201e3f11ee8cf6e48e98f6b0a Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 1 Sep 2022 16:35:26 -0700 Subject: [PATCH 20/26] correct variable naming bug --- cmd/src/users_clean.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 2fb32c1aa4..90534dbff5 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -130,7 +130,7 @@ func computeDaysSinceLastUse(user User) (timeDiff int, wasLastActive bool, _ err wasLastActive = false return 0, wasLastActive, nil } - lastActive, err := time.Parse(time.RFC3339, user.UsageStatistics.LastActiveTime) + timeLast, err := time.Parse(time.RFC3339, user.UsageStatistics.LastActiveTime) if err != nil { return 0, false, err } From 918e36f862e0ce22e97cc5f4c0d5c8fd136b9862 Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 1 Sep 2022 16:42:26 -0700 Subject: [PATCH 21/26] remove unused params in get users query --- cmd/src/users_clean.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 90534dbff5..942079a7d9 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -51,8 +51,8 @@ Examples: client := cfg.apiClient(apiFlags, flagSet.Output()) query := ` -query Users($first: Int, $query: String) { - users(first: $first, query: $query) { +query Users() { + users() { nodes { ...UserFields } From b396f326adb1a16dbbf40741b2ace4fa023598f2 Mon Sep 17 00:00:00 2001 From: Warren Gifford Date: Thu, 8 Sep 2022 01:37:39 -0700 Subject: [PATCH 22/26] Update cmd/src/users.go Co-authored-by: Joe Chen --- cmd/src/users.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/src/users.go b/cmd/src/users.go index f79d9e3bbc..38d4cfdc00 100644 --- a/cmd/src/users.go +++ b/cmd/src/users.go @@ -58,7 +58,7 @@ fragment UserFields on User { } emails { email - verified + verified } usageStatistics { lastActiveTime From d170645d6c715b8f19f1b4b3e35d88ac770f3a6c Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 8 Sep 2022 01:44:45 -0700 Subject: [PATCH 23/26] admins must be explcitly removed --- cmd/src/users_clean.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 942079a7d9..e6d7037406 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -15,13 +15,13 @@ import ( func init() { usage := ` -This command removes users from a Sourcegraph instance who have been inactive for 60 or more days. +This command removes users from a Sourcegraph instance who have been inactive for 60 or more days. Admin accounts are omitted by default. Examples: $ src users clean -days 182 - $ src users clean -noAdmin -removeNeverActive + $ src users clean -removeAdmin -removeNeverActive ` flagSet := flag.NewFlagSet("clean", flag.ExitOnError) @@ -32,7 +32,7 @@ Examples: } var ( daysToDelete = flagSet.Int("days", 60, "Days threshold on which to remove users, must be 60 days or greater and defaults to this value ") - noAdmin = flagSet.Bool("noAdmin", false, "Omit admin accounts from cleanup") + removeAdmin = flagSet.Bool("removeAdmin", false, "clean admin accounts") removeNoLastActive = flagSet.Bool("removeNeverActive", false, "removes users with null lastActive value") skipConfirmation = flagSet.Bool("force", false, "skips user confirmation step allowing programmatic use") apiFlags = api.NewFlags(flagSet) @@ -79,7 +79,7 @@ query Users() { if !wasLastActive && !*removeNoLastActive { continue } - if *noAdmin && user.SiteAdmin { + if !*removeAdmin && user.SiteAdmin { continue } if daysSinceLastUse <= *daysToDelete && wasLastActive { From 3065b9fac88d05fc634064a800b0d2124752919e Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 8 Sep 2022 09:24:32 -0700 Subject: [PATCH 24/26] commit dependencies --- go.mod | 1 + go.sum | 3 +++ 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index 4688c68922..923bcc71fb 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/google/go-cmp v0.5.8 github.com/grafana/regexp v0.0.0-20220304100321-149c8afcd6cb github.com/hexops/autogold v1.3.0 + github.com/jedib0t/go-pretty/v6 v6.3.7 github.com/jig/teereadcloser v0.0.0-20181016160506-953720c48e05 github.com/json-iterator/go v1.1.12 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 diff --git a/go.sum b/go.sum index 547852d34c..25ba1fefe8 100644 --- a/go.sum +++ b/go.sum @@ -198,6 +198,8 @@ github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0Gqw github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a h1:d4+I1YEKVmWZrgkt6jpXBnLgV2ZjO0YxEtLDdfIZfH4= github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw= +github.com/jedib0t/go-pretty/v6 v6.3.7 h1:H3Ulkf7h6A+p0HgKBGzgDn0bZIupRbKKWF4pO4Bs7iA= +github.com/jedib0t/go-pretty/v6 v6.3.7/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= @@ -396,6 +398,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= From 010ff3b1730b6c1e36d6e8a206d17fcdc26c3d9a Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 8 Sep 2022 10:51:23 -0700 Subject: [PATCH 25/26] camel case --- cmd/src/users_clean.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index e6d7037406..68c611be86 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -21,7 +21,7 @@ Examples: $ src users clean -days 182 - $ src users clean -removeAdmin -removeNeverActive + $ src users clean -remove-admin -remove-never-active ` flagSet := flag.NewFlagSet("clean", flag.ExitOnError) @@ -32,8 +32,8 @@ Examples: } var ( daysToDelete = flagSet.Int("days", 60, "Days threshold on which to remove users, must be 60 days or greater and defaults to this value ") - removeAdmin = flagSet.Bool("removeAdmin", false, "clean admin accounts") - removeNoLastActive = flagSet.Bool("removeNeverActive", false, "removes users with null lastActive value") + removeAdmin = flagSet.Bool("remove-admin", false, "clean admin accounts") + removeNoLastActive = flagSet.Bool("remove-never-active", false, "removes users with null lastActive value") skipConfirmation = flagSet.Bool("force", false, "skips user confirmation step allowing programmatic use") apiFlags = api.NewFlags(flagSet) ) From 817d78e41d851981cabf226406b603932feac41f Mon Sep 17 00:00:00 2001 From: DaedalusG Date: Thu, 8 Sep 2022 13:40:16 -0700 Subject: [PATCH 26/26] ensure clean doesnt clean the user issuing the command --- cmd/src/users_clean.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/cmd/src/users_clean.go b/cmd/src/users_clean.go index 68c611be86..adcd5ddbd7 100644 --- a/cmd/src/users_clean.go +++ b/cmd/src/users_clean.go @@ -50,7 +50,26 @@ Examples: ctx := context.Background() client := cfg.apiClient(apiFlags, flagSet.Output()) - query := ` + currentUserQuery := ` +query { + currentUser { + username + } +} +` + var currentUserResult struct { + Data struct { + CurrentUser struct { + Username string + } + } + } + if ok, err := cfg.apiClient(apiFlags, flagSet.Output()).NewRequest(currentUserQuery, nil).DoRaw(context.Background(), ¤tUserResult); err != nil || !ok { + return err + } + fmt.Println(currentUserResult) + + usersQuery := ` query Users() { users() { nodes { @@ -61,21 +80,26 @@ query Users() { ` + userFragment // get users to delete - var result struct { + var usersResult struct { Users struct { Nodes []User } } - if ok, err := client.NewRequest(query, nil).Do(ctx, &result); err != nil || !ok { + if ok, err := client.NewRequest(usersQuery, nil).Do(ctx, &usersResult); err != nil || !ok { return err } + fmt.Println(usersResult) usersToDelete := make([]UserToDelete, 0) - for _, user := range result.Users.Nodes { + for _, user := range usersResult.Users.Nodes { daysSinceLastUse, wasLastActive, err := computeDaysSinceLastUse(user) if err != nil { return err } + // never remove user issuing command + if user.Username == currentUserResult.Data.CurrentUser.Username { + continue + } if !wasLastActive && !*removeNoLastActive { continue }