mirror of
https://github.com/moul/sshportal.git
synced 2025-09-11 23:24:33 +08:00
Support assign multiple groups to hosts and users (#2)
This commit is contained in:
parent
d6a7a6702f
commit
f97c9f2878
5 changed files with 77 additions and 69 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## master (unreleased)
|
## master (unreleased)
|
||||||
|
|
||||||
* No entry
|
* Support adding multiple `--group` links on `host create` and `user create`
|
||||||
|
|
||||||
## v1.1.0 (2017-11-15)
|
## v1.1.0 (2017-11-15)
|
||||||
|
|
||||||
|
|
28
README.md
28
README.md
|
@ -140,9 +140,9 @@ You can enter in interactive mode using this syntax: `ssh admin@portal.example.o
|
||||||
# acl management
|
# acl management
|
||||||
acl help
|
acl help
|
||||||
acl create [-h] [--hostgroup=<value>...] [--usergroup=<value>...] [--pattern=<value>] [--comment=<value>] [--action=<value>] [--weight=value]
|
acl create [-h] [--hostgroup=<value>...] [--usergroup=<value>...] [--pattern=<value>] [--comment=<value>] [--action=<value>] [--weight=value]
|
||||||
acl inspect [-h] <id> [<id> [<id>...]]
|
acl inspect [-h] <id>...
|
||||||
acl ls [-h]
|
acl ls [-h]
|
||||||
acl rm [-h] <id> [<id> [<id>...]]
|
acl rm [-h] <id>...
|
||||||
|
|
||||||
# config management
|
# config management
|
||||||
config help
|
config help
|
||||||
|
@ -151,38 +151,38 @@ config restore [-h] [--confirm]
|
||||||
|
|
||||||
# host management
|
# host management
|
||||||
host help
|
host help
|
||||||
host create [-h] [--name=<value>] [--password=<value>] [--fingerprint=<value>] [--comment=<value>] [--key=<value>] [--group=<value>] <user>[:<password>]@<host>[:<port>]
|
host create [-h] [--name=<value>] [--password=<value>] [--fingerprint=<value>] [--comment=<value>] [--key=<value>] [--group=<value>...] <user>[:<password>]@<host>[:<port>]
|
||||||
host inspect [-h] <id or name> [<id or name> [<id or name>...]]
|
host inspect [-h] <id or name>...
|
||||||
host ls [-h]
|
host ls [-h]
|
||||||
host rm [-h] <id or name> [<id or name> [<id or name>...]]
|
host rm [-h] <id or name>...
|
||||||
|
|
||||||
# hostgroup management
|
# hostgroup management
|
||||||
hostgroup help
|
hostgroup help
|
||||||
hostgroup create [-h] [--name=<value>] [--comment=<value>]
|
hostgroup create [-h] [--name=<value>] [--comment=<value>]
|
||||||
hostgroup inspect [-h] <id or name> [<id or name> [<id or name>...]]
|
hostgroup inspect [-h] <id or name>...
|
||||||
hostgroup ls [-h]
|
hostgroup ls [-h]
|
||||||
hostgroup rm [-h] <id or name> [<id or name> [<id or name>...]]
|
hostgroup rm [-h] <id or name>...
|
||||||
|
|
||||||
# key management
|
# key management
|
||||||
key help
|
key help
|
||||||
key create [-h] [--name=<value>] [--type=<value>] [--length=<value>] [--comment=<value>]
|
key create [-h] [--name=<value>] [--type=<value>] [--length=<value>] [--comment=<value>]
|
||||||
key inspect [-h] <id or name> [<id or name> [<id or name>...]]
|
key inspect [-h] <id or name>...
|
||||||
key ls [-h]
|
key ls [-h]
|
||||||
key rm [-h] <id or name> [<id or name> [<id or name>...]]
|
key rm [-h] <id or name>...
|
||||||
|
|
||||||
# user management
|
# user management
|
||||||
user help
|
user help
|
||||||
user invite [-h] [--name=<value>] [--comment=<value>] [--group=<value>] <email>
|
user invite [-h] [--name=<value>] [--comment=<value>] [--group=<value>...] <email>
|
||||||
user inspect [-h] <id or email> [<id or email> [<id or email>...]]
|
user inspect [-h] <id or email>...
|
||||||
user ls [-h]
|
user ls [-h]
|
||||||
user rm [-h] <id or email> [<id or email> [<id or email>...]]
|
user rm [-h] <id or email>...
|
||||||
|
|
||||||
# usergroup management
|
# usergroup management
|
||||||
usergroup help
|
usergroup help
|
||||||
hostgroup create [-h] [--name=<value>] [--comment=<value>]
|
hostgroup create [-h] [--name=<value>] [--comment=<value>]
|
||||||
usergroup inspect [-h] <id or name> [<id or name> [<id or name>...]]
|
usergroup inspect [-h] <id or name>...
|
||||||
usergroup ls [-h]
|
usergroup ls [-h]
|
||||||
usergroup rm [-h] <id or name> [<id or name> [<id or name>...]]
|
usergroup rm [-h] <id or name>...
|
||||||
|
|
||||||
# other
|
# other
|
||||||
exit [-h]
|
exit [-h]
|
||||||
|
|
6
acl.go
6
acl.go
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
import "sort"
|
import "sort"
|
||||||
|
|
||||||
type ByWeight []ACL
|
type ByWeight []*ACL
|
||||||
|
|
||||||
func (a ByWeight) Len() int { return len(a) }
|
func (a ByWeight) Len() int { return len(a) }
|
||||||
func (a ByWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a ByWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
@ -10,7 +10,7 @@ func (a ByWeight) Less(i, j int) bool { return a[i].Weight < a[j].Weight }
|
||||||
|
|
||||||
func CheckACLs(user User, host Host) (string, error) {
|
func CheckACLs(user User, host Host) (string, error) {
|
||||||
// shared ACLs between user and host
|
// shared ACLs between user and host
|
||||||
aclMap := map[uint]ACL{}
|
aclMap := map[uint]*ACL{}
|
||||||
for _, userGroup := range user.Groups {
|
for _, userGroup := range user.Groups {
|
||||||
for _, userGroupACL := range userGroup.ACLs {
|
for _, userGroupACL := range userGroup.ACLs {
|
||||||
for _, hostGroup := range host.Groups {
|
for _, hostGroup := range host.Groups {
|
||||||
|
@ -30,7 +30,7 @@ func CheckACLs(user User, host Host) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// transofrm map to slice and sort it
|
// transofrm map to slice and sort it
|
||||||
acls := []ACL{}
|
acls := []*ACL{}
|
||||||
for _, acl := range aclMap {
|
for _, acl := range aclMap {
|
||||||
acls = append(acls, acl)
|
acls = append(acls, acl)
|
||||||
}
|
}
|
||||||
|
|
64
db.go
64
db.go
|
@ -12,14 +12,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
SSHKeys []SSHKey `json:"keys"`
|
SSHKeys []*SSHKey `json:"keys"`
|
||||||
Hosts []Host `json:"hosts"`
|
Hosts []*Host `json:"hosts"`
|
||||||
UserKeys []UserKey `json:"user_keys"`
|
UserKeys []*UserKey `json:"user_keys"`
|
||||||
Users []User `json:"users"`
|
Users []*User `json:"users"`
|
||||||
UserGroups []UserGroup `json:"user_groups"`
|
UserGroups []*UserGroup `json:"user_groups"`
|
||||||
HostGroups []HostGroup `json:"host_groups"`
|
HostGroups []*HostGroup `json:"host_groups"`
|
||||||
ACLs []ACL `json:"acls"`
|
ACLs []*ACL `json:"acls"`
|
||||||
Date time.Time `json:"date"`
|
Date time.Time `json:"date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHKey struct {
|
type SSHKey struct {
|
||||||
|
@ -29,9 +29,9 @@ type SSHKey struct {
|
||||||
Type string
|
Type string
|
||||||
Length uint
|
Length uint
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
PrivKey string `sql:"size:10000;"`
|
PrivKey string `sql:"size:10000;"`
|
||||||
PubKey string `sql:"size:10000;"`
|
PubKey string `sql:"size:10000;"`
|
||||||
Hosts []Host
|
Hosts []*Host `gorm:"ForeignKey:SSHKeyID"`
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +42,10 @@ type Host struct {
|
||||||
Addr string
|
Addr string
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
SSHKey *SSHKey
|
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"`
|
||||||
SSHKeyID uint `gorm:"index"`
|
SSHKeyID uint `gorm:"index"`
|
||||||
Groups []HostGroup `gorm:"many2many:host_host_groups;"`
|
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
|
||||||
Fingerprint string // FIXME: replace with hostkey ?
|
Fingerprint string // FIXME: replace with hostkey ?
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ type UserKey struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Key []byte `sql:"size:10000;"`
|
Key []byte `sql:"size:10000;"`
|
||||||
UserID uint
|
UserID uint
|
||||||
User *User
|
User *User `gorm:"ForeignKey:UserID"`
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +61,10 @@ type User struct {
|
||||||
// FIXME: use uuid for ID
|
// FIXME: use uuid for ID
|
||||||
gorm.Model
|
gorm.Model
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
Email string // FIXME: govalidator: email
|
Email string // FIXME: govalidator: email
|
||||||
Name string // FIXME: govalidator: min length 3, alphanum
|
Name string // FIXME: govalidator: min length 3, alphanum
|
||||||
Keys []UserKey
|
Keys []*UserKey `gorm:"ForeignKey:UserID"`
|
||||||
Groups []UserGroup `gorm:"many2many:user_user_groups;"`
|
Groups []*UserGroup `gorm:"many2many:user_user_groups;"`
|
||||||
Comment string
|
Comment string
|
||||||
InviteToken string
|
InviteToken string
|
||||||
}
|
}
|
||||||
|
@ -72,23 +72,23 @@ type User struct {
|
||||||
type UserGroup struct {
|
type UserGroup struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Name string
|
Name string
|
||||||
Users []User `gorm:"many2many:user_user_groups;"`
|
Users []*User `gorm:"many2many:user_user_groups;"`
|
||||||
ACLs []ACL `gorm:"many2many:user_group_acls;"`
|
ACLs []*ACL `gorm:"many2many:user_group_acls;"`
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HostGroup struct {
|
type HostGroup struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Name string
|
Name string
|
||||||
Hosts []Host `gorm:"many2many:host_host_groups;"`
|
Hosts []*Host `gorm:"many2many:host_host_groups;"`
|
||||||
ACLs []ACL `gorm:"many2many:host_group_acls;"`
|
ACLs []*ACL `gorm:"many2many:host_group_acls;"`
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ACL struct {
|
type ACL struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
HostGroups []HostGroup `gorm:"many2many:host_group_acls;"`
|
HostGroups []*HostGroup `gorm:"many2many:host_group_acls;"`
|
||||||
UserGroups []UserGroup `gorm:"many2many:user_group_acls;"`
|
UserGroups []*UserGroup `gorm:"many2many:user_group_acls;"`
|
||||||
HostPattern string
|
HostPattern string
|
||||||
Action string
|
Action string
|
||||||
Weight uint
|
Weight uint
|
||||||
|
@ -165,8 +165,8 @@ func dbInit(db *gorm.DB) error {
|
||||||
var defaultHostGroup HostGroup
|
var defaultHostGroup HostGroup
|
||||||
db.Where("name = ?", "default").First(&defaultHostGroup)
|
db.Where("name = ?", "default").First(&defaultHostGroup)
|
||||||
acl := ACL{
|
acl := ACL{
|
||||||
UserGroups: []UserGroup{defaultUserGroup},
|
UserGroups: []*UserGroup{&defaultUserGroup},
|
||||||
HostGroups: []HostGroup{defaultHostGroup},
|
HostGroups: []*HostGroup{&defaultHostGroup},
|
||||||
Action: "allow",
|
Action: "allow",
|
||||||
//HostPattern: "",
|
//HostPattern: "",
|
||||||
//Weight: 0,
|
//Weight: 0,
|
||||||
|
@ -189,7 +189,7 @@ func dbInit(db *gorm.DB) error {
|
||||||
Comment: "created by sshportal",
|
Comment: "created by sshportal",
|
||||||
IsAdmin: true,
|
IsAdmin: true,
|
||||||
InviteToken: RandStringBytes(16),
|
InviteToken: RandStringBytes(16),
|
||||||
Groups: []UserGroup{defaultUserGroup},
|
Groups: []*UserGroup{&defaultUserGroup},
|
||||||
}
|
}
|
||||||
db.Create(&user)
|
db.Create(&user)
|
||||||
log.Printf("Admin user created, use the user 'invite:%s' to associate a public key with this account", user.InviteToken)
|
log.Printf("Admin user created, use the user 'invite:%s' to associate a public key with this account", user.InviteToken)
|
||||||
|
@ -225,9 +225,9 @@ func dbDemo(db *gorm.DB) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
host1 = Host{Name: "sdf", Addr: "sdf.org:22", User: "new", SSHKeyID: key.ID, Groups: []HostGroup{*hostGroup}}
|
host1 = Host{Name: "sdf", Addr: "sdf.org:22", User: "new", SSHKeyID: key.ID, Groups: []*HostGroup{hostGroup}}
|
||||||
host2 = Host{Name: "whoami", Addr: "whoami.filippo.io:22", User: "test", SSHKeyID: key.ID, Groups: []HostGroup{*hostGroup}}
|
host2 = Host{Name: "whoami", Addr: "whoami.filippo.io:22", User: "test", SSHKeyID: key.ID, Groups: []*HostGroup{hostGroup}}
|
||||||
host3 = Host{Name: "ssh-chat", Addr: "chat.shazow.net:22", User: "test", SSHKeyID: key.ID, Fingerprint: "MD5:e5:d5:d1:75:90:38:42:f6:c7:03:d7:d0:56:7d:6a:db", Groups: []HostGroup{*hostGroup}}
|
host3 = Host{Name: "ssh-chat", Addr: "chat.shazow.net:22", User: "test", SSHKeyID: key.ID, Fingerprint: "MD5:e5:d5:d1:75:90:38:42:f6:c7:03:d7:d0:56:7d:6a:db", Groups: []*HostGroup{hostGroup}}
|
||||||
)
|
)
|
||||||
|
|
||||||
// FIXME: check if hosts exist to avoid `UNIQUE constraint` error
|
// FIXME: check if hosts exist to avoid `UNIQUE constraint` error
|
||||||
|
|
46
shell.go
46
shell.go
|
@ -75,8 +75,8 @@ GLOBAL OPTIONS:
|
||||||
acl := ACL{
|
acl := ACL{
|
||||||
Comment: c.String("comment"),
|
Comment: c.String("comment"),
|
||||||
HostPattern: c.String("pattern"),
|
HostPattern: c.String("pattern"),
|
||||||
UserGroups: []UserGroup{},
|
UserGroups: []*UserGroup{},
|
||||||
HostGroups: []HostGroup{},
|
HostGroups: []*HostGroup{},
|
||||||
Weight: c.Uint("weight"),
|
Weight: c.Uint("weight"),
|
||||||
Action: c.String("action"),
|
Action: c.String("action"),
|
||||||
}
|
}
|
||||||
|
@ -89,14 +89,14 @@ GLOBAL OPTIONS:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unknown user group %q: %v", name, err)
|
return fmt.Errorf("unknown user group %q: %v", name, err)
|
||||||
}
|
}
|
||||||
acl.UserGroups = append(acl.UserGroups, *userGroup)
|
acl.UserGroups = append(acl.UserGroups, userGroup)
|
||||||
}
|
}
|
||||||
for _, name := range c.StringSlice("hostgroup") {
|
for _, name := range c.StringSlice("hostgroup") {
|
||||||
hostGroup, err := FindHostGroupByIdOrName(db, name)
|
hostGroup, err := FindHostGroupByIdOrName(db, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unknown host group %q: %v", name, err)
|
return fmt.Errorf("unknown host group %q: %v", name, err)
|
||||||
}
|
}
|
||||||
acl.HostGroups = append(acl.HostGroups, *hostGroup)
|
acl.HostGroups = append(acl.HostGroups, hostGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(acl.UserGroups) == 0 {
|
if len(acl.UserGroups) == 0 {
|
||||||
|
@ -330,12 +330,12 @@ GLOBAL OPTIONS:
|
||||||
ArgsUsage: "<user>[:<password>]@<host>[:<port>]",
|
ArgsUsage: "<user>[:<password>]@<host>[:<port>]",
|
||||||
Description: "$> host create bart@foo.org\n $> host create bob:marley@example.com:2222",
|
Description: "$> host create bart@foo.org\n $> host create bob:marley@example.com:2222",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{Name: "name", Usage: "Assigns a name to the host"},
|
cli.StringFlag{Name: "name, n", Usage: "Assigns a name to the host"},
|
||||||
cli.StringFlag{Name: "password", Usage: "If present, sshportal will use password-based authentication"},
|
cli.StringFlag{Name: "password, p", Usage: "If present, sshportal will use password-based authentication"},
|
||||||
cli.StringFlag{Name: "fingerprint", Usage: "SSH host key fingerprint"},
|
cli.StringFlag{Name: "fingerprint, f", Usage: "SSH host key fingerprint"},
|
||||||
cli.StringFlag{Name: "comment"},
|
cli.StringFlag{Name: "comment, c"},
|
||||||
cli.StringFlag{Name: "key", Usage: "ID or name of the key to use for authentication"},
|
cli.StringFlag{Name: "key, k", Usage: "ID or name of the key to use for authentication"},
|
||||||
cli.StringFlag{Name: "group", Usage: "Name or ID of the host group", Value: "default"},
|
cli.StringSliceFlag{Name: "group, g", Usage: "Names or IDs of host groups (default: \"default\")"},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
if c.NArg() != 1 {
|
if c.NArg() != 1 {
|
||||||
|
@ -373,11 +373,15 @@ GLOBAL OPTIONS:
|
||||||
}
|
}
|
||||||
|
|
||||||
// host group
|
// host group
|
||||||
hostGroup, err := FindHostGroupByIdOrName(db, c.String("group"))
|
inputGroups := c.StringSlice("group")
|
||||||
|
if len(inputGroups) == 0 {
|
||||||
|
inputGroups = []string{"default"}
|
||||||
|
}
|
||||||
|
hostGroups, err := FindHostGroupsByIdOrName(db, inputGroups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
host.Groups = []HostGroup{*hostGroup}
|
host.Groups = hostGroups
|
||||||
|
|
||||||
if err := db.Create(&host).Error; err != nil {
|
if err := db.Create(&host).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -407,7 +411,7 @@ GLOBAL OPTIONS:
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "Lists hosts",
|
Usage: "Lists hosts",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
var hosts []Host
|
var hosts []*Host
|
||||||
if err := db.Preload("Groups").Find(&hosts).Error; err != nil {
|
if err := db.Preload("Groups").Find(&hosts).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -515,7 +519,7 @@ GLOBAL OPTIONS:
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "Lists host groups",
|
Usage: "Lists host groups",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
var hostGroups []HostGroup
|
var hostGroups []*HostGroup
|
||||||
if err := db.Preload("ACLs").Preload("Hosts").Find(&hostGroups).Error; err != nil {
|
if err := db.Preload("ACLs").Preload("Hosts").Find(&hostGroups).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -727,7 +731,7 @@ GLOBAL OPTIONS:
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{Name: "name", Usage: "Assigns a name to the user"},
|
cli.StringFlag{Name: "name", Usage: "Assigns a name to the user"},
|
||||||
cli.StringFlag{Name: "comment"},
|
cli.StringFlag{Name: "comment"},
|
||||||
cli.StringFlag{Name: "group", Usage: "Name or ID of the user group", Value: "default"},
|
cli.StringSliceFlag{Name: "group, g", Usage: "Names or IDs of user groups (default: \"default\")"},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
if c.NArg() != 1 {
|
if c.NArg() != 1 {
|
||||||
|
@ -750,11 +754,15 @@ GLOBAL OPTIONS:
|
||||||
}
|
}
|
||||||
|
|
||||||
// user group
|
// user group
|
||||||
userGroup, err := FindUserGroupByIdOrName(db, c.String("group"))
|
inputGroups := c.StringSlice("group")
|
||||||
|
if len(inputGroups) == 0 {
|
||||||
|
inputGroups = []string{"default"}
|
||||||
|
}
|
||||||
|
userGroups, err := FindUserGroupsByIdOrName(db, inputGroups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
user.Groups = []UserGroup{*userGroup}
|
user.Groups = userGroups
|
||||||
|
|
||||||
// save the user in database
|
// save the user in database
|
||||||
if err := db.Create(&user).Error; err != nil {
|
if err := db.Create(&user).Error; err != nil {
|
||||||
|
@ -840,7 +848,7 @@ GLOBAL OPTIONS:
|
||||||
// add myself to the new group
|
// add myself to the new group
|
||||||
myself := s.Context().Value(userContextKey).(User)
|
myself := s.Context().Value(userContextKey).(User)
|
||||||
// FIXME: use foreign key with ID to avoid updating the user with the context
|
// FIXME: use foreign key with ID to avoid updating the user with the context
|
||||||
userGroup.Users = []User{myself}
|
userGroup.Users = []*User{&myself}
|
||||||
|
|
||||||
if err := db.Create(&userGroup).Error; err != nil {
|
if err := db.Create(&userGroup).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -870,7 +878,7 @@ GLOBAL OPTIONS:
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "Lists user groups",
|
Usage: "Lists user groups",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
var userGroups []UserGroup
|
var userGroups []*UserGroup
|
||||||
if err := db.Preload("ACLs").Preload("Users").Find(&userGroups).Error; err != nil {
|
if err := db.Preload("ACLs").Preload("Users").Find(&userGroups).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue