intake/core/crontab.go

122 lines
3.2 KiB
Go

package core
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"sort"
"strings"
)
var IntakeCronBegin = "### begin intake-managed crontab entries"
var IntakeCronEnd = "### end intake-managed crontab entries"
func makeCrontabEntry(source string, spec string) string {
// TODO the /etc/profile setup is NixOS-specific, maybe there's another way to do this
return fmt.Sprintf("%-20s . /etc/profile; intake source fetch -s %s", spec, source)
}
func GetCronSources(db DB) (specs map[string]string, err error) {
res, err := db.Query(`
select source, value
from envs
where name = 'INTAKE_CRON'
`)
if err != nil {
return nil, fmt.Errorf("failed to get source crontabs: %v", err)
}
specs = make(map[string]string)
for res.Next() {
var source string
var value string
if err = res.Scan(&source, &value); err != nil {
return nil, fmt.Errorf("failed to scan source crontab: %v", err)
}
specs[source] = makeCrontabEntry(source, value)
}
return
}
// Update the intake-managed section of the user's crontab.
func UpdateCrontab(db DB, specs map[string]string) (err error) {
// If there is no crontab command available, quit early.
crontabPath, err := exec.LookPath("crontab")
if err != nil {
return fmt.Errorf("no crontab found")
}
// Get the current crontab without extra header lines via `EDITOR=cat crontab -e`
cmdLoad := exec.Command(crontabPath, "-e")
cmdLoad.Env = append(os.Environ(), "EDITOR=cat")
output, err := cmdLoad.Output()
if err != nil {
return fmt.Errorf("error: failed to get current crontab: %v", err)
}
lines := strings.Split(string(output), "\n")
// Sort the new intake crons
var sources []string
for source := range specs {
sources = append(sources, source)
}
sort.Strings(sources)
// Splice the intake crons into the crontab
var newCrontab []string
headerFound := false
inSection := false
for i := range lines {
switch {
case !headerFound && lines[i] == IntakeCronBegin:
headerFound = true
inSection = true
newCrontab = append(newCrontab, IntakeCronBegin)
for _, source := range sources {
newCrontab = append(newCrontab, specs[source])
}
case lines[i] == IntakeCronEnd:
newCrontab = append(newCrontab, IntakeCronEnd)
inSection = false
case !inSection:
newCrontab = append(newCrontab, lines[i])
}
}
// If the splice mark was never found, append the whole section to the end
if !headerFound {
newCrontab = append(newCrontab, IntakeCronBegin)
for _, source := range sources {
newCrontab = append(newCrontab, specs[source])
}
newCrontab = append(newCrontab, IntakeCronEnd)
}
log.Printf("Updating %d crontab entries", len(specs))
// Save the updated crontab
cmdSave := exec.Command(crontabPath, "-")
stdin, err := cmdSave.StdinPipe()
if err != nil {
return fmt.Errorf("failed to open stdin: %v", err)
}
if _, err := io.WriteString(stdin, strings.Join(newCrontab, "\n")); err != nil {
return fmt.Errorf("failed to write to crontab: %v", err)
}
if err := stdin.Close(); err != nil {
return fmt.Errorf("failed to close stdin: %v", err)
}
output, err = cmdSave.CombinedOutput()
if err != nil {
log.Printf("failed to read crontab output: %v", err)
}
if len(output) > 0 {
log.Printf("crontab output: %s", string(output))
}
return nil
}