From 9e2e02d8bdf19647864598d4a7299f5afeaaabc8 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Sat, 3 Feb 2024 11:51:29 -0500 Subject: [PATCH] add api for triggering jobs --- README.md | 20 ++++++++++++++++ config.yaml | 4 ++++ http.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 40 ++++++++++++++++++++++++++----- 4 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 http.go diff --git a/README.md b/README.md index 246ce54..b7636db 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ config: # optional: allows you to specify the max number of jobs to run at once jobLimit: 1 + # optional: allows you start an http server and trigger jobs via an api + httpServer: + enabled: true + jobs: # job name testRegex: @@ -58,3 +62,19 @@ docker run --rm -it -v "$PWD:$PWD" -w "$PWD" ghcr.io/huskydog9988/docker-db-back ``` The script will then dump the databases to the `out` folder, and continue to run every 5 minutes because of the cron option. + +## API + +> This feature requires the `httpServer` option to be enabled in the config file + +The script can also start an http server and trigger jobs via an api. This is useful if you want to trigger a job elsewhere, like from another backup service. + +To trigger a job, send a GET request to `http://localhost:3333/api/v1/queueJob?jobName=JOB_NAME`. + +Example using the example config file: + +```bash +curl http://localhost:3333/api/v1/queueJob?jobName=testRegex +``` + +The server will respond with a 200 status code **after** the job has finished running. Currently, there is no way to check if the job has failed or not. diff --git a/config.yaml b/config.yaml index ec34e79..6aacb1e 100644 --- a/config.yaml +++ b/config.yaml @@ -5,6 +5,10 @@ config: # optional: allows you to specify the max number of jobs to run at once jobLimit: 1 + # optional: allows you start an http server and trigger jobs via an api + httpServer: + enabled: true + jobs: testRegex: # Database type: postgres, mysql, mariadb diff --git a/http.go b/http.go new file mode 100644 index 0000000..f124442 --- /dev/null +++ b/http.go @@ -0,0 +1,68 @@ +package main + +import ( + "net/http" + + "github.com/rotisserie/eris" + log "github.com/sirupsen/logrus" +) + +var keyServerAddr = "serverAddr" + +// handle any unknown requests +func getRoot(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("Not found\n")) +} + +// start the http server +// handles if the http server is enabled or not +func startHttpServer(httpServer *http.Server, backup *Backup) { + // if the http server is not defined or disabled, return + if !k.Exists("config.httpServer.enabled") { + return + } else if !k.Bool("config.httpServer.enabled") { + return + } + + log.Infof("Starting http server") + + mux := http.NewServeMux() + httpServer.Handler = mux + + mux.HandleFunc("/", getRoot) + mux.HandleFunc("/api/v1/queueJob", func(w http.ResponseWriter, r *http.Request) { + // get the job name + jobName := r.URL.Query().Get("jobName") + if jobName == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("jobName is required\n")) + return + } + + // get the job config + jobConfig, err := getJobConfig(jobName) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error() + "\n")) + return + } + + // queue the job + // should be completly synchronous + // so when it returns, the job should be completed + backup.QueueJob(jobConfig) + + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK\n")) + }) + + err := httpServer.ListenAndServe() + if err != nil { + if eris.Is(err, http.ErrServerClosed) { + // ignore this error + return + } + log.Fatal(eris.Wrap(err, "error in http server")) + } +} diff --git a/main.go b/main.go index 65c3b2b..58f139f 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "net/http" "os" "os/signal" "syscall" @@ -15,6 +16,9 @@ import ( log "github.com/sirupsen/logrus" ) +// array of job names +var jobs []string + type JobConfig struct { Name string Config map[string]string @@ -61,20 +65,33 @@ func main() { backup := NewBackup(cli) // get a list of every job - jobs := k.MapKeys("jobs") + jobs = k.MapKeys("jobs") + + // start the http server + // also handles if it is disabled + httpServer := &http.Server{ + Addr: ":3333", + } + go startHttpServer(httpServer, backup) + defer func() { + err := httpServer.Shutdown(context.Background()) + if err != nil { + log.Error(eris.Wrap(err, "failed to shutdown http server")) + } + }() // schedule each job for _, job := range jobs { log.Infof("Scheduling job: %s", job) - jobConfig := &JobConfig{ - Name: job, - Config: k.StringMap("jobs." + job), + jobConfig, err := getJobConfig(job) + if err != nil { + log.Error(eris.Wrapf(err, "failed to get job config for %s", job)) } - _, err := s.NewJob(gocron.CronJob(jobConfig.Config["cron"], false), gocron.NewTask(backup.QueueJob, jobConfig)) + _, err = s.NewJob(gocron.CronJob(jobConfig.Config["cron"], false), gocron.NewTask(backup.QueueJob, jobConfig)) if err != nil { - log.Fatal(eris.Wrapf(err, "failed to schedule job %s", jobConfig.Name)) + log.Error(eris.Wrapf(err, "failed to schedule job %s", jobConfig.Name)) } } @@ -177,3 +194,14 @@ func preprocesContainerName(name string) string { _, i := utf8.DecodeRuneInString(name) return name[i:] } + +func getJobConfig(name string) (*JobConfig, error) { + if !k.Exists("jobs." + name) { + return nil, eris.Errorf("Job %s does not exist", name) + } + + return &JobConfig{ + Name: name, + Config: k.StringMap("jobs." + name), + }, nil +}