Skip to content

Commit a0f6cbf

Browse files
Add pure go ipmi provider:
This provider is written in pure Go and speaks the ipmi protocol. This means we can communicate via ipmi without the ipmitool binary available on the system. Signed-off-by: Jacob Weinstock <[email protected]>
1 parent a1b87e2 commit a0f6cbf

File tree

5 files changed

+276
-1
lines changed

5 files changed

+276
-1
lines changed

client.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/bmc-toolbox/bmclib/v2/internal/httpclient"
1414
"github.com/bmc-toolbox/bmclib/v2/providers/asrockrack"
1515
"github.com/bmc-toolbox/bmclib/v2/providers/dell"
16+
"github.com/bmc-toolbox/bmclib/v2/providers/goipmi"
1617
"github.com/bmc-toolbox/bmclib/v2/providers/intelamt"
1718
"github.com/bmc-toolbox/bmclib/v2/providers/ipmitool"
1819
"github.com/bmc-toolbox/bmclib/v2/providers/redfish"
@@ -58,6 +59,7 @@ type providerConfig struct {
5859
intelamt intelamt.Config
5960
dell dell.Config
6061
supermicro supermicro.Config
62+
goipmi goipmi.Config
6163
}
6264

6365
// NewClient returns a new Client struct
@@ -90,6 +92,10 @@ func NewClient(host, user, pass string, opts ...Option) *Client {
9092
supermicro: supermicro.Config{
9193
Port: "443",
9294
},
95+
goipmi: goipmi.Config{
96+
CipherSuite: 3,
97+
Port: 623,
98+
},
9399
},
94100
}
95101

@@ -187,6 +193,16 @@ func (c *Client) registerProviders() {
187193
smcHttpClient.Transport = c.httpClient.Transport.(*http.Transport).Clone()
188194
driverSupermicro := supermicro.NewClient(c.Auth.Host, c.Auth.User, c.Auth.Pass, c.Logger, supermicro.WithHttpClient(&smcHttpClient), supermicro.WithPort(c.providerConfig.supermicro.Port))
189195
c.Registry.Register(supermicro.ProviderName, supermicro.ProviderProtocol, supermicro.Features, nil, driverSupermicro)
196+
197+
// register goipmi provider
198+
goipmiOpts := []goipmi.Option{goipmi.WithCipherSuite(c.providerConfig.goipmi.CipherSuite)}
199+
driverGoipmi, err := goipmi.New(c.Auth.Host, c.providerConfig.goipmi.Port, c.Auth.User, c.Auth.Pass, goipmiOpts...)
200+
if err != nil {
201+
c.Logger.Info("goipmi provider not available", "error", err.Error())
202+
} else {
203+
driverGoipmi.Log = c.Logger
204+
c.Registry.Register(goipmi.ProviderName, goipmi.ProviderProtocol, goipmi.Features, nil, driverGoipmi)
205+
}
190206
}
191207

192208
// GetMetadata returns the metadata that is populated after each BMC function/method call

go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.18
55
require (
66
github.com/bmc-toolbox/common v0.0.0-20230220061748-93ff001f4a1d
77
github.com/bombsimon/logrusr/v2 v2.0.1
8+
github.com/bougou/go-ipmi v0.4.1-0.20230702095700-b7dd37559417
89
github.com/go-logr/logr v1.2.4
910
github.com/google/go-cmp v0.5.9
1011
github.com/hashicorp/go-multierror v1.1.1
@@ -25,8 +26,14 @@ require (
2526
github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230 // indirect
2627
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 // indirect
2728
github.com/davecgh/go-spew v1.1.1 // indirect
29+
github.com/google/uuid v1.1.2 // indirect
2830
github.com/hashicorp/errwrap v1.1.0 // indirect
31+
github.com/kr/pretty v0.3.0 // indirect
32+
github.com/kr/text v0.2.0 // indirect
33+
github.com/mattn/go-runewidth v0.0.9 // indirect
34+
github.com/olekukonko/tablewriter v0.0.5 // indirect
2935
github.com/pmezard/go-difflib v1.0.0 // indirect
36+
github.com/rogpeppe/go-internal v1.6.1 // indirect
3037
github.com/satori/go.uuid v1.2.0 // indirect
3138
golang.org/x/sys v0.1.0 // indirect
3239
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ github.com/bmc-toolbox/common v0.0.0-20230220061748-93ff001f4a1d h1:cQ30Wa8mhLzK
66
github.com/bmc-toolbox/common v0.0.0-20230220061748-93ff001f4a1d/go.mod h1:SY//n1PJjZfbFbmAsB6GvEKbc7UXz3d30s3kWxfJQ/c=
77
github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM=
88
github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio=
9+
github.com/bougou/go-ipmi v0.4.0 h1:lt25FldaHHmvjJFp62dEH+cc3u0VpzLAOcALrLl6HYc=
10+
github.com/bougou/go-ipmi v0.4.0/go.mod h1:+MKvz/6aFcJNmoQm27SLj41BJ5vdYxiQiQxdLZXQ78o=
11+
github.com/bougou/go-ipmi v0.4.1-0.20230702095700-b7dd37559417 h1:sOpeHNFf3ObqFzqFyl1XZnqhYrPG8AoL1OPB0bJp14E=
12+
github.com/bougou/go-ipmi v0.4.1-0.20230702095700-b7dd37559417/go.mod h1:+MKvz/6aFcJNmoQm27SLj41BJ5vdYxiQiQxdLZXQ78o=
913
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
1014
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1115
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -15,6 +19,8 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
1519
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
1620
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
1721
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
22+
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
23+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1824
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
1925
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
2026
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -25,16 +31,24 @@ github.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef/go.mod h1:Fgmi
2531
github.com/jacobweinstock/registrar v0.4.7 h1:s4dOExccgD+Pc7rJC+f3Mc3D+NXHcXUaOibtcEsPxOc=
2632
github.com/jacobweinstock/registrar v0.4.7/go.mod h1:PWmkdGFG5/ZdCqgMo7pvB3pXABOLHc5l8oQ0sgmBNDU=
2733
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
28-
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
34+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
2935
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
36+
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
37+
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
3038
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
3139
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
3240
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
3341
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
42+
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
43+
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
44+
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
45+
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
3446
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
3547
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
3648
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3749
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
50+
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
51+
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
3852
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
3953
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
4054
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
@@ -63,8 +77,10 @@ golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
6377
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6478
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
6579
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
80+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6681
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
6782
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
83+
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
6884
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
6985
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
7086
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

option.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,9 @@ func WithDellRedfishUseBasicAuth(useBasicAuth bool) Option {
137137
args.providerConfig.dell.UseBasicAuth = useBasicAuth
138138
}
139139
}
140+
141+
func WithGoipmiCipherSuite(cipherSuite int) Option {
142+
return func(args *Client) {
143+
args.providerConfig.goipmi.CipherSuite = cipherSuite
144+
}
145+
}

providers/goipmi/goipmi.go

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package goipmi
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
"github.com/bmc-toolbox/bmclib/v2/providers"
10+
"github.com/bougou/go-ipmi"
11+
"github.com/go-logr/logr"
12+
"github.com/jacobweinstock/registrar"
13+
)
14+
15+
const (
16+
// ProviderName for the provider pure go ipmi (go-ipmi) implementation.
17+
ProviderName = "goipmi"
18+
// ProviderProtocol for the provider pure go ipmi (go-ipmi) implementation.
19+
ProviderProtocol = "ipmi"
20+
)
21+
22+
var (
23+
// Features implemented by goipmi.
24+
Features = registrar.Features{
25+
providers.FeaturePowerSet,
26+
providers.FeaturePowerState,
27+
providers.FeatureBootDeviceSet,
28+
providers.FeatureUserRead,
29+
}
30+
)
31+
32+
// Config for goipmi provider.
33+
type Config struct {
34+
CipherSuite int
35+
Log logr.Logger
36+
Port int
37+
38+
client *ipmi.Client
39+
}
40+
41+
// Option for setting optional Client values.
42+
type Option func(*Config)
43+
44+
func New(host string, port int, user, pass string, opts ...Option) (*Config, error) {
45+
cl, err := ipmi.NewClient(host, port, user, pass)
46+
if err != nil {
47+
return nil, err
48+
}
49+
c := &Config{
50+
CipherSuite: 3,
51+
Log: logr.Discard(),
52+
client: cl,
53+
}
54+
for _, opt := range opts {
55+
opt(c)
56+
}
57+
c.client.WithInterface(ipmi.InterfaceLanplus)
58+
c.client.WithCipherSuiteID(toCipherSuiteID(c.CipherSuite))
59+
60+
return c, nil
61+
}
62+
63+
func WithCipherSuite(cipherSuite int) Option {
64+
return func(c *Config) {
65+
c.CipherSuite = cipherSuite
66+
}
67+
}
68+
69+
func (c *Config) Name() string {
70+
return ProviderName
71+
}
72+
73+
func (c *Config) Open(ctx context.Context) error {
74+
c.client.WithTimeout(getTimeout(ctx))
75+
return c.client.Connect()
76+
}
77+
78+
func getTimeout(ctx context.Context) time.Duration {
79+
deadline, ok := ctx.Deadline()
80+
if !ok {
81+
return 30 * time.Second
82+
}
83+
84+
return time.Until(deadline)
85+
}
86+
87+
func (c *Config) Close(_ context.Context) error {
88+
return c.client.Close()
89+
}
90+
91+
func (c *Config) PowerStateGet(_ context.Context) (string, error) {
92+
r, err := c.client.GetChassisStatus()
93+
if err != nil {
94+
return "", err
95+
}
96+
state := "off"
97+
if r.PowerIsOn {
98+
state = "on"
99+
}
100+
101+
return state, nil
102+
}
103+
104+
func (c *Config) PowerSet(_ context.Context, state string) (bool, error) {
105+
var action ipmi.ChassisControl
106+
switch strings.ToLower(state) {
107+
case "on":
108+
action = ipmi.ChassisControlPowerUp
109+
case "off":
110+
action = ipmi.ChassisControlPowerDown
111+
case "soft":
112+
action = ipmi.ChassisControlSoftShutdown
113+
case "reset":
114+
action = ipmi.ChassisControlHardwareRest
115+
case "cycle":
116+
action = ipmi.ChassisControlPowerCycle
117+
default:
118+
return false, fmt.Errorf("unknown or unimplemented state request: %v", state)
119+
}
120+
121+
// ipmi.ChassisControlResponse is an empty struct.
122+
// No methods return any actual response. So we ignore it.
123+
_, err := c.client.ChassisControl(action)
124+
if err != nil {
125+
return false, err
126+
}
127+
128+
return true, nil
129+
}
130+
131+
func (c *Config) BootDeviceSet(_ context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {
132+
d := ipmi.BootDeviceSelectorNoOverride
133+
switch strings.ToLower(bootDevice) {
134+
case "pxe":
135+
d = ipmi.BootDeviceSelectorForcePXE
136+
case "disk":
137+
d = ipmi.BootDeviceSelectorForceHardDrive
138+
case "safe":
139+
d = ipmi.BootDeviceSelectorForceHardDriveSafe
140+
case "diag":
141+
d = ipmi.BootDeviceSelectorForceDiagnosticPartition
142+
case "cdrom":
143+
d = ipmi.BootDeviceSelectorForceCDROM
144+
case "bios":
145+
d = ipmi.BootDeviceSelectorForceBIOSSetup
146+
case "floppy":
147+
d = ipmi.BootDeviceSelectorForceFloppy
148+
}
149+
bt := ipmi.BIOSBootTypeLegacy
150+
if efiBoot {
151+
bt = ipmi.BIOSBootTypeEFI
152+
}
153+
154+
if err := c.client.SetBootDevice(d, bt, setPersistent); err != nil {
155+
return false, err
156+
}
157+
158+
return true, nil
159+
}
160+
161+
func (c *Config) UserRead(_ context.Context) (users []map[string]string, err error) {
162+
u, err := c.client.ListUser(0)
163+
if err != nil {
164+
return nil, err
165+
}
166+
167+
for _, v := range u {
168+
if v.Name == "" {
169+
continue
170+
}
171+
users = append(users, map[string]string{
172+
"id": fmt.Sprintf("%v", v.ID),
173+
"name": v.Name,
174+
"callin": fmt.Sprintf("%v", v.Callin),
175+
"linkAuth": fmt.Sprintf("%v", v.LinkAuthEnabled),
176+
"ipmiMsg": fmt.Sprintf("%v", v.IPMIMessagingEnabled),
177+
"channelPrivLimit": fmt.Sprintf("%v", v.MaxPrivLevel),
178+
})
179+
}
180+
181+
return users, nil
182+
}
183+
184+
func toCipherSuiteID(c int) ipmi.CipherSuiteID {
185+
switch c {
186+
case 0:
187+
return ipmi.CipherSuiteID0
188+
case 1:
189+
return ipmi.CipherSuiteID1
190+
case 2:
191+
return ipmi.CipherSuiteID2
192+
case 3:
193+
return ipmi.CipherSuiteID3
194+
case 4:
195+
return ipmi.CipherSuiteID4
196+
case 5:
197+
return ipmi.CipherSuiteID5
198+
case 6:
199+
return ipmi.CipherSuiteID6
200+
case 7:
201+
return ipmi.CipherSuiteID7
202+
case 8:
203+
return ipmi.CipherSuiteID8
204+
case 9:
205+
return ipmi.CipherSuiteID9
206+
case 10:
207+
return ipmi.CipherSuiteID10
208+
case 11:
209+
return ipmi.CipherSuiteID11
210+
case 12:
211+
return ipmi.CipherSuiteID12
212+
case 13:
213+
return ipmi.CipherSuiteID13
214+
case 14:
215+
return ipmi.CipherSuiteID14
216+
case 15:
217+
return ipmi.CipherSuiteID15
218+
case 16:
219+
return ipmi.CipherSuiteID16
220+
case 17:
221+
return ipmi.CipherSuiteID17
222+
case 18:
223+
return ipmi.CipherSuiteID18
224+
case 19:
225+
return ipmi.CipherSuiteID19
226+
default:
227+
return ipmi.CipherSuiteID3
228+
}
229+
230+
}

0 commit comments

Comments
 (0)