From 78b0b9511ecdb07fd21b35ce2a3be8370f0bd9a0 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Tue, 29 Oct 2024 17:02:03 -0400 Subject: [PATCH] XXX BMC WIP WORK This is an initial rough POC of how BMC power integration would work. --- engine/resources/bmc_power.go | 309 ++++++++++++++++++ examples/bmc_server/bmc_server.go | 145 ++++++++ examples/lang/bmc-power.mcl | 10 + go.mod | 20 +- go.sum | 29 ++ lang/core/embedded/provisioner/main.mcl | 14 + lang/core/embedded/provisioner/provisioner.go | 8 + lang/core/embedded/provisioner/top.mcl | 2 + 8 files changed, 532 insertions(+), 5 deletions(-) create mode 100644 engine/resources/bmc_power.go create mode 100644 examples/bmc_server/bmc_server.go create mode 100644 examples/lang/bmc-power.mcl diff --git a/engine/resources/bmc_power.go b/engine/resources/bmc_power.go new file mode 100644 index 000000000..571815385 --- /dev/null +++ b/engine/resources/bmc_power.go @@ -0,0 +1,309 @@ +// Mgmt +// Copyright (C) 2013-2024+ James Shubin and the project contributors +// Written by James Shubin 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 . +// +// 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" + "strconv" + + "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 +) + +// BmcPowerRes is a no-op resource that does nothing. +type BmcPowerRes struct { + traits.Base // add the base methods without re-implementation + + init *engine.Init + + // Hostname to connect to. + Hostname string `lang:"hostname" yaml:"hostname"` + + // Port to connect to. + Port int `lang:"port" yaml:"port"` + + // Username to use to connect. + Username string `lang:"username" yaml:"username"` + + // Password to use to connect. + // XXX: Use mgmt magic credentials in the future. + Password string `lang:"password" yaml:"password"` + + // Driver to use, such as: "gofish". If you set this, then + // PreferredDriver is ignored. + Driver string `lang:"driver" yaml:"driver"` + + // PreferredDriver to use, such as: "gofish". + // TODO: Should we Validate that Driver is not also specified? + PreferredDriver string `lang:"preferred_driver" yaml:"preferred_driver"` + + // State of machine power. Can be "on" or "off". + State string `lang:"state" yaml:"state"` +} + +// 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 + } + + // 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(obj.Name()) + if err != nil { + return obj.Name() // 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. +func (obj *BmcPowerRes) getPort() string { + if obj.Port != 0 { + return strconv.Itoa(obj.Port) + } + + // 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(obj.Name()) + if err != nil { + return strconv.Itoa(DefaultBmcPowerPort) // default port + } + _ = host + + return port +} + +// getUsername returns the username that we want to connect with. +// TODO: If the Username field is not set, should we parse from the Name? +func (obj *BmcPowerRes) getUsername() string { + return obj.Username +} + +// 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") + //} + + return nil +} + +// Init runs some startup code for this resource. +func (obj *BmcPowerRes) Init(init *engine.Init) error { + obj.init = init // save for later + + return nil +} + +// Cleanup is run by the engine to clean up after the resource is done. +func (obj *BmcPowerRes) Cleanup() error { + return nil +} + +// 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 +} + +//func (obj *BmcPowerRes) connect(ctx context.Context error { +// TODO: refactor all that mess into here +//} + +// CheckApply method for BmcPower resource. Does nothing, returns happy! +func (obj *BmcPowerRes) CheckApply(ctx context.Context, apply bool) (bool, error) { + + u := fmt.Sprintf("http://%s", net.JoinHostPort(obj.getHostname(), obj.getPort())) + + opts := []bmclib.Option{ + //openbmc.WithPort("42"), // XXX: not an int? + //bmclib.WithLogger(log), + //bmclib.WithPerProviderTimeout(5 * time.Second), + bmclib.WithRPCOpt(rpc.Provider{ + //ConsumerURL: "http://127.0.0.1:8800", + ConsumerURL: u, + // Opts are not required. + //Opts: rpc.Opts{ + // HMAC: rpc.HMACOpts{ + // Secrets: rpc.Secrets{rpc.SHA256: {"superSecret1"}}, + // }, + // Signature: rpc.SignatureOpts{ + // HeaderName: "X-Bespoke-Signature", + // IncludedPayloadHeaders: []string{"X-Bespoke-Timestamp"}, + // }, + // Request: rpc.RequestOpts{ + // TimestampHeader: "X-Bespoke-Timestamp", + // }, + //}, + }), + } + + // XXX: this u doesn't seem to get used anywhere... WAT? + client := bmclib.NewClient(u, obj.getUsername(), obj.Password, opts...) + + // client.PreferProvider("gofish") + + //if obj.Driver != "" { + // // Only use this one driver. + // client.Registry.Drivers = client.Registry.Using(obj.Driver) + + //} else if obj.PreferredDriver != "" { + // // This will modify the order for all subsequent BMC calls. + // client.Registry.Drivers = client.Registry.PreferDriver(obj.PreferredDriver) + //} + + if err := client.Open(ctx); err != nil { + return false, err + } + defer client.Close(ctx) // (err error) + + if obj.init.Debug || true { // XXX !!! + obj.init.Logf("connected ok") + } + + state, err := client.GetPowerState(ctx) + if err != nil { + return false, err + } + obj.init.Logf("get state: %s", state) + + if !apply { + return false, nil + } + + if obj.State == state { + return true, nil + } + + 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.Driver != res.Driver { + return fmt.Errorf("the Driver differs") + } + if obj.PreferredDriver != res.PreferredDriver { + return fmt.Errorf("the PreferredDriver 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 +} diff --git a/examples/bmc_server/bmc_server.go b/examples/bmc_server/bmc_server.go new file mode 100644 index 000000000..0c1f0a627 --- /dev/null +++ b/examples/bmc_server/bmc_server.go @@ -0,0 +1,145 @@ +// This is an example mock BMC server/device. +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + + "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 +} + +// Run kicks this all off. +func (obj *MockBMC) Run() error { + fmt.Printf("running at: %s\n", obj.Addr) + fmt.Printf("device is: %s\n", obj.State) // we start off in this state + http.HandleFunc("/", obj.handler) + http.HandleFunc("/hello", obj.hello) + return http.ListenAndServe(obj.Addr, nil) +} + +func (obj *MockBMC) handler(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")) +} + +// 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"` +} + +// 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(":%d", args.Port), + //State: StateOff, // starts off off + //State: StateOn, + State: state, + } + return mock.Run() +} + +func main() { + fmt.Printf("main: %+v\n", Main()) +} diff --git a/examples/lang/bmc-power.mcl b/examples/lang/bmc-power.mcl new file mode 100644 index 000000000..17c70fde4 --- /dev/null +++ b/examples/lang/bmc-power.mcl @@ -0,0 +1,10 @@ +bmc:power "127.0.0.1:8800" { + #username => "admin", + #password => "hunter2", + #driver => "?", + #preferred_driver => "IntelAMT", + + state => "on", + + Meta:poll => 10, # required until BMC's support real events! +} diff --git a/go.mod b/go.mod index 2a14d5903..d1392af8f 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 22acfd998..9dd3adea5 100644 --- a/go.sum +++ b/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= diff --git a/lang/core/embedded/provisioner/main.mcl b/lang/core/embedded/provisioner/main.mcl index 34dfd92c3..cdd8d8f13 100644 --- a/lang/core/embedded/provisioner/main.mcl +++ b/lang/core/embedded/provisioner/main.mcl @@ -560,6 +560,10 @@ class base:host($name, $config) { $handoff_code = $config->handoff_code || "" panic(not strings.has_prefix($handoff_code, "/")) + # TODO: Should this be a giant driver://user:password@host:port/whatever URL instead? + $bmc_driver = $config->bmc_driver || "" + $bmc_connect = $config->bmc_connect || "" + # unique host key which is usually a mac address unless it's a default $hkey = if $mac == "" { "default" @@ -819,6 +823,16 @@ class base:host($name, $config) { Before => Print["ready"], } + bmc:power "${bmc_connect}" { # TODO: Name() API is not yet stable + #username => "admin", # XXX: ? + #password => "hunter2", # XXX: ? + driver => $bmc_driver, + + 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}" { diff --git a/lang/core/embedded/provisioner/provisioner.go b/lang/core/embedded/provisioner/provisioner.go index 56d2e30f1..ff7519d8e 100644 --- a/lang/core/embedded/provisioner/provisioner.go +++ b/lang/core/embedded/provisioner/provisioner.go @@ -171,6 +171,14 @@ 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/ + // BmcConnect specifies the BMC connect string we want to use for this + // host. + // TODO: Should this be a giant driver://user:password@host:port/whatever URL instead? + BmcConnect string `arg:"--bmc-connect" help:"bmc connect string to use for this host" func:"cli_bmc_connect"` + + // BmcDriver specifies the BMC driver we want to use for this host. + BmcDriver string `arg:"--bmc-driver" help:"bmc driver string to use for this host" func:"cli_bmc_driver"` + // OnlyUnify tells the compiler to stop after type unification. This is // used for testing. OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"` diff --git a/lang/core/embedded/provisioner/top.mcl b/lang/core/embedded/provisioner/top.mcl index f985eab16..1ac0b84dd 100644 --- a/lang/core/embedded/provisioner/top.mcl +++ b/lang/core/embedded/provisioner/top.mcl @@ -79,6 +79,8 @@ 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_connect => provisioner.cli_bmc_connect(), + bmc_driver => provisioner.cli_bmc_driver(), }) as host0 #if $host0.provisioned {