http-boot-config/config/remote/enroll.go

103 lines
3 KiB
Go

package remote
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"git.faercol.me/faercol/http-boot-config/config/logger"
"git.faercol.me/faercol/http-boot-config/config/prober"
"github.com/google/uuid"
)
const enrollURL = "/enroll"
type enrollEFIOption struct {
Name string `json:"name"`
Path string `json:"path"`
DiskID string `json:"disk_id"`
}
type enrollPayload struct {
Name string `json:"name"`
Options map[string]enrollEFIOption `json:"options"`
SelectedOption string `json:"selected_option"`
}
type enrollRespPayloadJSON struct {
ID string `json:"ID"`
MulticastGroup string `json:"multicast_group"`
MulticastPort int `json:"multicast_port"`
}
type EnrollConfig struct {
ID uuid.UUID
MulticastGroup net.IP
MulticastPort int
}
func enrollToServer(ctx context.Context, host string, name string, apps []prober.EfiApp) (EnrollConfig, error) {
payload := enrollPayload{
Name: name,
Options: make(map[string]enrollEFIOption),
}
for _, a := range apps {
appID := uuid.New()
payload.Options[appID.String()] = enrollEFIOption{Name: a.Name, Path: a.Path, DiskID: a.DiskID}
if a.Active {
payload.SelectedOption = appID.String()
}
}
dat, err := json.Marshal(payload)
if err != nil {
return EnrollConfig{}, fmt.Errorf("failed to serialize payload: %w", err)
}
subCtx, cancel := context.WithTimeout(ctx, reqTimeout)
defer cancel()
req, err := http.NewRequestWithContext(subCtx, http.MethodPost, host+enrollURL, bytes.NewBuffer(dat))
if err != nil {
return EnrollConfig{}, fmt.Errorf("failed to initialize request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return EnrollConfig{}, fmt.Errorf("failed to do query to remote boot server: %w", err)
}
if resp.StatusCode != http.StatusOK {
return EnrollConfig{}, fmt.Errorf("unexpected returncode %d", resp.StatusCode)
}
var respPayload enrollRespPayloadJSON
respDat, err := io.ReadAll(resp.Body)
if err != nil {
return EnrollConfig{}, fmt.Errorf("failed to read response from server: %w", err)
}
if err := json.Unmarshal(respDat, &respPayload); err != nil {
return EnrollConfig{}, fmt.Errorf("failed to deserialize data from server: %w", err)
}
newID, err := uuid.Parse(respPayload.ID)
if err != nil {
return EnrollConfig{}, fmt.Errorf("invalid UUID %q from server: %w", respPayload.ID, err)
}
return EnrollConfig{ID: newID, MulticastGroup: net.ParseIP(respPayload.MulticastGroup), MulticastPort: respPayload.MulticastPort}, nil
}
func Enroll(ctx context.Context, host, name string, l *logger.SimpleLogger) (EnrollConfig, error) {
apps, err := prober.GetEFIApps()
if err != nil {
return EnrollConfig{}, fmt.Errorf("failed to get list of available EFI applications: %w", err)
}
newConf, err := enrollToServer(ctx, host, name, apps)
if err != nil {
return EnrollConfig{}, fmt.Errorf("failed to enroll client to remote host: %w", err)
}
l.Infof("Successfully enrolled new client, ID is %q", newConf.ID.String())
return newConf, nil
}