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:
James Shubin
2025-01-03 18:19:31 -05:00
parent 3107dfbd08
commit e6d614f4dd
13 changed files with 1017 additions and 5 deletions

View 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
}

View 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.

View 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": {}
}

View File

@@ -0,0 +1,6 @@
{
"Success": {
"code": "Base.v1_4_0.Success",
"Message": "Successfully Completed Request."
}
}

View 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": {}
}

View 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"
}
]
}

View 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
View 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
View File

@@ -38,8 +38,8 @@ require (
go.etcd.io/etcd/client/v3 v3.5.13 go.etcd.io/etcd/client/v3 v3.5.13
go.etcd.io/etcd/server/v3 v3.5.13 go.etcd.io/etcd/server/v3 v3.5.13
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.22.0 golang.org/x/crypto v0.23.0
golang.org/x/sys v0.19.0 golang.org/x/sys v0.20.0
golang.org/x/time v0.5.0 golang.org/x/time v0.5.0
golang.org/x/tools v0.20.0 golang.org/x/tools v0.20.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@@ -48,13 +48,18 @@ require (
require ( require (
dario.cat/mergo v1.0.0 // indirect 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/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // 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/alexflint/go-scalar v1.2.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect
github.com/ashcrow/osrelease v0.0.0-20180626175927-9b292693c55c // indirect github.com/ashcrow/osrelease v0.0.0-20180626175927-9b292693c55c // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // 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/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb // 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/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.16.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // 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/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-logr/logr v1.4.1 // 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/golang-lru v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/serf v0.10.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork 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/common v0.53.0 // indirect
github.com/prometheus/procfs v0.14.0 // indirect github.com/prometheus/procfs v0.14.0 // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // 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/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect github.com/skeema/knownhosts v1.2.2 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // 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/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
github.com/tredoe/osutil v1.5.0 // indirect github.com/tredoe/osutil v1.5.0 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // 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/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // 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/sync v0.7.0 // indirect
golang.org/x/term v0.19.0 // indirect golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.15.0 // indirect
google.golang.org/genproto v0.0.0-20240415180920-8c6c420018be // 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/api v0.0.0-20240415180920-8c6c420018be // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect

29
go.sum
View File

@@ -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/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/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/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.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= 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-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/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= 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/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 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 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/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 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 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/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/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 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= 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/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 h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= 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 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 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= 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/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 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= 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 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 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= 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.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 h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA=
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 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.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 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 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-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 h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= 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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 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.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-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 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.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= 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.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.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.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 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -560,6 +560,9 @@ class base:host($name, $config) {
$handoff_code = $config->handoff_code || "" $handoff_code = $config->handoff_code || ""
panic(not strings.has_prefix($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 # unique host key which is usually a mac address unless it's a default
$hkey = if $mac == "" { $hkey = if $mac == "" {
"default" "default"
@@ -819,6 +822,15 @@ class base:host($name, $config) {
Before => Print["ready"], 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_true = convert.format_bool(true)
##$str_false = convert.format_bool(false) ##$str_false = convert.format_bool(false)
#http:flag "${name}" { #http:flag "${name}" {

View File

@@ -171,6 +171,10 @@ type localArgs struct {
// static code deploy bolus. This is useful for isolated, one-time runs. // 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/ 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 // OnlyUnify tells the compiler to stop after type unification. This is
// used for testing. // used for testing.
OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"` OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"`

View File

@@ -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 => $handoff, # alternatively some code word or querystring
#handoff_code => "/etc/mgmt/", # one way to do it #handoff_code => "/etc/mgmt/", # one way to do it
handoff_code => provisioner.cli_handoff_code(), handoff_code => provisioner.cli_handoff_code(),
bmc_uri => provisioner.cli_bmc_uri(),
}) as host0 }) as host0
#if $host0.provisioned { #if $host0.provisioned {