112 lines
3 KiB
Go
112 lines
3 KiB
Go
|
package remote
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"time"
|
||
|
|
||
|
"git.faercol.me/faercol/http-boot-config/config/logger"
|
||
|
"github.com/google/uuid"
|
||
|
)
|
||
|
|
||
|
var ErrNotEnrolled = errors.New("client not enrolled")
|
||
|
|
||
|
const getBootRoute = "/config"
|
||
|
const reqTimeout = 5 * time.Second
|
||
|
|
||
|
type clientConfig struct {
|
||
|
EFIConfig struct {
|
||
|
ID string `json:"ID"`
|
||
|
Name string `json:"name"`
|
||
|
Options map[string]struct {
|
||
|
Name string `json:"name"`
|
||
|
Path string `json:"path"`
|
||
|
} `json:"options"`
|
||
|
SelectedOption string `json:"selected_option"`
|
||
|
} `json:"efi_config"`
|
||
|
NetConfig struct {
|
||
|
MulticastGroup string `json:"multicast_group"`
|
||
|
Port int `json:"port"`
|
||
|
} `json:"net_config"`
|
||
|
}
|
||
|
|
||
|
func getRemoteConfig(ctx context.Context, host string, id uuid.UUID, l *logger.SimpleLogger) ([]byte, error) {
|
||
|
subCtx, cancel := context.WithTimeout(ctx, reqTimeout)
|
||
|
defer cancel()
|
||
|
req, err := http.NewRequestWithContext(subCtx, http.MethodGet, host+getBootRoute, nil)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
|
}
|
||
|
q := req.URL.Query()
|
||
|
q.Add("id", id.String())
|
||
|
req.URL.RawQuery = q.Encode()
|
||
|
|
||
|
resp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed to query server: %w", err)
|
||
|
}
|
||
|
|
||
|
switch resp.StatusCode {
|
||
|
case http.StatusBadRequest:
|
||
|
return nil, fmt.Errorf("invalid UUID")
|
||
|
case http.StatusNotFound:
|
||
|
return nil, ErrNotEnrolled
|
||
|
case http.StatusOK:
|
||
|
dat, err := io.ReadAll(resp.Body)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
||
|
}
|
||
|
return dat, nil
|
||
|
default:
|
||
|
return nil, fmt.Errorf("unexpected server error (%d)", resp.StatusCode)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func DisplayRemoteConfigJSON(ctx context.Context, host string, id uuid.UUID, l *logger.SimpleLogger) error {
|
||
|
dat, err := getRemoteConfig(ctx, host, id, l)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// We want to prettify the output for the user
|
||
|
buf := bytes.NewBuffer(nil)
|
||
|
if err := json.Indent(buf, dat, "", " "); err != nil {
|
||
|
return fmt.Errorf("failed to format result: %w", err)
|
||
|
}
|
||
|
|
||
|
l.Info(buf.String())
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func DisplayRemoteConfigPretty(ctx context.Context, host string, id uuid.UUID, l *logger.SimpleLogger) error {
|
||
|
dat, err := getRemoteConfig(ctx, host, id, l)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
var parsedConf clientConfig
|
||
|
if err := json.Unmarshal(dat, &parsedConf); err != nil {
|
||
|
return fmt.Errorf("invalid JSON format for result: %w", err)
|
||
|
}
|
||
|
|
||
|
l.Infof("Got the following config for client %s:", parsedConf.EFIConfig.Name)
|
||
|
selectedOption, ok := parsedConf.EFIConfig.Options[parsedConf.EFIConfig.SelectedOption]
|
||
|
if !ok {
|
||
|
l.Info(" * No selected boot option for client")
|
||
|
} else {
|
||
|
l.Infof(" * Client is set to boot to %s", selectedOption.Name)
|
||
|
}
|
||
|
l.Info(" * Available options are:")
|
||
|
for _, option := range parsedConf.EFIConfig.Options {
|
||
|
l.Infof("\t - %s: %s", option.Name, option.Path)
|
||
|
}
|
||
|
l.Infof(" * Remote boot server is listening on %s:%d", parsedConf.NetConfig.MulticastGroup, parsedConf.NetConfig.Port)
|
||
|
|
||
|
return nil
|
||
|
}
|