From 401d0e4c84dff869db915e789113c1469f9bef8c Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 14 Oct 2021 11:21:29 +0200 Subject: [PATCH 01/12] redfish: DEBUG_BMCLIB dumps gofish requests --- providers/redfish/redfish.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/providers/redfish/redfish.go b/providers/redfish/redfish.go index a1b453eb..3c3f6c80 100644 --- a/providers/redfish/redfish.go +++ b/providers/redfish/redfish.go @@ -3,6 +3,7 @@ package redfish import ( "context" "fmt" + "os" "strings" "time" @@ -41,6 +42,7 @@ type Conn struct { // Open a connection to a BMC via redfish func (c *Conn) Open(ctx context.Context) (err error) { + config := gofish.ClientConfig{ Endpoint: "https://" + c.Host, Username: c.User, @@ -48,6 +50,11 @@ func (c *Conn) Open(ctx context.Context) (err error) { Insecure: true, } + debug := os.Getenv("DEBUG_BMCLIB") + if debug != "" { + config.DumpWriter = os.Stdout + } + c.conn, err = gofish.ConnectContext(ctx, config) if err != nil { return err From 350020699719549949f46f5c3794a033ecada45e Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 14 Oct 2021 11:27:57 +0200 Subject: [PATCH 02/12] providers/redfish: add user create/update/delete methods --- examples/v1/users/user.go | 44 ++++++++++++ providers/redfish/user.go | 143 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 examples/v1/users/user.go create mode 100644 providers/redfish/user.go diff --git a/examples/v1/users/user.go b/examples/v1/users/user.go new file mode 100644 index 00000000..b80dd6cc --- /dev/null +++ b/examples/v1/users/user.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "log" + "time" + + "github.com/bmc-toolbox/bmclib" + "github.com/bombsimon/logrusr" + "github.com/sirupsen/logrus" +) + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + host := "" + port := "" + user := "" + pass := "" + + l := logrus.New() + l.Level = logrus.DebugLevel + logger := logrusr.NewLogger(l) + + var err error + + cl := bmclib.NewClient(host, port, user, pass, bmclib.WithLogger(logger)) + + // we may want to specify multiple protocols here + cl.Registry.Drivers = cl.Registry.Using("redfish") + + err = cl.Open(ctx) + if err != nil { + log.Fatal(err, "bmc login failed") + } + + defer cl.Close(ctx) + + _, err = cl.CreateUser(ctx, "foobar", "sekurity101", "Administrator") + if err != nil { + l.Error(err) + } + +} diff --git a/providers/redfish/user.go b/providers/redfish/user.go new file mode 100644 index 00000000..5692cc57 --- /dev/null +++ b/providers/redfish/user.go @@ -0,0 +1,143 @@ +package redfish + +import ( + "context" + + "github.com/pkg/errors" + "github.com/stmcginnis/gofish/redfish" +) + +var ( + ErrNoUserSlotsAvailable = errors.New("no user account slots available") + ErrUserNotPresent = errors.New("given user not present") + ErrUserPassParams = errors.New("user and pass parameters required") + ErrUserExists = errors.New("user exists") + ErrInvalidUserRole = errors.New("invalid user role") + ValidRoles = []string{"Administrator", "Operator", "ReadOnly", "None"} +) + +// UserRead returns a list of enabled user accounts +func (c *Conn) UserRead(ctx context.Context) (users []map[string]string, err error) { + service, err := c.conn.Service.AccountService() + if err != nil { + return nil, err + } + + accounts, err := service.Accounts() + if err != nil { + return nil, err + } + + users = make([]map[string]string, 0) + + for _, account := range accounts { + if account.Enabled { + user := map[string]string{ + "ID": account.ID, + "Name": account.Name, + "Username": account.UserName, + "RoleID": account.RoleID, + } + users = append(users, user) + } + } + + return users, nil +} + +// UserUpdate updates a user password and role +func (c *Conn) UserUpdate(ctx context.Context, user, pass, role string) (ok bool, err error) { + service, err := c.conn.Service.AccountService() + if err != nil { + return false, err + } + + accounts, err := service.Accounts() + if err != nil { + return false, err + } + + for _, account := range accounts { + if account.UserName == user { + var change bool + if pass != "" { + account.Password = pass + change = true + } + if role != "" { + account.RoleID = role + change = true + } + + if change { + err := account.Update() + if err != nil { + return false, err + } + return true, nil + } + } + } + + return ok, ErrUserNotPresent +} + +// UserCreate adds a new user account +func (c *Conn) UserCreate(ctx context.Context, user, pass, role string) (ok bool, err error) { + if !StringInSlice(role, ValidRoles) { + return false, ErrInvalidUserRole + } + + if user == "" || pass == "" { + return false, ErrUserPassParams + } + + service, err := c.conn.Service.AccountService() + if err != nil { + return false, err + } + + // fetch current list of accounts + accounts, err := service.Accounts() + if err != nil { + return false, err + } + + // identify account slot not in use + for _, account := range accounts { + // Dell iDracs don't want us to create accounts in these slots + if StringInSlice(account.ID, []string{"1"}) { + continue + } + + account := account + if account.UserName == user { + return false, errors.Wrap(ErrUserExists, user) + } + + if !account.Enabled && account.UserName == "" { + account.Enabled = true + account.UserName = user + account.Password = pass + account.RoleID = role + account.AccountTypes = []redfish.AccountTypes{"Redfish", "OEM"} + + err := account.Update() + if err != nil { + return false, err + } + return true, nil + } + } + + return false, ErrNoUserSlotsAvailable +} + +func StringInSlice(str string, sl []string) bool { + for _, s := range sl { + if str == s { + return true + } + } + return false +} From fda3dba446291af5f71d3d0e71aaa2a0616074d6 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 14 Oct 2021 11:30:10 +0200 Subject: [PATCH 03/12] Update gofish dep to v0.12.0; update README --- README.md | 4 +++- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4805c558..de51e36d 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,10 @@ With approval from [Booking.com](http://www.booking.com), the code and specification were generalized and published as Open Source on github, for which the authors would like to express their gratitude. +bmclib interfaces with Redfish with https://github.com/stmcginnis/gofish + #### Authors - Juliano Martinez -- Joel Rebello +- Joel Rebello - Guilherme M. Schroeder - Mariano Guezuraga diff --git a/go.mod b/go.mod index 9388c6f0..cbf2e73c 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.7.1 - github.com/stmcginnis/gofish v0.8.0 + github.com/stmcginnis/gofish v0.12.0 go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 diff --git a/go.sum b/go.sum index dc18c417..2bab9d12 100644 --- a/go.sum +++ b/go.sum @@ -250,8 +250,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stmcginnis/gofish v0.8.0 h1:lGMKmyHQr8rG6SHKL9SU8BuA4bV/r3Pj/8J+DkuT0gY= -github.com/stmcginnis/gofish v0.8.0/go.mod h1:BGtQsY16q48M2K6KDAs38QXtNoHrkXaY/WZ/mmyMgNc= +github.com/stmcginnis/gofish v0.12.0 h1:6UbNePjA++XkHtCKKLr7envKENxljJ1YyD8f4vS3Zeo= +github.com/stmcginnis/gofish v0.12.0/go.mod h1:BGtQsY16q48M2K6KDAs38QXtNoHrkXaY/WZ/mmyMgNc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= From db8e610ae5f56ce7d821498474f4a104cf055129 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 15 Oct 2021 18:15:49 +0200 Subject: [PATCH 04/12] errors: declare user account management errors --- errors/errors.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/errors/errors.go b/errors/errors.go index ccffb950..59bccd54 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -50,6 +50,27 @@ var ( // ErrDeviceNotMatched is the error returned when the device was not a type it was probed for ErrDeviceNotMatched = errors.New("the vendor device did not match the probe") + + // ErrRetrievingUserAccounts is returned when bmclib is unable to retrieve user accounts from the BMC + ErrRetrievingUserAccounts = errors.New("error retrieving user accounts") + + // ErrInvalidUserRole is returned when the given user account role is not valid + ErrInvalidUserRole = errors.New("invalid user account role") + + // ErrUserParamsRequired is returned when all the required user parameters are not provided - username, password, role + ErrUserParamsRequired = errors.New("username, password and role are required parameters") + + // ErrUserAccountExists is returned when a user account with the username is already present + ErrUserAccountExists = errors.New("user account already exists") + + // ErrNoUserSlotsAvailable is returned when there are no user account slots available + ErrNoUserSlotsAvailable = errors.New("no user account slots available") + + // ErrUserAccountNotFound is returned when the user account is not present + ErrUserAccountNotFound = errors.New("given user account does not exist") + + // ErrUserAccountUpdate is returned when the user account failed to be updated + ErrUserAccountUpdate = errors.New("user account attributes could not be updated") ) type ErrUnsupportedHardware struct { From baed09cd2f3877e36c8b39a9705bb08b3f2c003d Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 15 Oct 2021 18:16:25 +0200 Subject: [PATCH 05/12] internal/utils: add StringInSlice() helper method --- internal/utils.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/utils.go b/internal/utils.go index e03b12d2..e77a8236 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -43,3 +43,12 @@ func ValidateUserConfig(cfgUsers []*cfgresources.User) (err error) { return nil } + +func StringInSlice(str string, sl []string) bool { + for _, s := range sl { + if str == s { + return true + } + } + return false +} From e7818b1eafb0a1165c6efeed1938aa672ad62756 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 15 Oct 2021 18:16:53 +0200 Subject: [PATCH 06/12] providers/asrockrack: add helper methods to list and create/update user accounts --- providers/asrockrack/helpers.go | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/providers/asrockrack/helpers.go b/providers/asrockrack/helpers.go index 501f7d79..2b066e60 100644 --- a/providers/asrockrack/helpers.go +++ b/providers/asrockrack/helpers.go @@ -59,6 +59,49 @@ type biosUpdateAction struct { Action int `json:"action"` } +func (a *ASRockRack) listUsers() ([]*UserAccount, error) { + endpoint := "api/settings/users" + + resp, statusCode, err := a.queryHTTPS(endpoint, "GET", nil, nil, 0) + if err != nil { + return nil, err + } + + if statusCode != 200 { + return nil, fmt.Errorf("non 200 response: %d", statusCode) + } + + accounts := []*UserAccount{} + + err = json.Unmarshal(resp, &accounts) + if err != nil { + return nil, err + } + + return accounts, nil +} + +func (a *ASRockRack) createUpdateUser(account *UserAccount) error { + endpoint := "api/settings/users/" + fmt.Sprintf("%d", account.ID) + + payload, err := json.Marshal(account) + if err != nil { + return err + } + + headers := map[string]string{"Content-Type": "application/json"} + _, statusCode, err := a.queryHTTPS(endpoint, "PUT", bytes.NewReader(payload), headers, 0) + if err != nil { + return err + } + + if statusCode != 200 { + return fmt.Errorf("non 200 response: %d", statusCode) + } + + return nil +} + // 1 Set BMC to flash mode and prepare flash area // at this point all logged in sessions are terminated // and no logins are permitted From 4e2102595fb176a2bc55dae694952f2120a50ee2 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 15 Oct 2021 18:19:45 +0200 Subject: [PATCH 07/12] providers/asrockrack: implement UserCreate/UserList/UserUpdate bmc interface methods --- examples/v1/users/user.go | 2 +- go.mod | 2 + go.sum | 4 + providers/asrockrack/mock_test.go | 24 ++- providers/asrockrack/user.go | 182 +++++++++++++++++++++++ providers/asrockrack/user_test.go | 234 ++++++++++++++++++++++++++++++ 6 files changed, 444 insertions(+), 4 deletions(-) create mode 100644 providers/asrockrack/user.go create mode 100644 providers/asrockrack/user_test.go diff --git a/examples/v1/users/user.go b/examples/v1/users/user.go index b80dd6cc..0716e000 100644 --- a/examples/v1/users/user.go +++ b/examples/v1/users/user.go @@ -26,8 +26,8 @@ func main() { cl := bmclib.NewClient(host, port, user, pass, bmclib.WithLogger(logger)) - // we may want to specify multiple protocols here cl.Registry.Drivers = cl.Registry.Using("redfish") + // cl.Registry.Drivers = cl.Registry.Using("vendorapi") err = cl.Open(ctx) if err != nil { diff --git a/go.mod b/go.mod index cbf2e73c..0f232ee3 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/gebn/bmc v0.0.0-20200904230046-a5643220ab2a github.com/go-logr/logr v0.4.0 github.com/go-logr/zapr v0.4.0 // indirect + github.com/go-playground/assert/v2 v2.0.1 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect github.com/google/go-cmp v0.5.5 github.com/google/go-querystring v1.0.0 @@ -27,6 +28,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.7.1 github.com/stmcginnis/gofish v0.12.0 + github.com/stretchr/testify v1.7.0 go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 diff --git a/go.sum b/go.sum index 2bab9d12..32cdc9c6 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/zapr v0.3.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= @@ -259,6 +261,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= diff --git a/providers/asrockrack/mock_test.go b/providers/asrockrack/mock_test.go index c905c12a..c8e1ac7b 100644 --- a/providers/asrockrack/mock_test.go +++ b/providers/asrockrack/mock_test.go @@ -3,7 +3,6 @@ package asrockrack import ( "bytes" "encoding/json" - "fmt" "io/ioutil" "log" "net/http" @@ -25,6 +24,9 @@ var ( fwUploadResponse = []byte(`{"cc": 0}`) fwVerificationResponse = []byte(`[ { "id": 1, "current_image_name": "ast2500e", "current_image_version1": "0.01.00", "current_image_version2": "", "new_image_version": "0.03.00", "section_status": 0, "verification_status": 5 } ]`) fwUpgradeProgress = []byte(`{ "id": 1, "action": "Flashing...", "progress": "__PERCENT__% done ", "state": __STATE__ }`) + usersPayload = []byte(`[ { "id": 1, "name": "anonymous", "access": 0, "kvm": 1, "vmedia": 1, "snmp": 0, "prev_snmp": 0, "network_privilege": "administrator", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "none", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "ami_format", "ssh_key": "Not Available", "creation_time": 4802 }, { "id": 2, "name": "admin", "access": 1, "kvm": 1, "vmedia": 1, "snmp": 0, "prev_snmp": 0, "network_privilege": "administrator", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "none", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "ami_format", "ssh_key": "Not Available", "creation_time": 188 }, { "id": 3, "name": "foo", "access": 1, "kvm": 1, "vmedia": 1, "snmp": 0, "prev_snmp": 0, "network_privilege": "administrator", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "none", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "ami_format", "ssh_key": "Not Available", "creation_time": 4802 }, { "id": 4, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 5, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 6, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 7, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 8, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 9, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 10, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 } ]`) + // TODO: implement under rw mutex + httpRequestTestVar *http.Request ) // setup test BMC @@ -80,6 +82,9 @@ func mockASRockBMC() *httptest.Server { handler.HandleFunc("/api/maintenance/reset", bmcFirmwareUpgrade) handler.HandleFunc("/api/asrr/maintenance/BIOS/firmware", biosFirmwareUpgrade) + // user accounts endpoints + handler.HandleFunc("/api/settings/users", userAccountList) + handler.HandleFunc("/api/settings/users/3", userAccountList) return httptest.NewTLSServer(handler) } @@ -90,8 +95,21 @@ func index(w http.ResponseWriter, r *http.Request) { } } +func userAccountList(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + if os.Getenv("TEST_FAIL_QUERY") != "" { + w.WriteHeader(http.StatusInternalServerError) + } else { + _, _ = w.Write(usersPayload) + } + case "PUT": + httpRequestTestVar = r + } +} + func biosFirmwareUpgrade(w http.ResponseWriter, r *http.Request) { - fmt.Printf("%s -> %s\n", r.Method, r.RequestURI) + // fmt.Printf("%s -> %s\n", r.Method, r.RequestURI) switch r.Method { case "POST": switch r.RequestURI { @@ -112,7 +130,7 @@ func biosFirmwareUpgrade(w http.ResponseWriter, r *http.Request) { } func bmcFirmwareUpgrade(w http.ResponseWriter, r *http.Request) { - fmt.Printf("%s -> %s\n", r.Method, r.RequestURI) + // fmt.Printf("%s -> %s\n", r.Method, r.RequestURI) switch r.Method { case "GET": switch r.RequestURI { diff --git a/providers/asrockrack/user.go b/providers/asrockrack/user.go new file mode 100644 index 00000000..056429df --- /dev/null +++ b/providers/asrockrack/user.go @@ -0,0 +1,182 @@ +package asrockrack + +import ( + "context" + "fmt" + "strings" + + "github.com/davecgh/go-spew/spew" + "github.com/pkg/errors" + + bmclibErrs "github.com/bmc-toolbox/bmclib/errors" + "github.com/bmc-toolbox/bmclib/internal" +) + +var ( + // TODO: standardize these across Redfish, IPMI, Vendor GUI + validRoles = []string{"Administrator", "Operator", "User"} +) + +// UserAccount is a ASRR BMC user account struct +type UserAccount struct { + ID int `json:"id"` + Name string `json:"name"` + Access int `json:"access"` + Kvm int `json:"kvm"` + Vmedia int `json:"vmedia"` + NetworkPrivilege string `json:"network_privilege"` + FixedUserCount int `json:"fixed_user_count"` + OEMProprietaryLevelPrivilege int `json:"OEMProprietary_level_Privilege"` + PrivilegeLimitSerial string `json:"privilege_limit_serial"` + SSHKey string `json:"ssh_key"` + CreationTime int `json:"creation_time"` + Changepassword int `json:"changepassword"` + UserOperation int `json:"UserOperation"` + Password string `json:"password"` + ConfirmPassword string `json:"confirm_password"` + PasswordSize string `json:"password_size"` + PrevSNMP int `json:"prev_snmp"` + SNMP int `json:"snmp"` + SNMPAccess string `json:"snmp_access"` + SNMPAuthenticationProtocol string `json:"snmp_authentication_protocol"` + EmailFormat string `json:"email_format"` + EmailID string `json:"email_id"` +} + +// UserRead returns a list of enabled user accounts +func (a *ASRockRack) UserRead(ctx context.Context) (users []map[string]string, err error) { + err = a.Open(ctx) + if err != nil { + return nil, err + } + + accounts, err := a.listUsers() + if err != nil { + return nil, errors.Wrap(bmclibErrs.ErrRetrievingUserAccounts, err.Error()) + } + + users = make([]map[string]string, 0) + for _, account := range accounts { + if account.Access == 1 { + user := map[string]string{ + "ID": fmt.Sprintf("%d", account.ID), + "Name": account.Name, + "RoleID": account.NetworkPrivilege, + } + users = append(users, user) + } + } + + return users, nil +} + +// UserCreate adds a new user account +func (a *ASRockRack) UserCreate(ctx context.Context, user, pass, role string) (ok bool, err error) { + if !internal.StringInSlice(role, validRoles) { + return false, bmclibErrs.ErrInvalidUserRole + } + + if user == "" || pass == "" || role == "" { + return false, bmclibErrs.ErrUserParamsRequired + } + + // fetch current list of accounts + accounts, err := a.listUsers() + if err != nil { + return false, errors.Wrap(bmclibErrs.ErrRetrievingUserAccounts, err.Error()) + } + + // identify account slot not in use + for _, account := range accounts { + // ASRR BMCs have a reserved slot 1 for a disabled Anonymous, no idea why. + if account.ID == 1 { + continue + } + + account := account + if account.Name == user { + return false, errors.Wrap(bmclibErrs.ErrUserAccountExists, user) + } + + if account.Access == 0 && account.Name == "" { + newAccount := newUserAccount(account.ID, user, pass, strings.ToLower(role)) + err := a.createUpdateUser(newAccount) + if err != nil { + return false, err + } + return true, nil + } + } + + return false, bmclibErrs.ErrNoUserSlotsAvailable +} + +// + +// UserUpdate updates a user password and role +func (a *ASRockRack) UserUpdate(ctx context.Context, user, pass, role string) (ok bool, err error) { + if !internal.StringInSlice(role, validRoles) { + return false, bmclibErrs.ErrInvalidUserRole + } + + if user == "" || pass == "" || role == "" { + return false, bmclibErrs.ErrUserParamsRequired + } + + accounts, err := a.listUsers() + if err != nil { + return false, errors.Wrap(bmclibErrs.ErrRetrievingUserAccounts, err.Error()) + } + + role = strings.ToLower(role) + + // identify account slot not in use + for _, account := range accounts { + // ASRR BMCs have a reserved slot 1 for a disabled Anonymous, no idea why. + account := account + if account.Name == user { + user := newUserAccount(account.ID, user, pass, role) + + if role == "administrator" { + user.PrivilegeLimitSerial = "none" + user.UserOperation = 1 + user.CreationTime = 6000 // doesn't mean anything. + } + + err := a.createUpdateUser(user) + if err != nil { + return false, errors.Wrap(bmclibErrs.ErrUserAccountUpdate, err.Error()) + } + + spew.Dump(account) + return true, nil + } + } + + return ok, errors.Wrap(bmclibErrs.ErrUserAccountNotFound, user) +} + +// newUserAccount returns a user account object populated with the given attributes and certain defaults +// +// note: the role parameter must be validated before being passed to this constructor +func newUserAccount(id int, user, pass, role string) *UserAccount { + return &UserAccount{ + ID: id, + Name: user, + Access: 1, // Access enabled + Kvm: 1, + Vmedia: 1, + NetworkPrivilege: role, + FixedUserCount: 2, // No idea what this is about + OEMProprietaryLevelPrivilege: 1, + PrivilegeLimitSerial: role, + SSHKey: "Not Available", + CreationTime: 0, + Changepassword: 1, + UserOperation: 0, + Password: pass, + ConfirmPassword: pass, + PasswordSize: "bytes_16", // bytes_20 for larger passwords + EmailFormat: "AMI-Format", + } +} diff --git a/providers/asrockrack/user_test.go b/providers/asrockrack/user_test.go new file mode 100644 index 00000000..eae58415 --- /dev/null +++ b/providers/asrockrack/user_test.go @@ -0,0 +1,234 @@ +package asrockrack + +import ( + "context" + "errors" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + bmclibErrs "github.com/bmc-toolbox/bmclib/errors" +) + +// NOTE: user accounts are defined in mock_test.go as JSON payload in the userPayload var + +type testCase struct { + user string + pass string + role string + ok bool + err error + tName string +} + +var ( + // common set of test cases + testCases = []testCase{ + + { + "foo", + "baz", + "", + false, + bmclibErrs.ErrInvalidUserRole, + "role not defined", + }, + { + "foo", + "", + "Administrator", + false, + bmclibErrs.ErrUserParamsRequired, + "param not defined", + }, + } +) + +func Test_UserRead(t *testing.T) { + expected := []map[string]string{ + { + "RoleID": "administrator", + "ID": "2", + "Name": "admin", + }, + { + "ID": "3", + "Name": "foo", + "RoleID": "administrator", + }, + } + + err := aClient.httpsLogin() + if err != nil { + t.Errorf(err.Error()) + } + + users, err := aClient.UserRead(context.TODO()) + if err != nil { + t.Error(err) + } + + assert.Equal(t, expected, users) + + for _, tt := range testCases { + ok, err := aClient.UserCreate(context.TODO(), tt.user, tt.pass, tt.role) + assert.Equal(t, errors.Is(err, tt.err), true, tt.tName) + assert.Equal(t, tt.ok, ok, tt.tName) + } + + // test account retrieval failure error + os.Setenv("TEST_FAIL_QUERY", "womp womp") + defer os.Unsetenv("TEST_FAIL_QUERY") + + _, err = aClient.UserRead(context.TODO()) + assert.Equal(t, errors.Is(err, bmclibErrs.ErrRetrievingUserAccounts), true) +} + +func Test_UserCreate(t *testing.T) { + + tests := testCases + tests = append(tests, + []testCase{{ + "root", + "calvin", + "Administrator", + true, + nil, + "user account is created", + }, + { + "admin", + "foo", + "Administrator", + false, + bmclibErrs.ErrUserAccountExists, + "account already exists", + }, + }..., + ) + + err := aClient.httpsLogin() + if err != nil { + t.Error(err) + } + + for _, tt := range tests { + ok, err := aClient.UserCreate(context.TODO(), tt.user, tt.pass, tt.role) + assert.Equal(t, errors.Is(err, tt.err), true, tt.tName) + assert.Equal(t, tt.ok, ok, tt.tName) + } +} + +func Test_UserUpdate(t *testing.T) { + tests := testCases + tests = append(tests, + []testCase{ + { + "admin", + "calvin", + "Administrator", + true, + nil, + "user account is updated", + }, + { + "badmin", + "calvin", + "Administrator", + false, + bmclibErrs.ErrUserAccountNotFound, + "user account not present", + }, + }..., + ) + + err := aClient.httpsLogin() + if err != nil { + t.Error(err) + } + + for _, tt := range tests { + ok, err := aClient.UserUpdate(context.TODO(), tt.user, tt.pass, tt.role) + assert.Equal(t, errors.Is(err, tt.err), true, tt.tName) + assert.Equal(t, tt.ok, ok, tt.tName) + } +} + +func Test_createUser(t *testing.T) { + err := aClient.httpsLogin() + if err != nil { + t.Errorf(err.Error()) + } + + account := &UserAccount{ + ID: 3, + Name: "foobar", + Access: 1, + Kvm: 1, + Vmedia: 1, + NetworkPrivilege: "administrator", + FixedUserCount: 2, + OEMProprietaryLevelPrivilege: 1, + PrivilegeLimitSerial: "none", + SSHKey: "Not Available", + CreationTime: 4802, + Changepassword: 0, + UserOperation: 0, + Password: "", + ConfirmPassword: "", + PasswordSize: "", + } + + err = aClient.createUpdateUser(account) + if err != nil { + t.Error(err) + } + + assert.Equal(t, "/api/settings/users/3", httpRequestTestVar.URL.String()) + assert.Equal(t, http.MethodPut, httpRequestTestVar.Method) + var contentType string + for k, v := range httpRequestTestVar.Header { + if k == "Content-Type" { + contentType = v[0] + } + } + + assert.Equal(t, "application/json", contentType) + +} + +func Test_userAccounts(t *testing.T) { + err := aClient.httpsLogin() + if err != nil { + t.Errorf(err.Error()) + } + + account0 := &UserAccount{ + ID: 1, + Name: "anonymous", + Access: 0, + Kvm: 1, + Vmedia: 1, + NetworkPrivilege: "administrator", + FixedUserCount: 2, + OEMProprietaryLevelPrivilege: 1, + PrivilegeLimitSerial: "none", + SSHKey: "Not Available", + CreationTime: 4802, + Changepassword: 0, + UserOperation: 0, + Password: "", + ConfirmPassword: "", + PasswordSize: "", + } + + accounts, err := aClient.listUsers() + if err != nil { + t.Error(err) + } + + assert.Equal(t, 10, len(accounts)) + assert.Equal(t, account0, accounts[0]) +} From 8cec5d6df3a6b607efbc6e53151b1af7eea269e6 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 15 Oct 2021 18:27:03 +0200 Subject: [PATCH 08/12] providers/asrockrack: purge debug spew, fix test --- providers/asrockrack/user.go | 2 -- providers/asrockrack/user_test.go | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/providers/asrockrack/user.go b/providers/asrockrack/user.go index 056429df..261476ee 100644 --- a/providers/asrockrack/user.go +++ b/providers/asrockrack/user.go @@ -5,7 +5,6 @@ import ( "fmt" "strings" - "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" bmclibErrs "github.com/bmc-toolbox/bmclib/errors" @@ -148,7 +147,6 @@ func (a *ASRockRack) UserUpdate(ctx context.Context, user, pass, role string) (o return false, errors.Wrap(bmclibErrs.ErrUserAccountUpdate, err.Error()) } - spew.Dump(account) return true, nil } } diff --git a/providers/asrockrack/user_test.go b/providers/asrockrack/user_test.go index eae58415..2003d836 100644 --- a/providers/asrockrack/user_test.go +++ b/providers/asrockrack/user_test.go @@ -222,6 +222,7 @@ func Test_userAccounts(t *testing.T) { Password: "", ConfirmPassword: "", PasswordSize: "", + EmailFormat: "ami_format", } accounts, err := aClient.listUsers() From 1e73ef3638edc674361152973cfb1f8f2ad55dc5 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 15 Oct 2021 18:27:28 +0200 Subject: [PATCH 09/12] go: switch test asset package; go mod tidy --- go.mod | 1 - go.sum | 3 --- 2 files changed, 4 deletions(-) diff --git a/go.mod b/go.mod index 0f232ee3..2c7dac28 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/gebn/bmc v0.0.0-20200904230046-a5643220ab2a github.com/go-logr/logr v0.4.0 github.com/go-logr/zapr v0.4.0 // indirect - github.com/go-playground/assert/v2 v2.0.1 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect github.com/google/go-cmp v0.5.5 github.com/google/go-querystring v1.0.0 diff --git a/go.sum b/go.sum index 32cdc9c6..6725db7e 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,6 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/zapr v0.3.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= @@ -259,7 +257,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= From c9201f7e85a9148ae943e4b2630a0c91a7ad5e49 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Mon, 18 Oct 2021 11:01:09 +0200 Subject: [PATCH 10/12] redfish: update user example; check DEBUG_BMCLIB env var for 'true' val --- examples/v1/users/user.go | 8 ++++++-- providers/redfish/redfish.go | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/v1/users/user.go b/examples/v1/users/user.go index b80dd6cc..99552899 100644 --- a/examples/v1/users/user.go +++ b/examples/v1/users/user.go @@ -13,6 +13,8 @@ import ( func main() { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() + + // set BMC parameters here host := "" port := "" user := "" @@ -22,14 +24,16 @@ func main() { l.Level = logrus.DebugLevel logger := logrusr.NewLogger(l) - var err error + if host == "" || user == "" || pass == "" { + log.Fatal("required host/user/pass parameters not defined") + } cl := bmclib.NewClient(host, port, user, pass, bmclib.WithLogger(logger)) // we may want to specify multiple protocols here cl.Registry.Drivers = cl.Registry.Using("redfish") - err = cl.Open(ctx) + err := cl.Open(ctx) if err != nil { log.Fatal(err, "bmc login failed") } diff --git a/providers/redfish/redfish.go b/providers/redfish/redfish.go index 3c3f6c80..07a1a529 100644 --- a/providers/redfish/redfish.go +++ b/providers/redfish/redfish.go @@ -51,7 +51,7 @@ func (c *Conn) Open(ctx context.Context) (err error) { } debug := os.Getenv("DEBUG_BMCLIB") - if debug != "" { + if debug == "true" { config.DumpWriter = os.Stdout } From 13cdcf700cfe5c18b922bcb0e8ce31c78d798166 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Tue, 19 Oct 2021 14:45:09 +0200 Subject: [PATCH 11/12] providers/asrockrack: purge invalid/unused comments, use http.StatusOK const instead of 200 --- providers/asrockrack/helpers.go | 24 ++++++++++++------------ providers/asrockrack/mock_test.go | 2 -- providers/asrockrack/user.go | 1 - 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/providers/asrockrack/helpers.go b/providers/asrockrack/helpers.go index 2b066e60..7d37e451 100644 --- a/providers/asrockrack/helpers.go +++ b/providers/asrockrack/helpers.go @@ -67,7 +67,7 @@ func (a *ASRockRack) listUsers() ([]*UserAccount, error) { return nil, err } - if statusCode != 200 { + if statusCode != http.StatusOK { return nil, fmt.Errorf("non 200 response: %d", statusCode) } @@ -95,7 +95,7 @@ func (a *ASRockRack) createUpdateUser(account *UserAccount) error { return err } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response: %d", statusCode) } @@ -113,7 +113,7 @@ func (a *ASRockRack) setFlashMode() error { return err } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response: %d", statusCode) } @@ -175,7 +175,7 @@ func (a *ASRockRack) uploadFirmware(endpoint string, fwReader io.Reader, fileSiz return err } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response: %d", statusCode) } @@ -191,7 +191,7 @@ func (a *ASRockRack) verifyUploadedFirmware() error { return err } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response: %d", statusCode) } @@ -215,7 +215,7 @@ func (a *ASRockRack) upgradeBMC() error { return err } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response: %d", statusCode) } @@ -231,7 +231,7 @@ func (a *ASRockRack) reset() error { return err } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response: %d", statusCode) } @@ -245,7 +245,7 @@ func (a *ASRockRack) flashProgress(endpoint string) (*upgradeProgress, error) { return nil, err } - if statusCode != 200 { + if statusCode != http.StatusOK { return nil, fmt.Errorf("non 200 response: %d", statusCode) } @@ -267,7 +267,7 @@ func (a *ASRockRack) firmwareInfo() (*firmwareInfo, error) { return nil, err } - if statusCode != 200 { + if statusCode != http.StatusOK { return nil, fmt.Errorf("non 200 response: %d", statusCode) } @@ -298,7 +298,7 @@ func (a *ASRockRack) biosUpgradeConfiguration() error { return err } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response: %d", statusCode) } @@ -328,7 +328,7 @@ func (a *ASRockRack) biosUpgrade() error { return err } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response: %d", statusCode) } @@ -386,7 +386,7 @@ func (a *ASRockRack) httpsLogout() error { return fmt.Errorf("Error logging out: " + err.Error()) } - if statusCode != 200 { + if statusCode != http.StatusOK { return fmt.Errorf("non 200 response at https logout: %d", statusCode) } diff --git a/providers/asrockrack/mock_test.go b/providers/asrockrack/mock_test.go index c8e1ac7b..23d6ef98 100644 --- a/providers/asrockrack/mock_test.go +++ b/providers/asrockrack/mock_test.go @@ -109,7 +109,6 @@ func userAccountList(w http.ResponseWriter, r *http.Request) { } func biosFirmwareUpgrade(w http.ResponseWriter, r *http.Request) { - // fmt.Printf("%s -> %s\n", r.Method, r.RequestURI) switch r.Method { case "POST": switch r.RequestURI { @@ -130,7 +129,6 @@ func biosFirmwareUpgrade(w http.ResponseWriter, r *http.Request) { } func bmcFirmwareUpgrade(w http.ResponseWriter, r *http.Request) { - // fmt.Printf("%s -> %s\n", r.Method, r.RequestURI) switch r.Method { case "GET": switch r.RequestURI { diff --git a/providers/asrockrack/user.go b/providers/asrockrack/user.go index 261476ee..7091bfec 100644 --- a/providers/asrockrack/user.go +++ b/providers/asrockrack/user.go @@ -131,7 +131,6 @@ func (a *ASRockRack) UserUpdate(ctx context.Context, user, pass, role string) (o // identify account slot not in use for _, account := range accounts { - // ASRR BMCs have a reserved slot 1 for a disabled Anonymous, no idea why. account := account if account.Name == user { user := newUserAccount(account.ID, user, pass, role) From 6a51f168e4e60b9f60b61a17e76b3afd0e94cf55 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Tue, 19 Oct 2021 15:00:04 +0200 Subject: [PATCH 12/12] providers/redfish: purge dup StringInSlice() method --- providers/redfish/user.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/providers/redfish/user.go b/providers/redfish/user.go index 5692cc57..c3c26b50 100644 --- a/providers/redfish/user.go +++ b/providers/redfish/user.go @@ -3,6 +3,7 @@ package redfish import ( "context" + "github.com/bmc-toolbox/bmclib/internal" "github.com/pkg/errors" "github.com/stmcginnis/gofish/redfish" ) @@ -84,7 +85,7 @@ func (c *Conn) UserUpdate(ctx context.Context, user, pass, role string) (ok bool // UserCreate adds a new user account func (c *Conn) UserCreate(ctx context.Context, user, pass, role string) (ok bool, err error) { - if !StringInSlice(role, ValidRoles) { + if !internal.StringInSlice(role, ValidRoles) { return false, ErrInvalidUserRole } @@ -106,7 +107,7 @@ func (c *Conn) UserCreate(ctx context.Context, user, pass, role string) (ok bool // identify account slot not in use for _, account := range accounts { // Dell iDracs don't want us to create accounts in these slots - if StringInSlice(account.ID, []string{"1"}) { + if internal.StringInSlice(account.ID, []string{"1"}) { continue } @@ -132,12 +133,3 @@ func (c *Conn) UserCreate(ctx context.Context, user, pass, role string) (ok bool return false, ErrNoUserSlotsAvailable } - -func StringInSlice(str string, sl []string) bool { - for _, s := range sl { - if str == s { - return true - } - } - return false -}