Skip to content

Commit

Permalink
RPC provider (#343)
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Sep 13, 2023
2 parents f99e587 + 61c88e3 commit b1092ac
Show file tree
Hide file tree
Showing 18 changed files with 1,255 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ bmclib v2 is a library to abstract interacting with baseboard management control
- [IPMItool](https://github.com/bmc-toolbox/bmclib/tree/main/providers/ipmitool)
- [Intel AMT](https://github.com/bmc-toolbox/bmclib/tree/main/providers/intelamt)
- [Asrockrack](https://github.com/bmc-toolbox/bmclib/tree/main/providers/asrockrack)
- [RPC](providers/rpc/)

## Installation

Expand Down
27 changes: 27 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ package bmclib

import (
"context"
"fmt"
"io"
"net/http"
"sync"
"time"

"dario.cat/mergo"
"github.com/bmc-toolbox/bmclib/v2/bmc"
"github.com/bmc-toolbox/bmclib/v2/internal/httpclient"
"github.com/bmc-toolbox/bmclib/v2/providers/asrockrack"
"github.com/bmc-toolbox/bmclib/v2/providers/dell"
"github.com/bmc-toolbox/bmclib/v2/providers/intelamt"
"github.com/bmc-toolbox/bmclib/v2/providers/ipmitool"
"github.com/bmc-toolbox/bmclib/v2/providers/redfish"
"github.com/bmc-toolbox/bmclib/v2/providers/rpc"
"github.com/bmc-toolbox/bmclib/v2/providers/supermicro"
"github.com/bmc-toolbox/common"
"github.com/go-logr/logr"
Expand Down Expand Up @@ -58,6 +61,7 @@ type providerConfig struct {
intelamt intelamt.Config
dell dell.Config
supermicro supermicro.Config
rpc rpc.Provider
}

// NewClient returns a new Client struct
Expand Down Expand Up @@ -90,6 +94,7 @@ func NewClient(host, user, pass string, opts ...Option) *Client {
supermicro: supermicro.Config{
Port: "443",
},
rpc: rpc.Provider{},
},
}

Expand Down Expand Up @@ -129,7 +134,29 @@ func (c *Client) defaultTimeout(ctx context.Context) time.Duration {
return time.Until(deadline) / time.Duration(l)
}

func (c *Client) registerRPCProvider() error {
driverRPC := rpc.New(c.providerConfig.rpc.ConsumerURL, c.Auth.Host, c.providerConfig.rpc.Opts.HMAC.Secrets)
c.providerConfig.rpc.Logger = c.Logger
if err := mergo.Merge(driverRPC, c.providerConfig.rpc, mergo.WithOverride, mergo.WithTransformers(&rpc.Provider{})); err != nil {
return fmt.Errorf("failed to merge user specified rpc config with the config defaults, rpc provider not available: %w", err)
}
c.Registry.Register(rpc.ProviderName, rpc.ProviderProtocol, rpc.Features, nil, driverRPC)

return nil
}

func (c *Client) registerProviders() {
// register the rpc provider
// without the consumer URL there is no way to send RPC requests.
if c.providerConfig.rpc.ConsumerURL != "" {
// when the rpc provider is to be used, we won't register any other providers.
err := c.registerRPCProvider()
if err == nil {
c.Logger.Info("note: with the rpc provider registered, no other providers will be registered and available")
return
}
c.Logger.Info("failed to register rpc provider, falling back to registering all other providers", "error", err.Error())
}
// register ipmitool provider
ipmiOpts := []ipmitool.Option{
ipmitool.WithLogger(c.Logger),
Expand Down
3 changes: 1 addition & 2 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ func TestBMC(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
cl := NewClient(host, user, pass, WithLogger(log), WithPerProviderTimeout(5*time.Second))
err := cl.Open(ctx)
if err != nil {
if err := cl.Open(ctx); err != nil {
t.Logf("%+v", cl.GetMetadata())
t.Fatal(err)
}
Expand Down
94 changes: 94 additions & 0 deletions examples/rpc/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"context"
"encoding/json"
"net/http"
"time"

"github.com/bmc-toolbox/bmclib/v2"
"github.com/bmc-toolbox/bmclib/v2/logging"
"github.com/bmc-toolbox/bmclib/v2/providers/rpc"
)

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Start the test consumer
go testConsumer(ctx)
time.Sleep(100 * time.Millisecond)

log := logging.ZeroLogger("info")
opts := []bmclib.Option{
bmclib.WithLogger(log),
bmclib.WithPerProviderTimeout(5 * time.Second),
bmclib.WithRPCOpt(rpc.Provider{
ConsumerURL: "http://localhost:8800",
// Opts are not required.
Opts: rpc.Opts{
HMAC: rpc.HMACOpts{
Secrets: rpc.Secrets{rpc.SHA256: {"superSecret1"}},
},
Signature: rpc.SignatureOpts{
HeaderName: "X-Bespoke-Signature",
IncludedPayloadHeaders: []string{"X-Bespoke-Timestamp"},
},
Request: rpc.RequestOpts{
TimestampHeader: "X-Bespoke-Timestamp",
},
},
}),
}
host := "127.0.1.1"
user := "admin"
pass := "admin"
c := bmclib.NewClient(host, user, pass, opts...)
if err := c.Open(ctx); err != nil {
panic(err)
}
defer c.Close(ctx)

state, err := c.GetPowerState(ctx)
if err != nil {
panic(err)
}
log.Info("power state", "state", state)
log.Info("metadata for GetPowerState", "metadata", c.GetMetadata())

ok, err := c.SetPowerState(ctx, "on")
if err != nil {
panic(err)
}
log.Info("set power state", "ok", ok)
log.Info("metadata for SetPowerState", "metadata", c.GetMetadata())

<-ctx.Done()
}

func testConsumer(ctx context.Context) error {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
req := rpc.RequestPayload{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
rp := rpc.ResponsePayload{
ID: req.ID,
Host: req.Host,
}
switch req.Method {
case rpc.PowerGetMethod:
rp.Result = "on"
case rpc.PowerSetMethod:

case rpc.BootDeviceMethod:

default:
w.WriteHeader(http.StatusNotFound)
}
b, _ := json.Marshal(rp)
w.Write(b)
})

return http.ListenAndServe(":8800", nil)
}
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ module github.com/bmc-toolbox/bmclib/v2
go 1.18

require (
dario.cat/mergo v1.0.0
github.com/Jeffail/gabs/v2 v2.7.0
github.com/bmc-toolbox/common v0.0.0-20230220061748-93ff001f4a1d
github.com/bombsimon/logrusr/v2 v2.0.1
github.com/ghodss/yaml v1.0.0
github.com/go-logr/logr v1.2.4
github.com/go-logr/zerologr v1.2.3
github.com/google/go-cmp v0.5.9
github.com/hashicorp/go-multierror v1.1.1
github.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef
github.com/jacobweinstock/registrar v0.4.7
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.30.0
github.com/sirupsen/logrus v1.8.1
github.com/stmcginnis/gofish v0.14.0
github.com/stretchr/testify v1.8.0
Expand All @@ -26,8 +31,11 @@ require (
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
golang.org/x/sys v0.1.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=
github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=
github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230 h1:t95Grn2mOPfb3+kPDWsNnj4dlNcxnvuR72IjY8eYjfQ=
github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230/go.mod h1:t2EzW1qybnPDQ3LR/GgeF0GOzHUXT5IVMLP2gkW1cmc=
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 h1:a0MBqYm44o0NcthLKCljZHe1mxlN6oahCQHHThnSwB4=
Expand All @@ -6,13 +10,19 @@ github.com/bmc-toolbox/common v0.0.0-20230220061748-93ff001f4a1d h1:cQ30Wa8mhLzK
github.com/bmc-toolbox/common v0.0.0-20230220061748-93ff001f4a1d/go.mod h1:SY//n1PJjZfbFbmAsB6GvEKbc7UXz3d30s3kWxfJQ/c=
github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM=
github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs=
github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
Expand All @@ -31,10 +41,17 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
Expand All @@ -59,6 +76,8 @@ golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
Expand All @@ -67,6 +86,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
2 changes: 1 addition & 1 deletion lint.mk
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ LINTERS :=
FIXERS :=

GOLANGCI_LINT_CONFIG := $(LINT_ROOT)/.golangci.yml
GOLANGCI_LINT_VERSION ?= v1.51.2
GOLANGCI_LINT_VERSION ?= v1.53.3
GOLANGCI_LINT_BIN := $(LINT_ROOT)/out/linters/golangci-lint-$(GOLANGCI_LINT_VERSION)-$(LINT_ARCH)
$(GOLANGCI_LINT_BIN):
mkdir -p $(LINT_ROOT)/out/linters
Expand Down
19 changes: 19 additions & 0 deletions logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (

"github.com/bombsimon/logrusr/v2"
"github.com/go-logr/logr"
"github.com/go-logr/zerologr"
"github.com/rs/zerolog"
"github.com/sirupsen/logrus"
)

Expand All @@ -26,3 +28,20 @@ func DefaultLogger() logr.Logger {

return logrusr.New(logrusLog)
}

// ZeroLogger is a logr.Logger implementation that uses zerolog.
// This logger handles nested structs better than the logrus implementation.
func ZeroLogger(level string) logr.Logger {
zl := zerolog.New(os.Stdout)
zl = zl.With().Caller().Timestamp().Logger()
var l zerolog.Level
switch level {
case "debug":
l = zerolog.DebugLevel
default:
l = zerolog.InfoLevel
}
zl = zl.Level(l)

return zerologr.New(&zl)
}
7 changes: 7 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/bmc-toolbox/bmclib/v2/internal/httpclient"
"github.com/bmc-toolbox/bmclib/v2/providers/rpc"
"github.com/go-logr/logr"
"github.com/jacobweinstock/registrar"
)
Expand Down Expand Up @@ -137,3 +138,9 @@ func WithDellRedfishUseBasicAuth(useBasicAuth bool) Option {
args.providerConfig.dell.UseBasicAuth = useBasicAuth
}
}

func WithRPCOpt(opt rpc.Provider) Option {
return func(args *Client) {
args.providerConfig.rpc = opt
}
}
12 changes: 12 additions & 0 deletions providers/rpc/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
Package rpc is a provider that defines an HTTP request/response contract for handling BMC interactions.
It allows users a simple way to interoperate with an existing/bespoke out-of-band management solution.
The rpc provider request/response payloads are modeled after JSON-RPC 2.0, but are not JSON-RPC 2.0
compliant so as to allow for more flexibility and interoperability with existing systems.
The rpc provider has options that can be set to include an HMAC signature in the request header.
It follows the features found at https://webhooks.fyi/security/hmac, this includes hash algorithms sha256
and sha512, replay prevention, versioning, and key rotation.
*/
package rpc
26 changes: 26 additions & 0 deletions providers/rpc/experimental.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package rpc

import (
"github.com/Jeffail/gabs/v2"
"github.com/ghodss/yaml"
)

// embedPayload will embed the RequestPayload into the given JSON object at the dot path notation location ("object.data").
func (p *RequestPayload) embedPayload(rawJSON []byte, dotPath string) ([]byte, error) {
if rawJSON == nil {
return rawJSON, nil
}
jdata2, err := yaml.YAMLToJSON(rawJSON)
if err != nil {
return nil, err
}
g, err := gabs.ParseJSON(jdata2)
if err != nil {
return nil, err
}
if _, err := g.SetP(p, dotPath); err != nil {
return nil, err
}

return g.Bytes(), nil
}
Loading

0 comments on commit b1092ac

Please sign in to comment.