Skip to content
Snippets Groups Projects
Commit 834f68fe authored by Theo's avatar Theo :troll:
Browse files

Refactor: remodel some create.go functions and add comments

parent 99e8d1a1
Branches
No related tags found
No related merge requests found
...@@ -14,6 +14,8 @@ var rootCmd = &cobra.Command{ ...@@ -14,6 +14,8 @@ var rootCmd = &cobra.Command{
with the cobra library that took inspiration from the old with the cobra library that took inspiration from the old
useradm.py. It takes care of LDAP configuration and was useradm.py. It takes care of LDAP configuration and was
made as an update for the previous version.`, made as an update for the previous version.`,
// since we already print the errors in Execute()
// not having this would print the error twice :p
SilenceErrors: true, SilenceErrors: true,
} }
......
...@@ -8,6 +8,9 @@ import ( ...@@ -8,6 +8,9 @@ import (
var userCmd = &cobra.Command{ var userCmd = &cobra.Command{
Use: "user", Use: "user",
Short: "User subcommand", Short: "User subcommand",
Long: `Subcommand for managing unique users in general.
If what you are trying to do is create a lot of
new users, please do so with the bulk subcommand.`,
} }
func init() { func init() {
......
This diff is collapsed.
...@@ -37,8 +37,10 @@ func init() { ...@@ -37,8 +37,10 @@ func init() {
func deleteUserFunc(cmd *cobra.Command, args []string) error { func deleteUserFunc(cmd *cobra.Command, args []string) error {
success := false success := false
userLogin, err := cmd.Flags().GetString("login")
userLogin, err := getFlagString(cmd, "login")
if err != nil { return err } if err != nil { return err }
confirm, err := cmd.Flags().GetBool("confirm") confirm, err := cmd.Flags().GetBool("confirm")
if err != nil { return err } if err != nil { return err }
...@@ -68,25 +70,31 @@ func deleteUserFunc(cmd *cobra.Command, args []string) error { ...@@ -68,25 +70,31 @@ func deleteUserFunc(cmd *cobra.Command, args []string) error {
if err != nil { return err } if err != nil { return err }
fmt.Printf("\nUser removed!\n") fmt.Printf("\nUser removed!\n")
success = true success = true
return nil return nil
} }
// searches for the specified login string, there can only be one >:)
func locateUser(login string) (model.User, error) { func locateUser(login string) (model.User, error) {
var u model.User var u model.User
users, err := getUsers() users, err := getUsers()
if !loginExists(users, login) { if !loginExists(users, login) {
return u, fmt.Errorf("Failed to find login in LDAP database: %v", err) return u, fmt.Errorf("Failed to find login in LDAP database: %v", err)
} }
filter := searchUser(users, false, login, "", "", "", "", "") filter := searchUser(users, false, login, "", "", "", "", "")
if len(filter) != 1 { if len(filter) != 1 {
return u, fmt.Errorf(`More than one user matched the login given. return u, fmt.Errorf(`More than one user matched the login given.
search made: "useradm user show -l %v"`, login) search made: "useradm user show -l %v"`, login)
} }
u = filter[0] u = filter[0]
return u, nil return u, nil
} }
// moves dirs to their respective trash dir
func removeDirs(u model.User) error { func removeDirs(u model.User) error {
err := moveAndChown(u.Homedir, HOME_TRASH, "nobody", "nogroup") err := moveAndChown(u.Homedir, HOME_TRASH, "nobody", "nogroup")
if err != nil { return err } if err != nil { return err }
...@@ -107,21 +115,23 @@ func delUserLDAP(u model.User) error { ...@@ -107,21 +115,23 @@ func delUserLDAP(u model.User) error {
} }
defer l.Close() defer l.Close()
// Remove user from all groups // first remove user from all groups
searchReq := ldap.NewSearchRequest( searchReq := ldap.NewSearchRequest(
"ou=grupos,dc=c3local,dc=com", "ou=grupos,dc=c3local,dc=com",
ldap.ScopeWholeSubtree, ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, 0, 0, false, ldap.NeverDerefAliases, 0, 0, false,
"(memberUid=" + u.UID + ")", // Filter by UID membership "(memberUid=" + u.UID + ")", // filter by UID membership
[]string{"dn"}, []string{"dn"},
nil, nil,
) )
// searches groups for target
groups, err := l.Search(searchReq) groups, err := l.Search(searchReq)
if err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { if err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
return fmt.Errorf("Group members search failed: %v", err) return fmt.Errorf("Group members search failed: %v", err)
} }
// removing from groups
for _, entry := range groups.Entries { for _, entry := range groups.Entries {
modReq := ldap.NewModifyRequest(entry.DN, nil) modReq := ldap.NewModifyRequest(entry.DN, nil)
modReq.Delete("memberUid", []string{u.UID}) modReq.Delete("memberUid", []string{u.UID})
...@@ -131,7 +141,7 @@ func delUserLDAP(u model.User) error { ...@@ -131,7 +141,7 @@ func delUserLDAP(u model.User) error {
} }
} }
// Delete user entry // removing user entry
delReq := ldap.NewDelRequest(u.DN, nil) delReq := ldap.NewDelRequest(u.DN, nil)
if err := l.Del(delReq); err != nil && if err := l.Del(delReq); err != nil &&
!ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
...@@ -141,6 +151,7 @@ func delUserLDAP(u model.User) error { ...@@ -141,6 +151,7 @@ func delUserLDAP(u model.User) error {
return nil return nil
} }
// usually not necessary but used in a failed create command
func delKerberosPrincipal(login string) error { func delKerberosPrincipal(login string) error {
cmd := exec.Command("kadmin.local", "-q", fmt.Sprintf("delprinc -force %s", login)) cmd := exec.Command("kadmin.local", "-q", fmt.Sprintf("delprinc -force %s", login))
...@@ -153,26 +164,26 @@ func delKerberosPrincipal(login string) error { ...@@ -153,26 +164,26 @@ func delKerberosPrincipal(login string) error {
} }
func moveAndChown(orig, dest, owner, group string) error { func moveAndChown(orig, dest, owner, group string) error {
// Check if orig exists // check if orig exists
if _, err := os.Stat(orig); err != nil { if _, err := os.Stat(orig); err != nil {
log.Printf("Directory %v not found so not moved\n", orig) log.Printf("Directory %v not found so not moved\n", orig)
return nil return nil
} }
// Construct destination path // construct destination path
destPath := filepath.Join(dest, filepath.Base(orig)) destPath := filepath.Join(dest, filepath.Base(orig))
if _, err := os.Stat(destPath); err == nil { if _, err := os.Stat(destPath); err == nil {
return fmt.Errorf("Directory %v already exists\n", destPath) return fmt.Errorf("Directory %v already exists, can't move\n", destPath)
} }
// Move directory using shell command // move directory
cmd := exec.Command("mv", orig, destPath) cmd := exec.Command("mv", orig, destPath)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("Failed to move dirs: %w\nOutput: %v", err, output) return fmt.Errorf("Failed to move dirs: %w\nOutput: %v", err, output)
} }
// Recursive chown using shell command // recursive chown
cmd = exec.Command("chown", "-R", owner+":"+group, destPath) cmd = exec.Command("chown", "-R", owner+":"+group, destPath)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("Failed to set owner/group: %w\nOutput: %v", err, output) return fmt.Errorf("Failed to set owner/group: %w\nOutput: %v", err, output)
......
...@@ -19,6 +19,7 @@ var ModCmd = &cobra.Command{ ...@@ -19,6 +19,7 @@ var ModCmd = &cobra.Command{
} }
func init() { func init() {
// possible flags
ModCmd.Flags().StringP("grr", "r", "", "New user GRR") ModCmd.Flags().StringP("grr", "r", "", "New user GRR")
ModCmd.Flags().StringP("shell", "s", "", "New user shell") ModCmd.Flags().StringP("shell", "s", "", "New user shell")
ModCmd.Flags().StringP("login", "l", "", "User login name") ModCmd.Flags().StringP("login", "l", "", "User login name")
...@@ -30,6 +31,7 @@ func init() { ...@@ -30,6 +31,7 @@ func init() {
ModCmd.Flags().StringP("password", "p", "", "New password in plain text (use \"auto\" to autogenerate)") ModCmd.Flags().StringP("password", "p", "", "New password in plain text (use \"auto\" to autogenerate)")
ModCmd.Flags().BoolP ("block", "b", false, "Block the user (changes the password and sets their shell to /bin/false)") ModCmd.Flags().BoolP ("block", "b", false, "Block the user (changes the password and sets their shell to /bin/false)")
// required flags
ModCmd.MarkFlagRequired("login") ModCmd.MarkFlagRequired("login")
ModCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt") ModCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt")
...@@ -129,6 +131,7 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error { ...@@ -129,6 +131,7 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error {
return nil return nil
} }
// do NOT include ':' in the charset, it WILL break the command
func genPassword() string { func genPassword() string {
const charset = "@*()=+[];,.?123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ" const charset = "@*()=+[];,.?123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
b := make([]byte, 20) b := make([]byte, 20)
...@@ -141,11 +144,14 @@ func genPassword() string { ...@@ -141,11 +144,14 @@ func genPassword() string {
return string(b) return string(b)
} }
// prints a confirmation prompt, given the operation being performed
func confirmationPrompt(confirm bool, operation string) { func confirmationPrompt(confirm bool, operation string) {
if !confirm { if !confirm {
fmt.Printf("Proceed with user %v? [y/N] ", operation) fmt.Printf("Proceed with user %v? [y/N] ", operation)
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
response, _ := reader.ReadString('\n') response, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(response)) != "y" { if strings.TrimSpace(strings.ToLower(response)) != "y" {
fmt.Fprintln(os.Stderr, "Aborted.") fmt.Fprintln(os.Stderr, "Aborted.")
os.Exit(1) os.Exit(1)
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
) )
const ( const (
NUM_GECOS_FIELDS = 9 NUM_GECOS_FIELDS = 9 // change if going to add new field
PASSWD_PATH = "/etc/ldapscripts/ldapscripts.passwd" PASSWD_PATH = "/etc/ldapscripts/ldapscripts.passwd"
) )
...@@ -24,7 +24,7 @@ var ShowCmd = &cobra.Command{ ...@@ -24,7 +24,7 @@ var ShowCmd = &cobra.Command{
} }
func init() { func init() {
// Possible search fields // possible search fields
ShowCmd.Flags().StringP("grr", "r", "", "Search by user GRR") ShowCmd.Flags().StringP("grr", "r", "", "Search by user GRR")
ShowCmd.Flags().StringP("name", "n", "", "Search by user name (case sensitive)") ShowCmd.Flags().StringP("name", "n", "", "Search by user name (case sensitive)")
ShowCmd.Flags().StringP("login", "l", "", "Search by login") ShowCmd.Flags().StringP("login", "l", "", "Search by login")
...@@ -32,27 +32,34 @@ func init() { ...@@ -32,27 +32,34 @@ func init() {
ShowCmd.Flags().StringP("status", "s", "", "Search by user status (Active/Blocked)") ShowCmd.Flags().StringP("status", "s", "", "Search by user status (Active/Blocked)")
ShowCmd.Flags().StringP("homedir", "d", "", "Search by user homedir") ShowCmd.Flags().StringP("homedir", "d", "", "Search by user homedir")
ShowCmd.Flags().BoolP("ignore", "i", false, "Make the search case-insensitive") // at least one is required!
// At least one is required!
ShowCmd.MarkFlagsOneRequired("grr", "name", "login", "group", "homedir", "status") ShowCmd.MarkFlagsOneRequired("grr", "name", "login", "group", "homedir", "status")
ShowCmd.Flags().BoolP("ignore", "i", false, "Make the search case-insensitive")
} }
func searchUserFunc(cmd *cobra.Command, args []string) error { func searchUserFunc(cmd *cobra.Command, args []string) error {
grr, err := cmd.Flags().GetString("grr") grr, err := getFlagString(cmd,"grr")
if err != nil { return err } if err != nil { return err }
name, err := cmd.Flags().GetString("name")
name, err := getFlagString(cmd,"name")
if err != nil { return err } if err != nil { return err }
login, err := cmd.Flags().GetString("login")
login, err := getFlagString(cmd,"login")
if err != nil { return err } if err != nil { return err }
group, err := cmd.Flags().GetString("group")
group, err := getFlagString(cmd,"group")
if err != nil { return err } if err != nil { return err }
homedir, err := cmd.Flags().GetString("homedir")
homedir, err := getFlagString(cmd,"homedir")
if err != nil { return err } if err != nil { return err }
status, err := cmd.Flags().GetString("status")
status, err := getFlagString(cmd,"status")
if err != nil { return err } if err != nil { return err }
ig, err := cmd.Flags().GetBool("ignore") ig, err := cmd.Flags().GetBool("ignore")
if err != nil { return err } if err != nil { return err }
users, err := getUsers() users, err := getUsers()
if err != nil { return err } if err != nil { return err }
...@@ -75,7 +82,7 @@ func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.Us ...@@ -75,7 +82,7 @@ func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.Us
strings.ToLower(substr), strings.ToLower(substr),
) )
} }
return strings.Contains(src, substr) return strings.Contains(src, substr) // normal search
} }
// Search // Search
...@@ -88,7 +95,7 @@ func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.Us ...@@ -88,7 +95,7 @@ func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.Us
}) })
} }
// Generic function for filtering data types, in this case the model User // generic function for filtering data types, in this case the model User
func Filter[T any](slice []T, predicate func(T) bool) []T { func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T var result []T
for _, v := range slice { for _, v := range slice {
...@@ -100,17 +107,15 @@ func Filter[T any](slice []T, predicate func(T) bool) []T { ...@@ -100,17 +107,15 @@ func Filter[T any](slice []T, predicate func(T) bool) []T {
} }
func getUsers() ([]model.User, error) { func getUsers() ([]model.User, error) {
var users []model.User var users []model.User
// Connect
l, err := connLDAP() l, err := connLDAP()
if err != nil { if err != nil {
return users, err return users, err
} }
defer l.Close() defer l.Close()
// Create the LDAP search request // create the LDAP search request
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
"dc=c3local", // search base "dc=c3local", // search base
ldap.ScopeWholeSubtree, // scope ldap.ScopeWholeSubtree, // scope
...@@ -124,21 +129,21 @@ func getUsers() ([]model.User, error) { ...@@ -124,21 +129,21 @@ func getUsers() ([]model.User, error) {
nil, nil,
) )
// Perform the search // perform the search
sr, err := l.Search(searchRequest) sr, err := l.Search(searchRequest)
if err != nil { if err != nil {
err = fmt.Errorf("Failed to fetch users from LDAP: %v", err) err = fmt.Errorf("Failed to fetch users from LDAP: %v", err)
return nil, err return nil, err
} }
// Get all the groups and ids // get all the groups and ids
groups, err := getGroups() groups, err := getGroups()
if err != nil { if err != nil {
err = fmt.Errorf("Failed to fetch groups and gids from LDAP: %v", err) err = fmt.Errorf("Failed to fetch groups and gids from LDAP: %v", err)
return nil, err return nil, err
} }
// Iterate over the search results // iterate over the search results
for _, entry := range sr.Entries { for _, entry := range sr.Entries {
shell := entry.GetAttributeValue("loginShell") shell := entry.GetAttributeValue("loginShell")
...@@ -158,7 +163,7 @@ func getUsers() ([]model.User, error) { ...@@ -158,7 +163,7 @@ func getUsers() ([]model.User, error) {
gidNumber := user.GIDNumber gidNumber := user.GIDNumber
// Safe assignment // safe assignment :)
groupName, exists := groups[gidNumber] groupName, exists := groups[gidNumber]
if !exists { if !exists {
fmt.Printf("WARNING: no group found for GIDNumber %s, user %s. Continuing...", gidNumber, user.UID) fmt.Printf("WARNING: no group found for GIDNumber %s, user %s. Continuing...", gidNumber, user.UID)
...@@ -174,21 +179,21 @@ func getUsers() ([]model.User, error) { ...@@ -174,21 +179,21 @@ func getUsers() ([]model.User, error) {
} }
func connLDAP() (*ldap.Conn, error) { func connLDAP() (*ldap.Conn, error) {
// Connect to the LDAP server // connect to the LDAP server
l, err := ldap.DialURL("ldapi:///") l, err := ldap.DialURL("ldapi:///")
if err != nil { if err != nil {
err = fmt.Errorf("Failed to connect to LDAP: %v", err) err = fmt.Errorf("Failed to connect to LDAP: %v", err)
return nil, err return nil, err
} }
// Get admin credentials // get admin credentials
password, err := getLDAPPassword(PASSWD_PATH) password, err := getLDAPPassword(PASSWD_PATH)
if err != nil { if err != nil {
err = fmt.Errorf("Failed to read LDAP password: %v", err) err = fmt.Errorf("Failed to read LDAP password: %v", err)
return nil, err return nil, err
} }
// Bind using admin credentials // bind using admin credentials
err = l.Bind("cn=admin,dc=c3local", password) err = l.Bind("cn=admin,dc=c3local", password)
if err != nil { if err != nil {
l.Close() l.Close()
...@@ -211,16 +216,16 @@ func getLDAPPassword(path string) (string, error) { ...@@ -211,16 +216,16 @@ func getLDAPPassword(path string) (string, error) {
return password, nil return password, nil
} }
// Campos do GECOS: // Gecos Fields:
// 0. Nome completo do titular da conta // 0. users full name
// 1. GRR do aluno // 1. users grr
// 2. Professor responsavel // 2. responsible professor
// 3. Curso/minicurso do usuario (ate para temporarios) // 3. users course/minicourse (even temp)
// 4. Validade da conta (data maxima para delecao) // 4. account expiry date (may be used for deletion)
// 5. Status da conta (Blocked/Active) // 5. account status (Blocked/Active)
// 6. Tipo de login (ini, first, last) // 6. login type (ini, first or last)
// 7. Webdir do usuario // 7. users webdir
// 8. Nobackup do usuario // 8. users nobackup dir
// 9. to be continued... // 9. to be continued...
func parseGecos(user model.User) model.User { func parseGecos(user model.User) model.User {
result := [NUM_GECOS_FIELDS]string{0: "_"} result := [NUM_GECOS_FIELDS]string{0: "_"}
...@@ -238,10 +243,11 @@ func parseGecos(user model.User) model.User { ...@@ -238,10 +243,11 @@ func parseGecos(user model.User) model.User {
user.GRR = result[1] user.GRR = result[1]
user.Resp = result[2] user.Resp = result[2]
user.Course = result[3] user.Course = result[3]
user.Expiry = result[4]
// only set with gecos if empty
if user.Status == "_"{ if user.Status == "_"{
user.Status = result[5] user.Status = result[5]
} }
user.Expiry = result[4]
user.Ltype = result[6] user.Ltype = result[6]
user.Webdir = result[7] user.Webdir = result[7]
user.Nobackup = result[8] user.Nobackup = result[8]
...@@ -249,9 +255,7 @@ func parseGecos(user model.User) model.User { ...@@ -249,9 +255,7 @@ func parseGecos(user model.User) model.User {
return user return user
} }
func getGroups() (map[string]string, error) { func getGroups() (map[string]string, error) {
groupMap := make(map[string]string) groupMap := make(map[string]string)
l, err := connLDAP() l, err := connLDAP()
...@@ -260,22 +264,24 @@ func getGroups() (map[string]string, error) { ...@@ -260,22 +264,24 @@ func getGroups() (map[string]string, error) {
} }
defer l.Close() defer l.Close()
// build search
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
"dc=c3local", "dc=c3local",
ldap.ScopeWholeSubtree, ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, ldap.NeverDerefAliases,
0, 0, 0, 0, false,
false,
"(&(objectClass=posixGroup))", "(&(objectClass=posixGroup))",
[]string{"gidNumber", "cn"}, []string{"gidNumber", "cn"},
nil, nil,
) )
// search
sr, err := l.Search(searchRequest) sr, err := l.Search(searchRequest)
if err != nil { if err != nil {
return nil, fmt.Errorf("LDAP search failed: %w", err) return nil, fmt.Errorf("LDAP search failed: %w", err)
} }
// arrange result into a map string->string
for _, entry := range sr.Entries { for _, entry := range sr.Entries {
gid := entry.GetAttributeValue("gidNumber") gid := entry.GetAttributeValue("gidNumber")
cn := entry.GetAttributeValue("cn") cn := entry.GetAttributeValue("cn")
...@@ -288,7 +294,6 @@ func getGroups() (map[string]string, error) { ...@@ -288,7 +294,6 @@ func getGroups() (map[string]string, error) {
} }
func getUIDs() ([]int, error) { func getUIDs() ([]int, error) {
var uidNumbers []int var uidNumbers []int
l, err := connLDAP() l, err := connLDAP()
...@@ -297,22 +302,24 @@ func getUIDs() ([]int, error) { ...@@ -297,22 +302,24 @@ func getUIDs() ([]int, error) {
} }
defer l.Close() defer l.Close()
// build search
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
"dc=c3local", "dc=c3local",
ldap.ScopeWholeSubtree, ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, ldap.NeverDerefAliases,
0, 0, 0, 0, false,
false,
"(&(objectClass=posixAccount))", "(&(objectClass=posixAccount))",
[]string{"uidNumber"}, []string{"uidNumber"},
nil, nil,
) )
// search
sr, err := l.Search(searchRequest) sr, err := l.Search(searchRequest)
if err != nil { if err != nil {
return nil, fmt.Errorf("LDAP search failed: %w", err) return nil, fmt.Errorf("LDAP search failed: %w", err)
} }
// arrange result into a array of integers
for _, entry := range sr.Entries { for _, entry := range sr.Entries {
uidStr := entry.GetAttributeValue("uidNumber") uidStr := entry.GetAttributeValue("uidNumber")
if uidStr != "" { if uidStr != "" {
...@@ -324,6 +331,7 @@ func getUIDs() ([]int, error) { ...@@ -324,6 +331,7 @@ func getUIDs() ([]int, error) {
} }
} }
// sort for ease of use :)
sort.Ints(uidNumbers) sort.Ints(uidNumbers)
return uidNumbers, nil return uidNumbers, nil
...@@ -333,11 +341,13 @@ func loginExists(users []model.User, login string) bool { ...@@ -333,11 +341,13 @@ func loginExists(users []model.User, login string) bool {
return exists(users, func(u model.User) bool { return u.UID == login }) return exists(users, func(u model.User) bool { return u.UID == login })
} }
// accepts GRRs in the format "GRRXXXXXXXX" aswell as 8 digit number
func grrExists(users []model.User, grr string) bool { func grrExists(users []model.User, grr string) bool {
return exists(users, func(u model.User) bool { return u.GRR == grr }) || return exists(users, func(u model.User) bool { return u.GRR == grr }) ||
exists(users, func(u model.User) bool { return u.GRR == "GRR" + grr }) exists(users, func(u model.User) bool { return u.GRR == "GRR" + grr })
} }
// generic function for check existence of data types
func exists[T any](slice []T, predicate func(T) bool) bool { func exists[T any](slice []T, predicate func(T) bool) bool {
for _, item := range slice { for _, item := range slice {
if predicate(item) { if predicate(item) {
......
...@@ -41,6 +41,7 @@ func (u *User) ToString() string { ...@@ -41,6 +41,7 @@ func (u *User) ToString() string {
u.Nobackup, u.Status, u.Resp, u.Course, u.Expiry) u.Nobackup, u.Status, u.Resp, u.Course, u.Expiry)
} }
// useful for debugging :)
func (u *User) FullToString() string { func (u *User) FullToString() string {
return fmt.Sprintf(`User: return fmt.Sprintf(`User:
DN: %s DN: %s
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment