engine: resources: Add a bmc resource
This resource manages bmc devices in servers or elsewhere. This also integrates with the provisioner code.
This commit is contained in:
466
engine/resources/bmc_power.go
Normal file
466
engine/resources/bmc_power.go
Normal file
@@ -0,0 +1,466 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
"github.com/purpleidea/mgmt/engine/traits"
|
||||
|
||||
bmclib "github.com/bmc-toolbox/bmclib/v2"
|
||||
"github.com/bmc-toolbox/bmclib/v2/providers/rpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
engine.RegisterResource("bmc:power", func() engine.Res { return &BmcPowerRes{} })
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultBmcPowerPort is the default port we try to connect on.
|
||||
DefaultBmcPowerPort = 443
|
||||
|
||||
// BmcDriverSecureSuffix is the magic char we append to a driver name to
|
||||
// specify we want the SSL/TLS variant.
|
||||
BmcDriverSecureSuffix = "s"
|
||||
|
||||
// BmcDriverRPC is the RPC driver.
|
||||
BmcDriverRPC = "rpc"
|
||||
|
||||
// BmcDriverGofish is the gofish driver.
|
||||
BmcDriverGofish = "gofish"
|
||||
)
|
||||
|
||||
// BmcPowerRes is a resource that manages power state of a BMC. This is usually
|
||||
// used for turning computers on and off. The name value can be a big URL string
|
||||
// in the form: `driver://user:pass@hostname:port` for example you may see:
|
||||
// gofishs://ADMIN:hunter2@127.0.0.1:8800 to use the "https" variant of the
|
||||
// gofish driver.
|
||||
//
|
||||
// NOTE: New drivers should either not end in "s" or at least not be identical
|
||||
// to the name of another driver an "s" is added or removed to the end.
|
||||
type BmcPowerRes struct {
|
||||
traits.Base // add the base methods without re-implementation
|
||||
|
||||
init *engine.Init
|
||||
|
||||
// Hostname to connect to. If not specified, we parse this from the
|
||||
// Name.
|
||||
Hostname string `lang:"hostname" yaml:"hostname"`
|
||||
|
||||
// Port to connect to. If not specified, we parse this from the Name.
|
||||
Port int `lang:"port" yaml:"port"`
|
||||
|
||||
// Username to use to connect. If not specified, we parse this from the
|
||||
// Name.
|
||||
// TODO: If the Username field is not set, should we parse from the
|
||||
// Name? It's not really part of the BMC unique identifier so maybe we
|
||||
// shouldn't use that.
|
||||
Username string `lang:"username" yaml:"username"`
|
||||
|
||||
// Password to use to connect. We do NOT parse this from the Name unless
|
||||
// you set InsecurePassword to true.
|
||||
// XXX: Use mgmt magic credentials in the future.
|
||||
Password string `lang:"password" yaml:"password"`
|
||||
|
||||
// InsecurePassword can be set to true to allow a password in the Name.
|
||||
InsecurePassword bool `lang:"insecure_password" yaml:"insecure_password"`
|
||||
|
||||
// Driver to use, such as: "gofish" or "rpc". This is a different
|
||||
// concept than the "bmclib" driver vs provider distinction. Here we
|
||||
// just statically pick what we're using without any magic. If not
|
||||
// specified, we parse this from the Name scheme. If this ends with an
|
||||
// extra "s" then we use https instead of http.
|
||||
Driver string `lang:"driver" yaml:"driver"`
|
||||
|
||||
// State of machine power. Can be "on" or "off".
|
||||
State string `lang:"state" yaml:"state"`
|
||||
|
||||
driver string
|
||||
scheme string
|
||||
}
|
||||
|
||||
// validDriver determines if we are using a valid drive. This does not include
|
||||
// the magic "s" bits. This function need to be expanded as we support more
|
||||
// drivers.
|
||||
func (obj *BmcPowerRes) validDriver(driver string) error {
|
||||
if driver == BmcDriverRPC {
|
||||
return nil
|
||||
}
|
||||
if driver == BmcDriverGofish {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown driver: %s", driver)
|
||||
}
|
||||
|
||||
// getHostname returns the hostname that we want to connect to. If the Hostname
|
||||
// field is set, we use that, otherwise we parse from the Name.
|
||||
func (obj *BmcPowerRes) getHostname() string {
|
||||
if obj.Hostname != "" {
|
||||
return obj.Hostname
|
||||
}
|
||||
|
||||
u, err := url.Parse(obj.Name())
|
||||
if err != nil || u == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SplitHostPort splits a network address of the form "host:port",
|
||||
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
|
||||
// host%zone and port.
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return u.Host // must be a naked hostname or ip w/o port
|
||||
}
|
||||
_ = port
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
// getPort returns the port that we want to connect to. If the Port field is
|
||||
// set, we use that, otherwise we parse from the Name.
|
||||
//
|
||||
// NOTE: We return a string since all the bmclib things usually expect a string,
|
||||
// but if that gets fixed we should return an int here instead.
|
||||
func (obj *BmcPowerRes) getPort() string {
|
||||
if obj.Port != 0 {
|
||||
return strconv.Itoa(obj.Port)
|
||||
}
|
||||
|
||||
u, err := url.Parse(obj.Name())
|
||||
if err != nil || u == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SplitHostPort splits a network address of the form "host:port",
|
||||
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
|
||||
// host%zone and port.
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return strconv.Itoa(DefaultBmcPowerPort) // default port
|
||||
}
|
||||
_ = host
|
||||
|
||||
return port
|
||||
}
|
||||
|
||||
// getUsername returns the username that we want to connect with. If the
|
||||
// Username field is set, we use that, otherwise we parse from the Name.
|
||||
// TODO: If the Username field is not set, should we parse from the Name? It's
|
||||
// not really part of the BMC unique identifier so maybe we shouldn't use that.
|
||||
func (obj *BmcPowerRes) getUsername() string {
|
||||
if obj.Username != "" {
|
||||
return obj.Username
|
||||
}
|
||||
|
||||
u, err := url.Parse(obj.Name())
|
||||
if err != nil || u == nil || u.User == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return u.User.Username()
|
||||
}
|
||||
|
||||
// getPassword returns the password that we want to connect with.
|
||||
// XXX: Use mgmt magic credentials in the future.
|
||||
func (obj *BmcPowerRes) getPassword() string {
|
||||
if obj.Password != "" || !obj.InsecurePassword {
|
||||
return obj.Password
|
||||
}
|
||||
// NOTE: We don't look at any password string from the name unless the
|
||||
// InsecurePassword field is true.
|
||||
|
||||
u, err := url.Parse(obj.Name())
|
||||
if err != nil || u == nil || u.User == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
password, ok := u.User.Password()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
// getRawDriver returns the raw magic driver string. If the Driver field is set,
|
||||
// we use that, otherwise we parse from the Name. This version may include the
|
||||
// magic "s" at the end.
|
||||
func (obj *BmcPowerRes) getRawDriver() string {
|
||||
if obj.Driver != "" {
|
||||
return obj.Driver
|
||||
}
|
||||
|
||||
u, err := url.Parse(obj.Name())
|
||||
if err != nil || u == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return u.Scheme
|
||||
}
|
||||
|
||||
// getDriverAndScheme figures out which driver and scheme we want to use.
|
||||
func (obj *BmcPowerRes) getDriverAndScheme() (string, string, error) {
|
||||
driver := obj.getRawDriver()
|
||||
err := obj.validDriver(driver)
|
||||
if err == nil {
|
||||
return driver, "http", nil
|
||||
}
|
||||
|
||||
driver = strings.TrimSuffix(driver, BmcDriverSecureSuffix)
|
||||
if err := obj.validDriver(driver); err == nil {
|
||||
return driver, "https", nil
|
||||
}
|
||||
|
||||
return "", "", err // return the first error
|
||||
}
|
||||
|
||||
// getDriver returns the actual driver that we want to connect with. If the
|
||||
// Driver field is set, we use that, otherwise we parse from the Name. This
|
||||
// version does NOT include the magic "s" at the end.
|
||||
func (obj *BmcPowerRes) getDriver() string {
|
||||
return obj.driver
|
||||
}
|
||||
|
||||
// getScheme figures out which scheme we want to use.
|
||||
func (obj *BmcPowerRes) getScheme() string {
|
||||
return obj.scheme
|
||||
}
|
||||
|
||||
// Default returns some sensible defaults for this resource.
|
||||
func (obj *BmcPowerRes) Default() engine.Res {
|
||||
return &BmcPowerRes{}
|
||||
}
|
||||
|
||||
// Validate if the params passed in are valid data.
|
||||
func (obj *BmcPowerRes) Validate() error {
|
||||
// XXX: Force polling until we have real events...
|
||||
if obj.MetaParams().Poll == 0 {
|
||||
return fmt.Errorf("events are not yet supported, use polling")
|
||||
}
|
||||
|
||||
if obj.getHostname() == "" {
|
||||
return fmt.Errorf("need a Hostname")
|
||||
}
|
||||
//if obj.getUsername() == "" {
|
||||
// return fmt.Errorf("need a Username")
|
||||
//}
|
||||
|
||||
if obj.getRawDriver() == "" {
|
||||
return fmt.Errorf("need a Driver")
|
||||
}
|
||||
if _, _, err := obj.getDriverAndScheme(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init runs some startup code for this resource.
|
||||
func (obj *BmcPowerRes) Init(init *engine.Init) error {
|
||||
obj.init = init // save for later
|
||||
|
||||
driver, scheme, err := obj.getDriverAndScheme()
|
||||
if err != nil {
|
||||
// programming error (we checked in Validate)
|
||||
return err
|
||||
}
|
||||
obj.driver = driver
|
||||
obj.scheme = scheme
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup is run by the engine to clean up after the resource is done.
|
||||
func (obj *BmcPowerRes) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// client builds the bmclib client. The API to build it is complicated.
|
||||
func (obj *BmcPowerRes) client() *bmclib.Client {
|
||||
// NOTE: The bmclib API is weird, you can't put the port in this string!
|
||||
u := fmt.Sprintf("%s://%s", obj.getScheme(), obj.getHostname())
|
||||
|
||||
uPort := u
|
||||
if p := obj.getPort(); p != "" {
|
||||
uPort = u + ":" + p
|
||||
}
|
||||
|
||||
opts := []bmclib.Option{}
|
||||
|
||||
if obj.getDriver() == BmcDriverRPC {
|
||||
opts = append(opts, bmclib.WithRPCOpt(rpc.Provider{
|
||||
// NOTE: The main API cannot take a port, but here we do!
|
||||
ConsumerURL: uPort,
|
||||
}))
|
||||
}
|
||||
|
||||
if p := obj.getPort(); p != "" {
|
||||
switch obj.getDriver() {
|
||||
case BmcDriverRPC:
|
||||
// TODO: ???
|
||||
|
||||
case BmcDriverGofish:
|
||||
// XXX: Why doesn't this accept an int?
|
||||
opts = append(opts, bmclib.WithRedfishPort(p))
|
||||
|
||||
//case BmcDriverOpenbmc:
|
||||
// // XXX: Why doesn't this accept an int?
|
||||
// opts = append(opts, openbmc.WithPort(p))
|
||||
|
||||
default:
|
||||
// TODO: error or pass through?
|
||||
obj.init.Logf("unhandled driver: %s", obj.getDriver())
|
||||
}
|
||||
}
|
||||
|
||||
client := bmclib.NewClient(u, obj.getUsername(), obj.Password, opts...)
|
||||
|
||||
if obj.getDriver() != "" && obj.getDriver() != BmcDriverRPC {
|
||||
client = client.For(obj.getDriver()) // limit to this provider
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// Watch is the primary listener for this resource and it outputs events.
|
||||
func (obj *BmcPowerRes) Watch(ctx context.Context) error {
|
||||
obj.init.Running() // when started, notify engine that we're running
|
||||
|
||||
select {
|
||||
case <-ctx.Done(): // closed by the engine to signal shutdown
|
||||
}
|
||||
|
||||
//obj.init.Event() // notify engine of an event (this can block)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckApply method for BmcPower resource. Does nothing, returns happy!
|
||||
func (obj *BmcPowerRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
|
||||
client := obj.client()
|
||||
|
||||
if err := client.Open(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer client.Close(ctx) // (err error)
|
||||
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("connected ok")
|
||||
}
|
||||
|
||||
state, err := client.GetPowerState(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
state = strings.ToLower(state) // normalize
|
||||
obj.init.Logf("get state: %s", state)
|
||||
|
||||
if !apply {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if obj.State == state {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TODO: should this be "On" and "Off"? Does case matter?
|
||||
ok, err := client.SetPowerState(ctx, obj.State)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
// TODO: When is this ever false?
|
||||
}
|
||||
obj.init.Logf("set state: %s", obj.State)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Cmp compares two resources and returns an error if they are not equivalent.
|
||||
func (obj *BmcPowerRes) Cmp(r engine.Res) error {
|
||||
// we can only compare BmcPowerRes to others of the same resource kind
|
||||
res, ok := r.(*BmcPowerRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("not a %s", obj.Kind())
|
||||
}
|
||||
|
||||
if obj.Hostname != res.Hostname {
|
||||
return fmt.Errorf("the Hostname differs")
|
||||
}
|
||||
if obj.Port != res.Port {
|
||||
return fmt.Errorf("the Port differs")
|
||||
}
|
||||
if obj.Username != res.Username {
|
||||
return fmt.Errorf("the Username differs")
|
||||
}
|
||||
if obj.Password != res.Password {
|
||||
return fmt.Errorf("the Password differs")
|
||||
}
|
||||
if obj.InsecurePassword != res.InsecurePassword {
|
||||
return fmt.Errorf("the InsecurePassword differs")
|
||||
}
|
||||
|
||||
if obj.Driver != res.Driver {
|
||||
return fmt.Errorf("the Driver differs")
|
||||
}
|
||||
if obj.State != res.State {
|
||||
return fmt.Errorf("the State differs")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
|
||||
// primarily useful for setting the defaults.
|
||||
func (obj *BmcPowerRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type rawRes BmcPowerRes // indirection to avoid infinite recursion
|
||||
|
||||
def := obj.Default() // get the default
|
||||
res, ok := def.(*BmcPowerRes) // put in the right format
|
||||
if !ok {
|
||||
return fmt.Errorf("could not convert to BmcPowerRes")
|
||||
}
|
||||
raw := rawRes(*res) // convert; the defaults go here
|
||||
|
||||
if err := unmarshal(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*obj = BmcPowerRes(raw) // restore from indirection with type conversion!
|
||||
return nil
|
||||
}
|
||||
12
examples/lang/bmc-power.mcl
Normal file
12
examples/lang/bmc-power.mcl
Normal file
@@ -0,0 +1,12 @@
|
||||
# A standard bmc might connect with:
|
||||
bmc:power "gofishs://ADMIN@127.0.0.1:8800" {
|
||||
#username => "ADMIN",
|
||||
password => "ADMIN",
|
||||
#driver => "gofishs", # https gofish
|
||||
|
||||
state => "on",
|
||||
|
||||
Meta:poll => 10, # required until BMC's support real events!
|
||||
}
|
||||
|
||||
# The testing rpc example can connect to: rpcs://127.0.0.1:8800 instead.
|
||||
45
examples/mockbmc/fixtures/service_root.json
Normal file
45
examples/mockbmc/fixtures/service_root.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"@odata.context": "/redfish/v1/$metadata#ServiceRoot.ServiceRoot",
|
||||
"@odata.type": "#ServiceRoot.v1_1_0.ServiceRoot",
|
||||
"@odata.id": "/redfish/v1/",
|
||||
"Id": "RootService",
|
||||
"Name": "Root Service",
|
||||
"RedfishVersion": "1.0.1",
|
||||
"UUID": "00000000-0000-0000-0000-0CC47A8BDADA",
|
||||
"Systems": {
|
||||
"@odata.id": "/redfish/v1/Systems"
|
||||
},
|
||||
"Chassis": {
|
||||
"@odata.id": "/redfish/v1/Chassis"
|
||||
},
|
||||
"Managers": {
|
||||
"@odata.id": "/redfish/v1/Managers"
|
||||
},
|
||||
"Tasks": {
|
||||
"@odata.id": "/redfish/v1/TaskService"
|
||||
},
|
||||
"SessionService": {
|
||||
"@odata.id": "/redfish/v1/SessionService"
|
||||
},
|
||||
"AccountService": {
|
||||
"@odata.id": "/redfish/v1/AccountService"
|
||||
},
|
||||
"EventService": {
|
||||
"@odata.id": "/redfish/v1/EventService"
|
||||
},
|
||||
"UpdateService": {
|
||||
"@odata.id": "/redfish/v1/UpdateService"
|
||||
},
|
||||
"Registries": {
|
||||
"@odata.id": "/redfish/v1/Registries"
|
||||
},
|
||||
"JsonSchemas": {
|
||||
"@odata.id": "/redfish/v1/JsonSchemas"
|
||||
},
|
||||
"Links": {
|
||||
"Sessions": {
|
||||
"@odata.id": "/redfish/v1/SessionService/Sessions"
|
||||
}
|
||||
},
|
||||
"Oem": {}
|
||||
}
|
||||
6
examples/mockbmc/fixtures/session_delete.json
Normal file
6
examples/mockbmc/fixtures/session_delete.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Success": {
|
||||
"code": "Base.v1_4_0.Success",
|
||||
"Message": "Successfully Completed Request."
|
||||
}
|
||||
}
|
||||
10
examples/mockbmc/fixtures/session_service.json
Normal file
10
examples/mockbmc/fixtures/session_service.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"@odata.context": "/redfish/v1/$metadata#Session.Session",
|
||||
"@odata.type": "#Session.v1_0_0.Session",
|
||||
"@odata.id": "/redfish/v1/SessionService/Sessions/1",
|
||||
"Id": "1",
|
||||
"Name": "User Session",
|
||||
"Description": "Manager User Session",
|
||||
"UserName": "ADMIN",
|
||||
"Oem": {}
|
||||
}
|
||||
13
examples/mockbmc/fixtures/systems.json
Normal file
13
examples/mockbmc/fixtures/systems.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"@odata.context": "/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection",
|
||||
"@odata.type": "#ComputerSystemCollection.ComputerSystemCollection",
|
||||
"@odata.id": "/redfish/v1/Systems",
|
||||
"Name": "Computer System Collection",
|
||||
"Description": "Computer System Collection",
|
||||
"Members@odata.count": 1,
|
||||
"Members": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/1"
|
||||
}
|
||||
]
|
||||
}
|
||||
107
examples/mockbmc/fixtures/systems_1.json.tmpl
Normal file
107
examples/mockbmc/fixtures/systems_1.json.tmpl
Normal file
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"@odata.context": "/redfish/v1/$metadata#ComputerSystem.ComputerSystem",
|
||||
"@odata.type": "#ComputerSystem.v1_3_0.ComputerSystem",
|
||||
"@odata.id": "/redfish/v1/Systems/1",
|
||||
"Id": "1",
|
||||
"Name": "System",
|
||||
"Description": "Description of server",
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "Critical"
|
||||
},
|
||||
"SerialNumber": " ",
|
||||
"PartNumber": "",
|
||||
"SystemType": "Physical",
|
||||
"BiosVersion": "3.3",
|
||||
"Manufacturer": "Supermicro",
|
||||
"Model": "Super Server",
|
||||
"SKU": "To be filled by O.E.M.",
|
||||
"UUID": "00000000-0000-0000-0000-0CC47A847624",
|
||||
"ProcessorSummary": {
|
||||
"Count": 1,
|
||||
"Model": "Intel(R) Xeon(R) processor",
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK"
|
||||
}
|
||||
},
|
||||
"MemorySummary": {
|
||||
"TotalSystemMemoryGiB": 16,
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK"
|
||||
}
|
||||
},
|
||||
"IndicatorLED": "Off",
|
||||
"PowerState": "{{ .PowerState }}",
|
||||
"Boot": {
|
||||
"BootSourceOverrideEnabled": "Disabled",
|
||||
"BootSourceOverrideTarget": "None",
|
||||
"BootSourceOverrideTarget@Redfish.AllowableValues": [
|
||||
"None",
|
||||
"Pxe",
|
||||
"Hdd",
|
||||
"Diags",
|
||||
"CD/DVD",
|
||||
"BiosSetup",
|
||||
"FloppyRemovableMedia",
|
||||
"UsbKey",
|
||||
"UsbHdd",
|
||||
"UsbFloppy",
|
||||
"UsbCd",
|
||||
"UefiUsbKey",
|
||||
"UefiCd",
|
||||
"UefiHdd",
|
||||
"UefiUsbHdd",
|
||||
"UefiUsbCd"
|
||||
]
|
||||
},
|
||||
"Processors": {
|
||||
"@odata.id": "/redfish/v1/Systems/1/Processors"
|
||||
},
|
||||
"Memory": {
|
||||
"@odata.id": "/redfish/v1/Systems/1/Memory"
|
||||
},
|
||||
"EthernetInterfaces": {
|
||||
"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces"
|
||||
},
|
||||
"SimpleStorage": {
|
||||
"@odata.id": "/redfish/v1/Systems/1/SimpleStorage"
|
||||
},
|
||||
"Storage": {
|
||||
"@odata.id": "/redfish/v1/Systems/1/Storage"
|
||||
},
|
||||
"LogServices": {
|
||||
"@odata.id": "/redfish/v1/Systems/1/LogServices"
|
||||
},
|
||||
"Links": {
|
||||
"Chassis": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Chassis/1"
|
||||
}
|
||||
],
|
||||
"ManagedBy": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Managers/1"
|
||||
}
|
||||
],
|
||||
"Oem": {}
|
||||
},
|
||||
"Actions": {
|
||||
"#ComputerSystem.Reset": {
|
||||
"target": "/redfish/v1/Systems/1/Actions/ComputerSystem.Reset",
|
||||
"ResetType@Redfish.AllowableValues": [
|
||||
"On",
|
||||
"ForceOff",
|
||||
"GracefulShutdown",
|
||||
"GracefulRestart",
|
||||
"ForceRestart",
|
||||
"Nmi",
|
||||
"ForceOn"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Oem": {
|
||||
"Supermicro": {}
|
||||
}
|
||||
}
|
||||
297
examples/mockbmc/mockbmc.go
Normal file
297
examples/mockbmc/mockbmc.go
Normal file
@@ -0,0 +1,297 @@
|
||||
// This is an example mock BMC server/device.
|
||||
// Many thanks to Joel Rebello for figuring out the specific endpoints needed.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
|
||||
"github.com/alexflint/go-arg"
|
||||
"github.com/bmc-toolbox/bmclib/v2/providers/rpc"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultPort to listen on. Seen in the bmclib docs.
|
||||
DefaultPort = 8800
|
||||
|
||||
// StateOn is the power "on" state.
|
||||
StateOn = "on"
|
||||
|
||||
// StateOff is the power "off" state.
|
||||
StateOff = "off"
|
||||
)
|
||||
|
||||
// MockBMC is a simple mocked BMC device.
|
||||
type MockBMC struct {
|
||||
// Addr to listen on. Eg: :8800 for example.
|
||||
Addr string
|
||||
|
||||
// State of the device power. This gets read and changed by the API.
|
||||
State string
|
||||
|
||||
// Driver specifies which driver we want to mock.
|
||||
// TODO: Do I mean "driver" or "provider" ?
|
||||
Driver string
|
||||
}
|
||||
|
||||
// Data is what we use to template the outputs.
|
||||
type Data struct {
|
||||
PowerState string
|
||||
}
|
||||
|
||||
// Run kicks this all off.
|
||||
func (obj *MockBMC) Run() error {
|
||||
|
||||
tls := util.NewTLS()
|
||||
tls.Host = "localhost" // TODO: choose something
|
||||
keyPemFile := "/tmp/key.pem"
|
||||
certPemFile := "/tmp/cert.pem"
|
||||
|
||||
if err := tls.Generate(keyPemFile, certPemFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("running at: %s\n", obj.Addr)
|
||||
fmt.Printf("driver is: %s\n", obj.Driver)
|
||||
fmt.Printf("device is: %s\n", obj.State) // we start off in this state
|
||||
if obj.Driver == "rpc" {
|
||||
http.HandleFunc("/", obj.rpcHandler)
|
||||
}
|
||||
if obj.Driver == "redfish" || obj.Driver == "gofish" {
|
||||
http.HandleFunc("/redfish/v1/", obj.endpointFunc("service_root.json", http.MethodGet, 200, nil))
|
||||
|
||||
// login
|
||||
sessionHeader := map[string]string{
|
||||
"X-Auth-Token": "t5tpiajo89fyvvel5434h9p2l3j69hzx", // TODO: how do we get this?
|
||||
"Location": "/redfish/v1/SessionService/Sessions/1",
|
||||
}
|
||||
|
||||
http.HandleFunc("/redfish/v1/SessionService/Sessions", obj.endpointFunc("session_service.json", http.MethodPost, 201, sessionHeader))
|
||||
|
||||
// get power state
|
||||
http.HandleFunc("/redfish/v1/Systems", obj.endpointFunc("systems.json", http.MethodGet, 200, nil))
|
||||
http.HandleFunc("/redfish/v1/Systems/1", obj.endpointFunc("systems_1.json.tmpl", http.MethodGet, 200, nil))
|
||||
|
||||
// set pxe - we can't have two routes with the same pattern
|
||||
//http.HandleFunc("/redfish/v1/Systems/1", obj.endpointFunc("", http.MethodPatch, 200, nil))
|
||||
|
||||
// power on/off XXX: seems to post here to turn on
|
||||
http.HandleFunc("/redfish/v1/Systems/1/Actions/ComputerSystem.Reset", obj.endpointFunc("", http.MethodPost, 200, nil))
|
||||
|
||||
// logoff
|
||||
http.HandleFunc("/redfish/v1/SessionService/Sessions/1", obj.endpointFunc("session_delete.json", http.MethodDelete, 200, nil))
|
||||
}
|
||||
|
||||
http.HandleFunc("/hello", obj.hello)
|
||||
//return http.ListenAndServe(obj.Addr, nil)
|
||||
return http.ListenAndServeTLS(obj.Addr, certPemFile, keyPemFile, nil)
|
||||
}
|
||||
|
||||
func (obj *MockBMC) template(templateText string, data interface{}) (string, error) {
|
||||
var err error
|
||||
tmpl := template.New("name") // whatever name you want
|
||||
//tmpl = tmpl.Funcs(funcMap)
|
||||
tmpl, err = tmpl.Parse(templateText)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// run the template
|
||||
if err := tmpl.Execute(buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// endpointFunc handles the bmc mock requirements.
|
||||
func (obj *MockBMC) endpointFunc(file, method string, retStatus int, retHeader map[string]string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
data := &Data{
|
||||
PowerState: obj.State,
|
||||
}
|
||||
|
||||
fmt.Printf("URL: %s\n", r.URL.Path)
|
||||
fmt.Printf("[%s] file: %s\n", method, file)
|
||||
//if method == "POST" {
|
||||
//for name, values := range r.Header {
|
||||
// fmt.Printf("\t[%s] header: %+v\n", name, values)
|
||||
//}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
fmt.Printf("error parsing form: %+v\n", err)
|
||||
}
|
||||
for name, values := range r.PostForm {
|
||||
fmt.Printf("\t[%s] values: %+v\n", name, values)
|
||||
}
|
||||
//}
|
||||
|
||||
// purge check on patch method if set pxe request is attempted
|
||||
if r.Method != method && r.Method != http.MethodPatch {
|
||||
resp := fmt.Sprintf("unexpected request - url: %s, method: %s", r.URL, r.Method)
|
||||
_, _ = w.Write([]byte(resp))
|
||||
}
|
||||
|
||||
for k, v := range retHeader {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
|
||||
w.WriteHeader(retStatus)
|
||||
if file != "" {
|
||||
out1 := mustReadFile(file)
|
||||
if !strings.HasSuffix(file, ".tmpl") {
|
||||
_, _ = w.Write(out1)
|
||||
return
|
||||
}
|
||||
|
||||
out2, err := obj.template(string(out1), data)
|
||||
if err != nil {
|
||||
resp := fmt.Sprintf("unexpected request - url: %s, method: %s", r.URL, r.Method)
|
||||
_, _ = w.Write([]byte(resp))
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte(out2))
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// rpcHandler is used for the rpc driver.
|
||||
func (obj *MockBMC) rpcHandler(w http.ResponseWriter, r *http.Request) {
|
||||
//fmt.Printf("req1: %+v\n", r)
|
||||
//fmt.Printf("method: %s\n", r.Method)
|
||||
//fmt.Printf("URL: %s\n", r.URL)
|
||||
//fmt.Printf("Body: %v\n", r.Body)
|
||||
|
||||
req := rpc.RequestPayload{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
//fmt.Printf("data: %+v\n", req)
|
||||
|
||||
rp := rpc.ResponsePayload{
|
||||
ID: req.ID,
|
||||
Host: req.Host,
|
||||
}
|
||||
switch req.Method {
|
||||
case rpc.PowerGetMethod:
|
||||
rp.Result = obj.State
|
||||
fmt.Printf("get state: %s\n", obj.State)
|
||||
|
||||
case rpc.PowerSetMethod:
|
||||
//fmt.Printf("req2: %T %+v\n", req.Params, req.Params)
|
||||
// TODO: This is a mess, isn't there a cleaner way to unpack it?
|
||||
m, ok := req.Params.(map[string]interface{})
|
||||
if ok {
|
||||
param, exists := m["state"]
|
||||
state, ok := param.(string)
|
||||
if ok {
|
||||
if exists && (state == StateOn || state == StateOff) {
|
||||
obj.State = state
|
||||
fmt.Printf("set state: %s\n", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case rpc.BootDeviceMethod:
|
||||
|
||||
case rpc.PingMethod:
|
||||
fmt.Printf("got ping\n")
|
||||
rp.Result = "pong"
|
||||
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
b, _ := json.Marshal(rp)
|
||||
//fmt.Printf("out: %s\n", b)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func (obj *MockBMC) hello(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Printf("req: %+v\n", req)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte("This is hello world!\n"))
|
||||
//w.Write([]byte("OpenBMC says hello!\n"))
|
||||
}
|
||||
|
||||
func mustReadFile(filename string) []byte {
|
||||
fixture := "fixtures" + "/" + filename
|
||||
fh, err := os.Open(fixture)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer fh.Close()
|
||||
|
||||
b, err := io.ReadAll(fh)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// The original examples had no trailing newlines. Not sure if allowed.
|
||||
return bytes.TrimSuffix(b, []byte("\n"))
|
||||
}
|
||||
|
||||
// Args are what are used to build the CLI.
|
||||
type Args struct {
|
||||
// XXX: We cannot have both subcommands and a positional argument.
|
||||
// XXX: I think it's a bug of this library that it can't handle argv[0].
|
||||
//Argv0 string `arg:"positional"`
|
||||
|
||||
On bool `arg:"--on" help:"start on"`
|
||||
|
||||
Port int `arg:"--port" help:"port to listen on"`
|
||||
|
||||
Driver string `arg:"--driver" default:"redfish" help:"driver to use"`
|
||||
}
|
||||
|
||||
// Main program that returns error.
|
||||
func Main() error {
|
||||
args := Args{
|
||||
Port: DefaultPort,
|
||||
}
|
||||
config := arg.Config{}
|
||||
parser, err := arg.NewParser(config, &args)
|
||||
if err != nil {
|
||||
// programming error
|
||||
return err
|
||||
}
|
||||
err = parser.Parse(os.Args[1:]) // XXX: args[0] needs to be dropped
|
||||
if err == arg.ErrHelp {
|
||||
parser.WriteHelp(os.Stdout)
|
||||
return nil
|
||||
}
|
||||
|
||||
state := StateOff
|
||||
if args.On {
|
||||
state = StateOn
|
||||
}
|
||||
|
||||
mock := &MockBMC{
|
||||
Addr: fmt.Sprintf("localhost:%d", args.Port),
|
||||
//State: StateOff, // starts off off
|
||||
//State: StateOn,
|
||||
State: state,
|
||||
Driver: args.Driver,
|
||||
}
|
||||
return mock.Run()
|
||||
}
|
||||
|
||||
// wget --no-check-certificate --post-data 'user=foo&password=bar' \
|
||||
// https://localhost:8800/redfish/v1/Systems/1/Actions/ComputerSystem.Reset -O -
|
||||
func main() {
|
||||
fmt.Printf("main: %+v\n", Main())
|
||||
}
|
||||
20
go.mod
20
go.mod
@@ -38,8 +38,8 @@ require (
|
||||
go.etcd.io/etcd/client/v3 v3.5.13
|
||||
go.etcd.io/etcd/server/v3 v3.5.13
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/time v0.5.0
|
||||
golang.org/x/tools v0.20.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@@ -48,13 +48,18 @@ require (
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Jeffail/gabs/v2 v2.7.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230 // indirect
|
||||
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 // indirect
|
||||
github.com/alexflint/go-scalar v1.2.0 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/ashcrow/osrelease v0.0.0-20180626175927-9b292693c55c // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||
github.com/bmc-toolbox/bmclib/v2 v2.3.3 // indirect
|
||||
github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb // indirect
|
||||
@@ -67,6 +72,7 @@ require (
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
@@ -90,6 +96,8 @@ require (
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/serf v0.10.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef // indirect
|
||||
github.com/jacobweinstock/registrar v0.4.7 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||
@@ -121,12 +129,14 @@ require (
|
||||
github.com/prometheus/common v0.53.0 // indirect
|
||||
github.com/prometheus/procfs v0.14.0 // indirect
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/spf13/cobra v1.8.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect
|
||||
github.com/stmcginnis/gofish v0.19.0 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
|
||||
github.com/tredoe/osutil v1.5.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
@@ -150,10 +160,10 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/term v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240415180920-8c6c420018be // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
|
||||
|
||||
29
go.sum
29
go.sum
@@ -6,11 +6,17 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=
|
||||
github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
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=
|
||||
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22/go.mod h1:/B7V22rcz4860iDqstGvia/2+IYWXf3/JdQCVd/1D2A=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -43,6 +49,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bmc-toolbox/bmclib/v2 v2.3.3 h1:ORCB91SrCShUnJ4vwQ00OPSmNg+08NLgaBH/ezfMKjQ=
|
||||
github.com/bmc-toolbox/bmclib/v2 v2.3.3/go.mod h1:t8If/0fHQTRIK/yKDk2H3SgthDNNj+7z2aeftDFRFrU=
|
||||
github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 h1:/BjZSX/sphptIdxpYo4wxAQkgMLyMMgfdl48J9DKNeE=
|
||||
github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
@@ -108,6 +118,7 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
@@ -236,6 +247,10 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef h1:G4k02HGmBUfJFSNu3gfKJ+ki+B3qutKsYzYndkqqKc4=
|
||||
github.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef/go.mod h1:FgmiLTU6cJewV4Xgrq6m5o8CUlTQOJtqzaFLGA0mG+E=
|
||||
github.com/jacobweinstock/registrar v0.4.7 h1:s4dOExccgD+Pc7rJC+f3Mc3D+NXHcXUaOibtcEsPxOc=
|
||||
github.com/jacobweinstock/registrar v0.4.7/go.mod h1:PWmkdGFG5/ZdCqgMo7pvB3pXABOLHc5l8oQ0sgmBNDU=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
@@ -397,6 +412,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
@@ -421,6 +438,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA=
|
||||
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stmcginnis/gofish v0.19.0 h1:fmxdRZ5WHfs+4ExArMYoeRfoh+SAxLELKtmoVplBkU4=
|
||||
github.com/stmcginnis/gofish v0.19.0/go.mod h1:lq2jHj2t8Krg0Gx02ABk8MbK7Dz9jvWpO/TGnVksn00=
|
||||
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/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
@@ -517,6 +536,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
@@ -560,6 +581,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -626,6 +649,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
@@ -633,6 +658,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -643,6 +670,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -560,6 +560,9 @@ class base:host($name, $config) {
|
||||
$handoff_code = $config->handoff_code || ""
|
||||
panic(not strings.has_prefix($handoff_code, "/"))
|
||||
|
||||
# This is a giant driver://user:password@host:port/whatever URL...
|
||||
$bmc_uri = $config->bmc_uri || ""
|
||||
|
||||
# unique host key which is usually a mac address unless it's a default
|
||||
$hkey = if $mac == "" {
|
||||
"default"
|
||||
@@ -819,6 +822,15 @@ class base:host($name, $config) {
|
||||
Before => Print["ready"],
|
||||
}
|
||||
|
||||
bmc:power "${bmc_uri}" { # TODO: Name() API is not yet stable
|
||||
#password => "hunter2",
|
||||
insecure_password => true, # XXX: get from uri for now
|
||||
|
||||
state => "on",
|
||||
|
||||
Meta:poll => 60, # required until BMC's support real events!
|
||||
}
|
||||
|
||||
##$str_true = convert.format_bool(true)
|
||||
##$str_false = convert.format_bool(false)
|
||||
#http:flag "${name}" {
|
||||
|
||||
@@ -171,6 +171,10 @@ type localArgs struct {
|
||||
// static code deploy bolus. This is useful for isolated, one-time runs.
|
||||
HandoffCode string `arg:"--handoff-code" help:"code dir to handoff to host" func:"cli_handoff_code"` // eg: /etc/mgmt/
|
||||
|
||||
// BmcURI specifies the BMC connect string we want to use for this host.
|
||||
// This is a giant driver://user:password@host:port/whatever URL...
|
||||
BmcURI string `arg:"--bmc-uri" help:"bmc connect string to use for this host" func:"cli_bmc_uri"`
|
||||
|
||||
// OnlyUnify tells the compiler to stop after type unification. This is
|
||||
// used for testing.
|
||||
OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"`
|
||||
|
||||
@@ -79,6 +79,7 @@ include base.host("host0", struct{ # TODO: do we need a usable name anywhere?
|
||||
handoff => $handoff, # alternatively some code word or querystring
|
||||
#handoff_code => "/etc/mgmt/", # one way to do it
|
||||
handoff_code => provisioner.cli_handoff_code(),
|
||||
bmc_uri => provisioner.cli_bmc_uri(),
|
||||
}) as host0
|
||||
|
||||
#if $host0.provisioned {
|
||||
|
||||
Reference in New Issue
Block a user