127 lines
2.7 KiB
Go
127 lines
2.7 KiB
Go
// Copyright (C) 2023-2024 Umorpha Systems
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/datawire/ocibuild/pkg/cliutil"
|
|
"github.com/spf13/cobra"
|
|
|
|
source "git.mothstuff.lol/lukeshu/eclipse"
|
|
"git.mothstuff.lol/lukeshu/eclipse/lib/common"
|
|
"git.mothstuff.lol/lukeshu/eclipse/lib/follower"
|
|
)
|
|
|
|
func main() {
|
|
argparser := &cobra.Command{
|
|
Use: os.Args[0] + " [flags] JOBS_URL",
|
|
Short: "Choose a job from the leader server",
|
|
Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
|
|
|
|
SilenceErrors: true, // we'll handle this ourselves after .ExecuteContext()
|
|
SilenceUsage: true, // FlagErrorFunc will handle this
|
|
|
|
CompletionOptions: cobra.CompletionOptions{
|
|
DisableDefaultCmd: true,
|
|
},
|
|
}
|
|
argparser.SetFlagErrorFunc(cliutil.FlagErrorFunc)
|
|
argparser.SetHelpTemplate(cliutil.HelpTemplate)
|
|
|
|
var cfgFile string
|
|
argparser.Flags().StringVar(&cfgFile, "config", source.DefaultFollowerConfigFile,
|
|
"Config file to use")
|
|
_ = argparser.MarkFlagFilename("config", "yml", "yaml")
|
|
|
|
argparser.RunE = func(*cobra.Command, []string) error {
|
|
return Pick(cfgFile)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
if err := argparser.ExecuteContext(ctx); err != nil {
|
|
fmt.Fprintf(os.Stderr, "%v: error: %v\n", argparser.CommandPath(), err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func randInt(max int) (int, error) {
|
|
bigN, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int(bigN.Int64()), nil
|
|
}
|
|
|
|
func Pick(cfgFile string) error {
|
|
cfg, err := follower.LoadConfig(cfgFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
u, err := url.Parse(cfg.LeaderURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.Path = "/jobs/"
|
|
u.RawQuery = url.Values{
|
|
"status": []string{"new"},
|
|
"wait": []string{"true"},
|
|
}.Encode()
|
|
|
|
httpClient := &http.Client{
|
|
Timeout: 45 * time.Second,
|
|
}
|
|
|
|
// TODO: If we go 1 minute without an "HTTP 100 Continue"
|
|
// keepalive, then fail with a timeout.
|
|
resp, err := httpClient.Get(u.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
err := fmt.Errorf("HTTP %s", resp.Status)
|
|
body, _ := io.ReadAll(resp.Body)
|
|
if len(body) > 0 {
|
|
err = fmt.Errorf("%w\n%s", err, body)
|
|
}
|
|
return err
|
|
}
|
|
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var options []common.Job
|
|
if err := json.Unmarshal(bodyBytes, &options); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(options) == 0 {
|
|
return errors.New("no jobs to choose from; this indicates a server bug, it should never return an empty non-error response")
|
|
}
|
|
|
|
n, err := randInt(len(options))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
u.Path = fmt.Sprintf("/jobs/%d/", options[n].ID)
|
|
u.RawQuery = ""
|
|
|
|
fmt.Println(u)
|
|
|
|
return nil
|
|
}
|