mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-20 22:21:13 +02:00
Fixes #2515 This implements simple HTTP API which should cover same methods as IPMI methods in Sidero. Examples: ``` $ curl http://172.20.0.1:34791/status {"PoweredOn":false} ``` ``` $ curl -X POST http://172.20.0.1:34791/poweroff ``` API listens on bridge address, each VM has unique port which can be found in cluster state as `apiport: NNNN`. Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
140 lines
3.2 KiB
Go
140 lines
3.2 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package vm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/talos-systems/talos/pkg/provision/internal/inmemhttp"
|
|
)
|
|
|
|
// ReadConfig loads configuration from stdin.
|
|
func ReadConfig(config interface{}) error {
|
|
d := json.NewDecoder(os.Stdin)
|
|
if err := d.Decode(config); err != nil {
|
|
return fmt.Errorf("error decoding config from stdin: %w", err)
|
|
}
|
|
|
|
if d.More() {
|
|
return fmt.Errorf("extra unexpected input on stdin")
|
|
}
|
|
|
|
if err := os.Stdin.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConfigureSignals configures signal handling for the process.
|
|
func ConfigureSignals() chan os.Signal {
|
|
signal.Ignore(syscall.SIGHUP)
|
|
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
|
|
|
|
return c
|
|
}
|
|
|
|
func httpPostWrapper(f func() error) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if req.Body != nil {
|
|
_, _ = io.Copy(ioutil.Discard, req.Body) //nolint: errcheck
|
|
req.Body.Close() //nolint: errcheck
|
|
}
|
|
|
|
if req.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusNotImplemented)
|
|
|
|
return
|
|
}
|
|
|
|
err := f()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
fmt.Fprint(w, err.Error())
|
|
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
}
|
|
|
|
func httpGetWrapper(f func(w io.Writer)) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if req.Body != nil {
|
|
_, _ = io.Copy(ioutil.Discard, req.Body) //nolint: errcheck
|
|
req.Body.Close() //nolint: errcheck
|
|
}
|
|
|
|
switch req.Method {
|
|
case http.MethodHead:
|
|
w.Header().Add("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
case http.MethodGet:
|
|
w.Header().Add("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
f(w)
|
|
default:
|
|
w.WriteHeader(http.StatusNotImplemented)
|
|
}
|
|
})
|
|
}
|
|
|
|
// NewHTTPServer creates new inmemhttp.Server and mounts config file into it.
|
|
func NewHTTPServer(gatewayAddr net.IP, port int, config []byte, controller Controller) (inmemhttp.Server, error) {
|
|
httpServer, err := inmemhttp.NewServer(fmt.Sprintf("%s:%d", gatewayAddr, port))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error launching in-memory HTTP server: %w", err)
|
|
}
|
|
|
|
if err = httpServer.AddFile("config.yaml", config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if controller != nil {
|
|
for _, method := range []struct {
|
|
pattern string
|
|
f func() error
|
|
}{
|
|
{
|
|
pattern: "/poweron",
|
|
f: controller.PowerOn,
|
|
},
|
|
{
|
|
pattern: "/poweroff",
|
|
f: controller.PowerOff,
|
|
},
|
|
{
|
|
pattern: "/reboot",
|
|
f: controller.Reboot,
|
|
},
|
|
{
|
|
pattern: "/pxeboot",
|
|
f: controller.PXEBootOnce,
|
|
},
|
|
} {
|
|
httpServer.AddHandler(method.pattern, httpPostWrapper(method.f))
|
|
}
|
|
|
|
httpServer.AddHandler("/status", httpGetWrapper(func(w io.Writer) {
|
|
json.NewEncoder(w).Encode(controller.Status()) //nolint: errcheck
|
|
}))
|
|
}
|
|
|
|
return httpServer, nil
|
|
}
|