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 }