Skip to content

Commit

Permalink
Merge pull request #2377 from semaphoreui/runners_ui
Browse files Browse the repository at this point in the history
runners ui
  • Loading branch information
fiftin authored Sep 29, 2024
2 parents 8f25e35 + 0bd8c87 commit 1ca883f
Show file tree
Hide file tree
Showing 45 changed files with 5,205 additions and 1,571 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ web/public/fonts/*.*
web/.nyc_output
api/public/**/*
/config.json
/config-runner.json
/config-runner-token.txt
/.dredd/config.json
/database.boltdb
/database.boltdb.lock
Expand All @@ -29,3 +31,4 @@ node_modules/
.vscode
__debug_bin*
.task/
/web/.env
1 change: 1 addition & 0 deletions api/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ func setAppActive(w http.ResponseWriter, r *http.Request) {
}

if !helpers.Bind(w, r, &body) {
helpers.WriteErrorStatus(w, "Invalid request body", http.StatusBadRequest)
return
}

Expand Down
30 changes: 22 additions & 8 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,20 @@ func Route() *mux.Router {
publicAPIRouter := r.PathPrefix(webPath + "api").Subrouter()
publicAPIRouter.Use(StoreMiddleware, JSONMiddleware)

publicAPIRouter.HandleFunc("/runners", runners.RegisterRunner).Methods("POST")
publicAPIRouter.HandleFunc("/auth/login", login).Methods("GET", "POST")
publicAPIRouter.HandleFunc("/auth/logout", logout).Methods("POST")
publicAPIRouter.HandleFunc("/auth/oidc/{provider}/login", oidcLogin).Methods("GET")
publicAPIRouter.HandleFunc("/auth/oidc/{provider}/redirect", oidcRedirect).Methods("GET")
publicAPIRouter.HandleFunc("/auth/oidc/{provider}/redirect/{redirect_path:.*}", oidcRedirect).Methods("GET")

routersAPI := r.PathPrefix(webPath + "api").Subrouter()
routersAPI.Use(StoreMiddleware, JSONMiddleware, runners.RunnerMiddleware)
routersAPI.Path("/runners/{runner_id}").HandlerFunc(runners.GetRunner).Methods("GET", "HEAD")
routersAPI.Path("/runners/{runner_id}").HandlerFunc(runners.UpdateRunner).Methods("PUT")
routersAPI.Path("/runners/{runner_id}").HandlerFunc(runners.UnregisterRunner).Methods("DELETE")
internalAPI := publicAPIRouter.PathPrefix("/internal").Subrouter()
internalAPI.HandleFunc("/runners", runners.RegisterRunner).Methods("POST")

runnersAPI := internalAPI.PathPrefix("/runners").Subrouter()
runnersAPI.Use(runners.RunnerMiddleware)
runnersAPI.Path("").HandlerFunc(runners.GetRunner).Methods("GET", "HEAD")
runnersAPI.Path("").HandlerFunc(runners.UpdateRunner).Methods("PUT")
runnersAPI.Path("").HandlerFunc(runners.UnregisterRunner).Methods("DELETE")

publicWebHookRouter := r.PathPrefix(webPath + "api").Subrouter()
publicWebHookRouter.Use(StoreMiddleware, JSONMiddleware)
Expand Down Expand Up @@ -128,6 +130,16 @@ func Route() *mux.Router {
adminAPI.Path("/options").HandlerFunc(getOptions).Methods("GET", "HEAD")
adminAPI.Path("/options").HandlerFunc(setOption).Methods("POST")

adminAPI.Path("/runners").HandlerFunc(getGlobalRunners).Methods("GET", "HEAD")
adminAPI.Path("/runners").HandlerFunc(addGlobalRunner).Methods("POST", "HEAD")

globalRunnersAPI := adminAPI.PathPrefix("/runners").Subrouter()
globalRunnersAPI.Use(globalRunnerMiddleware)
globalRunnersAPI.Path("/{runner_id}").HandlerFunc(getGlobalRunner).Methods("GET", "HEAD")
globalRunnersAPI.Path("/{runner_id}").HandlerFunc(updateGlobalRunner).Methods("PUT", "POST")
globalRunnersAPI.Path("/{runner_id}/active").HandlerFunc(setGlobalRunnerActive).Methods("POST")
globalRunnersAPI.Path("/{runner_id}").HandlerFunc(deleteGlobalRunner).Methods("DELETE")

appsAPI := adminAPI.PathPrefix("/apps").Subrouter()
appsAPI.Use(appMiddleware)
appsAPI.Path("/{app_id}").HandlerFunc(getApp).Methods("GET", "HEAD")
Expand Down Expand Up @@ -446,8 +458,10 @@ func serveFile(w http.ResponseWriter, r *http.Request, name string) {

func getSystemInfo(w http.ResponseWriter, r *http.Request) {
body := map[string]interface{}{
"version": util.Version(),
"ansible": util.AnsibleVersion(),
"version": util.Version(),
"ansible": util.AnsibleVersion(),
"web_host": util.WebHostURL.String(),
"use_remote_runner": util.Config.UseRemoteRunner,
}

helpers.WriteJSON(w, http.StatusOK, body)
Expand Down
157 changes: 157 additions & 0 deletions api/runners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package api

import (
"github.com/ansible-semaphore/semaphore/api/helpers"
"github.com/ansible-semaphore/semaphore/db"
log "github.com/sirupsen/logrus"
"net/http"

"github.com/gorilla/context"
)

//type minimalGlobalRunner struct {
// ID int `json:"id"`
// Name string `json:"name"`
// Active bool `json:"active"`
// Webhook string `db:"webhook" json:"webhook"`
// MaxParallelTasks int `db:"max_parallel_tasks" json:"max_parallel_tasks"`
//}

func getGlobalRunners(w http.ResponseWriter, r *http.Request) {
runners, err := helpers.Store(r).GetGlobalRunners(false)

if err != nil {
panic(err)
}

var result = make([]db.Runner, 0)

for _, runner := range runners {
result = append(result, runner)
}

helpers.WriteJSON(w, http.StatusOK, result)
}

type runnerWithToken struct {
db.Runner
Token string `json:"token"`
}

func addGlobalRunner(w http.ResponseWriter, r *http.Request) {
var runner db.Runner
if !helpers.Bind(w, r, &runner) {
return
}

runner.ProjectID = nil
newRunner, err := helpers.Store(r).CreateRunner(runner)

if err != nil {
log.Warn("Runner is not created: " + err.Error())
w.WriteHeader(http.StatusBadRequest)
return
}

helpers.WriteJSON(w, http.StatusCreated, runnerWithToken{
Runner: newRunner,
Token: newRunner.Token,
})
}

func globalRunnerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
runnerID, err := helpers.GetIntParam("runner_id", w, r)

if err != nil {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{
"error": "runner_id required",
})
return
}

store := helpers.Store(r)

runner, err := store.GetGlobalRunner(runnerID)

if err != nil {
helpers.WriteJSON(w, http.StatusNotFound, map[string]string{
"error": "Runner not found",
})
return
}

context.Set(r, "runner", &runner)
next.ServeHTTP(w, r)
})
}

func getGlobalRunner(w http.ResponseWriter, r *http.Request) {
runner := context.Get(r, "runner").(*db.Runner)

helpers.WriteJSON(w, http.StatusOK, runner)
}

func updateGlobalRunner(w http.ResponseWriter, r *http.Request) {
oldRunner := context.Get(r, "runner").(*db.Runner)

var runner db.Runner
if !helpers.Bind(w, r, &runner) {
return
}

store := helpers.Store(r)

runner.ID = oldRunner.ID
runner.ProjectID = nil

err := store.UpdateRunner(runner)

if err != nil {
helpers.WriteErrorStatus(w, err.Error(), http.StatusBadRequest)
return
}

w.WriteHeader(http.StatusNoContent)
}

func deleteGlobalRunner(w http.ResponseWriter, r *http.Request) {
runner := context.Get(r, "runner").(*db.Runner)

store := helpers.Store(r)

err := store.DeleteGlobalRunner(runner.ID)

if err != nil {
helpers.WriteErrorStatus(w, err.Error(), http.StatusBadRequest)
return
}

w.WriteHeader(http.StatusNoContent)
}

func setGlobalRunnerActive(w http.ResponseWriter, r *http.Request) {
runner := context.Get(r, "runner").(*db.Runner)

store := helpers.Store(r)

var body struct {
Active bool `json:"active"`
}

if !helpers.Bind(w, r, &body) {
helpers.WriteErrorStatus(w, "Invalid request body", http.StatusBadRequest)
return
}

runner.Active = body.Active

err := store.UpdateRunner(*runner)

if err != nil {
helpers.WriteErrorStatus(w, err.Error(), http.StatusBadRequest)
return
}

w.WriteHeader(http.StatusNoContent)
}
20 changes: 6 additions & 14 deletions api/runners/runners.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func RunnerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

token := r.Header.Get("X-API-Token")
token := r.Header.Get("X-Runner-Token")

if token == "" {
helpers.WriteJSON(w, http.StatusUnauthorized, map[string]string{
Expand All @@ -23,18 +23,9 @@ func RunnerMiddleware(next http.Handler) http.Handler {
return
}

runnerID, err := helpers.GetIntParam("runner_id", w, r)

if err != nil {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{
"error": "runner_id required",
})
return
}

store := helpers.Store(r)

runner, err := store.GetGlobalRunner(runnerID)
runner, err := store.GetGlobalRunnerByToken(token)

if err != nil {
helpers.WriteJSON(w, http.StatusNotFound, map[string]string{
Expand Down Expand Up @@ -199,11 +190,12 @@ func RegisterRunner(w http.ResponseWriter, r *http.Request) {
return
}

res := util.RunnerConfig{
RunnerID: runner.ID,
Token: runner.Token,
var res struct {
Token string `json:"token"`
}

res.Token = runner.Token

helpers.WriteJSON(w, http.StatusOK, res)
}

Expand Down
6 changes: 4 additions & 2 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
)

var configPath string
var noConfig bool

var rootCmd = &cobra.Command{
Use: "semaphore",
Expand All @@ -34,8 +35,9 @@ Complete documentation is available at https://ansible-semaphore.com.`,

func Execute() {
rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "Configuration file path")
rootCmd.PersistentFlags().BoolVar(&noConfig, "no-config", false, "Don't use configuration file")
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Expand Down Expand Up @@ -96,7 +98,7 @@ func runService() {
}

func createStore(token string) db.Store {
util.ConfigInit(configPath)
util.ConfigInit(configPath, noConfig)

store := factory.CreateStore()

Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/runner_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func init() {
}

func runRunner() {
util.ConfigInit(configPath)
util.ConfigInit(configPath, noConfig)

taskPool := runners.JobPool{}

Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/runner_unregister.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func init() {
}

func unregisterRunner() {
util.ConfigInit(configPath)
util.ConfigInit(configPath, noConfig)

taskPool := runners.JobPool{}
err := taskPool.Unregister()
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func doSetup() int {

configPath := setup.SaveConfig(config)

util.ConfigInit(configPath)
util.ConfigInit(configPath, false)

fmt.Println(" Pinging db..")

Expand Down
13 changes: 0 additions & 13 deletions config-runner.json

This file was deleted.

1 change: 1 addition & 0 deletions db/Migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func GetMigrations() []Migration {
{Version: "2.10.12"},
{Version: "2.10.15"},
{Version: "2.10.16"},
{Version: "2.10.26"},
}
}

Expand Down
4 changes: 3 additions & 1 deletion db/Runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ type RunnerState string
//)

type Runner struct {
ID int `db:"id" json:"-"`
ID int `db:"id" json:"id"`
Token string `db:"token" json:"-"`
ProjectID *int `db:"project_id" json:"project_id"`
//State RunnerState `db:"state" json:"state"`
Webhook string `db:"webhook" json:"webhook"`
MaxParallelTasks int `db:"max_parallel_tasks" json:"max_parallel_tasks"`
Active bool `db:"active" json:"active"`
Name string `db:"name" json:"name"`
}
3 changes: 2 additions & 1 deletion db/Store.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,9 @@ type Store interface {
GetRunner(projectID int, runnerID int) (Runner, error)
GetRunners(projectID int) ([]Runner, error)
DeleteRunner(projectID int, runnerID int) error
GetGlobalRunnerByToken(token string) (Runner, error)
GetGlobalRunner(runnerID int) (Runner, error)
GetGlobalRunners() ([]Runner, error)
GetGlobalRunners(activeOnly bool) ([]Runner, error)
DeleteGlobalRunner(runnerID int) error
UpdateRunner(runner Runner) error
CreateRunner(runner Runner) (Runner, error)
Expand Down
Loading

0 comments on commit 1ca883f

Please sign in to comment.