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