http-boot-config/config/remote/remote.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"`
DiskID string `json:"disk_id"`
} `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 (disk id %s)", option.Name, option.Path, option.DiskID)
}
l.Infof(" * Remote boot server is listening on %s:%d", parsedConf.NetConfig.MulticastGroup, parsedConf.NetConfig.Port)
return nil
}