Through an RPC command, because systemd documentation told us to.
This commit is contained in:
parent
2bd231b84f
commit
4a7fc55c92
@ -37,6 +37,8 @@ Commands
|
||||
*restart* [_ID_]...::
|
||||
Schedule tasks with the given IDs to be rerun.
|
||||
Run this command without arguments to pick up external database changes.
|
||||
*reload*::
|
||||
Reload configuration.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
91
acid.go
91
acid.go
@ -26,6 +26,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
ttemplate "text/template"
|
||||
"time"
|
||||
@ -40,9 +41,9 @@ var (
|
||||
projectName = "acid"
|
||||
projectVersion = "?"
|
||||
|
||||
gConfig Config = Config{Listen: ":http"}
|
||||
gNotifyScript *ttemplate.Template
|
||||
gDB *sql.DB
|
||||
gConfigPath string
|
||||
gConfig atomic.Pointer[Config]
|
||||
gDB *sql.DB
|
||||
|
||||
gNotifierSignal = make(chan struct{}, 1)
|
||||
gExecutorSignal = make(chan struct{}, 1)
|
||||
@ -52,6 +53,8 @@ var (
|
||||
gRunning = make(map[int64]*RunningTask)
|
||||
)
|
||||
|
||||
func getConfig() *Config { return gConfig.Load() }
|
||||
|
||||
// --- Config ------------------------------------------------------------------
|
||||
|
||||
type Config struct {
|
||||
@ -65,6 +68,8 @@ type Config struct {
|
||||
|
||||
Runners map[string]ConfigRunner `yaml:"runners"` // script runners
|
||||
Projects map[string]ConfigProject `yaml:"projects"` // configured projects
|
||||
|
||||
notifyTemplate *ttemplate.Template
|
||||
}
|
||||
|
||||
type ConfigRunner struct {
|
||||
@ -86,8 +91,9 @@ type ConfigProject struct {
|
||||
func (cf *ConfigProject) AutomaticRunners() (runners []string) {
|
||||
// We pass through unknown runner names,
|
||||
// so that they can cause reference errors later.
|
||||
config := getConfig()
|
||||
for runner := range cf.Runners {
|
||||
if r, _ := gConfig.Runners[runner]; !r.Manual {
|
||||
if r, _ := config.Runners[runner]; !r.Manual {
|
||||
runners = append(runners, runner)
|
||||
}
|
||||
}
|
||||
@ -102,17 +108,28 @@ type ConfigProjectRunner struct {
|
||||
Timeout string `yaml:"timeout"` // timeout duration
|
||||
}
|
||||
|
||||
func parseConfig(path string) error {
|
||||
if f, err := os.Open(path); err != nil {
|
||||
// loadConfig reloads configuration.
|
||||
// Beware that changes do not get applied globally at the same moment.
|
||||
func loadConfig() error {
|
||||
new := &Config{}
|
||||
if f, err := os.Open(gConfigPath); err != nil {
|
||||
return err
|
||||
} else if err = yaml.NewDecoder(f).Decode(&gConfig); err != nil {
|
||||
} else if err = yaml.NewDecoder(f).Decode(new); err != nil {
|
||||
return err
|
||||
}
|
||||
if old := getConfig(); old != nil && old.DB != new.DB {
|
||||
return fmt.Errorf("the database file cannot be changed in runtime")
|
||||
}
|
||||
|
||||
var err error
|
||||
gNotifyScript, err =
|
||||
ttemplate.New("notify").Funcs(shellFuncs).Parse(gConfig.Notify)
|
||||
return err
|
||||
new.notifyTemplate, err =
|
||||
ttemplate.New("notify").Funcs(shellFuncs).Parse(new.Notify)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gConfig.Store(new)
|
||||
return nil
|
||||
}
|
||||
|
||||
var shellFuncs = ttemplate.FuncMap{
|
||||
@ -137,7 +154,7 @@ var shellFuncs = ttemplate.FuncMap{
|
||||
// --- Utilities ---------------------------------------------------------------
|
||||
|
||||
func giteaSign(b []byte) string {
|
||||
payloadHmac := hmac.New(sha256.New, []byte(gConfig.Secret))
|
||||
payloadHmac := hmac.New(sha256.New, []byte(getConfig().Secret))
|
||||
payloadHmac.Write(b)
|
||||
return hex.EncodeToString(payloadHmac.Sum(nil))
|
||||
}
|
||||
@ -145,9 +162,9 @@ func giteaSign(b []byte) string {
|
||||
func giteaNewRequest(ctx context.Context, method, path string, body io.Reader) (
|
||||
*http.Request, error) {
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx, method, gConfig.Gitea+path, body)
|
||||
ctx, method, getConfig().Gitea+path, body)
|
||||
if req != nil {
|
||||
req.Header.Set("Authorization", "token "+gConfig.Token)
|
||||
req.Header.Set("Authorization", "token "+getConfig().Token)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
}
|
||||
return req, err
|
||||
@ -397,7 +414,7 @@ func handlePush(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("received push: %s %s\n",
|
||||
event.Repository.FullName, event.HeadCommit.ID)
|
||||
|
||||
project, ok := gConfig.Projects[event.Repository.FullName]
|
||||
project, ok := getConfig().Projects[event.Repository.FullName]
|
||||
if !ok {
|
||||
// This is okay, don't set any commit statuses.
|
||||
fmt.Fprintf(w, "The project is not configured.")
|
||||
@ -506,7 +523,7 @@ func rpcEnqueue(ctx context.Context,
|
||||
return fmt.Errorf("%s: %w", ref, err)
|
||||
}
|
||||
|
||||
project, ok := gConfig.Projects[owner+"/"+repo]
|
||||
project, ok := getConfig().Projects[owner+"/"+repo]
|
||||
if !ok {
|
||||
return fmt.Errorf("project configuration not found")
|
||||
}
|
||||
@ -558,6 +575,17 @@ func rpcRestart(ctx context.Context,
|
||||
return nil
|
||||
}
|
||||
|
||||
func rpcReload(ctx context.Context,
|
||||
w io.Writer, fs *flag.FlagSet, args []string) error {
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if fs.NArg() > 0 {
|
||||
return errWrongUsage
|
||||
}
|
||||
return loadConfig()
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
var rpcCommands = map[string]struct {
|
||||
@ -570,6 +598,8 @@ var rpcCommands = map[string]struct {
|
||||
"Create or restart tasks for the given reference."},
|
||||
"restart": {rpcRestart, "[ID]...",
|
||||
"Schedule tasks with the given IDs to be rerun."},
|
||||
"reload": {rpcReload, "",
|
||||
"Reload configuration."},
|
||||
}
|
||||
|
||||
func rpcPrintCommands(w io.Writer) {
|
||||
@ -656,7 +686,7 @@ func handleRPC(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func notifierRunCommand(ctx context.Context, task Task) {
|
||||
script := bytes.NewBuffer(nil)
|
||||
if err := gNotifyScript.Execute(script, &task); err != nil {
|
||||
if err := getConfig().notifyTemplate.Execute(script, &task); err != nil {
|
||||
log.Printf("error: notify: %s", err)
|
||||
return
|
||||
}
|
||||
@ -683,13 +713,14 @@ func notifierNotify(ctx context.Context, task Task) error {
|
||||
TargetURL string `json:"target_url"`
|
||||
}{}
|
||||
|
||||
runner, ok := gConfig.Runners[task.Runner]
|
||||
config := getConfig()
|
||||
runner, ok := config.Runners[task.Runner]
|
||||
if !ok {
|
||||
log.Printf("task %d has an unknown runner %s\n", task.ID, task.Runner)
|
||||
return nil
|
||||
}
|
||||
payload.Context = runner.Name
|
||||
payload.TargetURL = fmt.Sprintf("%s/task/%d", gConfig.Root, task.ID)
|
||||
payload.TargetURL = fmt.Sprintf("%s/task/%d", config.Root, task.ID)
|
||||
|
||||
switch task.State {
|
||||
case taskStateNew:
|
||||
@ -807,13 +838,14 @@ type RunningTask struct {
|
||||
// newRunningTask prepares a task for running, without executing anything yet.
|
||||
func newRunningTask(task Task) (*RunningTask, error) {
|
||||
rt := &RunningTask{DB: task}
|
||||
config := getConfig()
|
||||
|
||||
var ok bool
|
||||
rt.Runner, ok = gConfig.Runners[rt.DB.Runner]
|
||||
rt.Runner, ok = config.Runners[rt.DB.Runner]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown runner: %s", rt.DB.Runner)
|
||||
}
|
||||
project, ok := gConfig.Projects[rt.DB.FullName()]
|
||||
project, ok := config.Projects[rt.DB.FullName()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("project configuration not found")
|
||||
}
|
||||
@ -1381,7 +1413,7 @@ type Task struct {
|
||||
func (t *Task) FullName() string { return t.Owner + "/" + t.Repo }
|
||||
|
||||
func (t *Task) RunnerName() string {
|
||||
if runner, ok := gConfig.Runners[t.Runner]; !ok {
|
||||
if runner, ok := getConfig().Runners[t.Runner]; !ok {
|
||||
return t.Runner
|
||||
} else {
|
||||
return runner.Name
|
||||
@ -1389,20 +1421,20 @@ func (t *Task) RunnerName() string {
|
||||
}
|
||||
|
||||
func (t *Task) URL() string {
|
||||
return fmt.Sprintf("%s/task/%d", gConfig.Root, t.ID)
|
||||
return fmt.Sprintf("%s/task/%d", getConfig().Root, t.ID)
|
||||
}
|
||||
|
||||
func (t *Task) RepoURL() string {
|
||||
return fmt.Sprintf("%s/%s/%s", gConfig.Gitea, t.Owner, t.Repo)
|
||||
return fmt.Sprintf("%s/%s/%s", getConfig().Gitea, t.Owner, t.Repo)
|
||||
}
|
||||
|
||||
func (t *Task) CommitURL() string {
|
||||
return fmt.Sprintf("%s/%s/%s/commit/%s",
|
||||
gConfig.Gitea, t.Owner, t.Repo, t.Hash)
|
||||
getConfig().Gitea, t.Owner, t.Repo, t.Hash)
|
||||
}
|
||||
|
||||
func (t *Task) CloneURL() string {
|
||||
return fmt.Sprintf("%s/%s/%s.git", gConfig.Gitea, t.Owner, t.Repo)
|
||||
return fmt.Sprintf("%s/%s/%s.git", getConfig().Gitea, t.Owner, t.Repo)
|
||||
}
|
||||
|
||||
func (t *Task) update() error {
|
||||
@ -1509,7 +1541,7 @@ func callRPC(args []string) error {
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost,
|
||||
fmt.Sprintf("%s/rpc", gConfig.Root), bytes.NewReader(body))
|
||||
fmt.Sprintf("%s/rpc", getConfig().Root), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1580,7 +1612,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := parseConfig(flag.Arg(0)); err != nil {
|
||||
gConfigPath = flag.Arg(0)
|
||||
if err := loadConfig(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if flag.NArg() > 1 {
|
||||
@ -1590,7 +1623,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := dbOpen(gConfig.DB); err != nil {
|
||||
if err := dbOpen(getConfig().DB); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer gDB.Close()
|
||||
@ -1599,7 +1632,7 @@ func main() {
|
||||
ctx, stop := signal.NotifyContext(
|
||||
context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
server := &http.Server{Addr: gConfig.Listen}
|
||||
server := &http.Server{Addr: getConfig().Listen}
|
||||
http.HandleFunc("/{$}", handleTasks)
|
||||
http.HandleFunc("/task/{id}", handleTask)
|
||||
http.HandleFunc("/push", handlePush)
|
||||
|
Loading…
Reference in New Issue
Block a user