Skip to content

Commit c92ee2a

Browse files
authored
Merge branch 'semaphoreui:develop' into develop
2 parents 178b1df + 865d0e5 commit c92ee2a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+39000
-35867
lines changed

.devcontainer/config-runner.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"web_host": "http://localhost:3000",
3+
"runner": {
4+
"token_file": "/home/codespace/.semaphore-runner-token"
5+
}
6+
}

.devcontainer/config.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"bolt": {
3+
"host": "database.boltdb",
4+
"options": {
5+
"sessionConnection": "true"
6+
}
7+
},
8+
"dialect": "bolt",
9+
"cookie_hash": "5WJjXCLpvf3Cn5t+C/IV9F0asZUQLakOhCT+eSdIwP0=",
10+
"cookie_encryption": "6x6mmQWGn6YcsHN1rN0HiQjhYA+7HukcbCxUGHuT2CE="
11+
}
12+

.devcontainer/devcontainer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"features": {
44
"ghcr.io/devcontainers/features/go:1": {},
55
"ghcr.io/devcontainers/features/node:1": {}
6-
}
6+
},
7+
"postCreateCommand": "./.devcontainer/postCreateCommand.sh"
78
}

.devcontainer/postCreateCommand.sh

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
go install github.com/go-task/task/v3/cmd/task@latest
2+
3+
(cd ./web && npm install)
4+
5+
python3 -m venv .venv
6+
7+
./.venv/bin/pip3 install ansible
8+
9+
task build
10+
11+
./bin/semaphore user add \
12+
--admin \
13+
--login admin \
14+
--name Admin \
15+
16+
--password changeme \
17+
--config ./.devcontainer/config.json

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ node_modules/
2828
.dredd/compiled_hooks
2929
.dredd/compiled_hooks.exe
3030

31-
.vscode
3231
__debug_bin*
3332
.task/
34-
/web/.env
33+
/web/.env
34+
/.venv/

.vscode/launch.json

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Launch Server",
6+
"type": "go",
7+
"request": "launch",
8+
"mode": "auto",
9+
"program": "./cli/main.go",
10+
"args": ["server", "--config", "${workspaceFolder}/.devcontainer/config.json"],
11+
"cwd": "${workspaceFolder}",
12+
"env": {
13+
"PATH": "${workspaceFolder}/.venv/bin:${env:PATH}"
14+
}
15+
},
16+
{
17+
"name": "Launch Server with remote Runner",
18+
"type": "go",
19+
"request": "launch",
20+
"mode": "auto",
21+
"program": "./cli/main.go",
22+
"args": ["server", "--config", "${workspaceFolder}/.devcontainer/config.json"],
23+
"cwd": "${workspaceFolder}",
24+
"env": {
25+
"PATH": "${workspaceFolder}/.venv/bin:${env:PATH}",
26+
"SEMAPHORE_USE_REMOTE_RUNNER": "true"
27+
}
28+
},
29+
{
30+
"name": "Launch Runner",
31+
"type": "go",
32+
"request": "launch",
33+
"mode": "auto",
34+
"program": "./cli/main.go",
35+
"args": ["runner", "start", "--config", "${workspaceFolder}/.devcontainer/config-runner.json", "--log-level", "debug"],
36+
"cwd": "${workspaceFolder}",
37+
"env": {
38+
"PATH": "${workspaceFolder}/.venv/bin:${env:PATH}"
39+
}
40+
}
41+
]
42+
}

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
Modern UI for Ansible, Terraform, OpenTofu, PowerShell and other DevOps tools.
44

55
[![telegram](https://img.shields.io/badge/discord_community-skyblue?style=for-the-badge&logo=discord)](https://discord.gg/5R6k7hNGcH)
6-
[![telegram](https://img.shields.io/badge/youtube_channel-red?style=for-the-badge&logo=youtube)](https://www.youtube.com/@semaphoreui)
6+
[![youtube](https://img.shields.io/badge/youtube_channel-red?style=for-the-badge&logo=youtube)](https://www.youtube.com/@semaphoreui)
7+
<!-- [![devcontainer](https://img.shields.io/badge/dev_container-gray?style=for-the-badge&logo=github)](https://codespaces.new/semaphoreui/semaphore) -->
78
<!-- [![docker](https://img.shields.io/badge/container_configurator-white?style=for-the-badge&logo=docker)](https://semaphoreui.com/install/docker/) -->
89

910
![responsive-ui-phone1](https://user-images.githubusercontent.com/914224/134777345-8789d9e4-ff0d-439c-b80e-ddc56b74fcee.png)
@@ -80,6 +81,10 @@ For more installation options, visit our [Installation page](https://semaphoreui
8081
* [User Guide](https://docs.semaphoreui.com)
8182
* [API Reference](https://semaphoreui.com/api-docs)
8283

84+
## Contribution
85+
* [Contribution Guide](https://github.com/semaphoreui/semaphore/blob/develop/CONTRIBUTING.md)
86+
* [Dev Container](https://codespaces.new/semaphoreui/semaphore) (default user `admin` / `changeme`)
87+
8388
## License
8489
MIT © [Denis Gukov](https://github.com/fiftin)
8590

api/router.go

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/ansible-semaphore/semaphore/api/helpers"
1616
"github.com/ansible-semaphore/semaphore/api/projects"
1717
"github.com/ansible-semaphore/semaphore/api/sockets"
18+
"github.com/ansible-semaphore/semaphore/api/tasks"
1819
"github.com/ansible-semaphore/semaphore/db"
1920
"github.com/ansible-semaphore/semaphore/util"
2021
"github.com/gorilla/mux"
@@ -147,6 +148,12 @@ func Route() *mux.Router {
147148
appsAPI.Path("/{app_id}/active").HandlerFunc(setAppActive).Methods("POST")
148149
appsAPI.Path("/{app_id}").HandlerFunc(deleteApp).Methods("DELETE")
149150

151+
adminAPI.Path("/tasks").HandlerFunc(tasks.GetTasks).Methods("GET", "HEAD")
152+
tasksAPI := adminAPI.PathPrefix("/tasks").Subrouter()
153+
tasksAPI.Use(tasks.TaskMiddleware)
154+
tasksAPI.Path("/{task_id}").HandlerFunc(tasks.GetTasks).Methods("GET", "HEAD")
155+
tasksAPI.Path("/{task_id}").HandlerFunc(tasks.DeleteTask).Methods("DELETE")
156+
150157
userAPI := authenticatedAPI.Path("/users/{user_id}").Subrouter()
151158
userAPI.Use(getUserMiddleware)
152159

api/tasks/tasks.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package tasks
2+
3+
import (
4+
"github.com/ansible-semaphore/semaphore/pkg/task_logger"
5+
"net/http"
6+
7+
"github.com/ansible-semaphore/semaphore/api/helpers"
8+
"github.com/ansible-semaphore/semaphore/db"
9+
task2 "github.com/ansible-semaphore/semaphore/services/tasks"
10+
"github.com/gorilla/context"
11+
)
12+
13+
func TaskMiddleware(next http.Handler) http.Handler {
14+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15+
taskID, err := helpers.GetIntParam("task_id", w, r)
16+
if err != nil {
17+
helpers.WriteErrorStatus(w, err.Error(), http.StatusBadRequest)
18+
}
19+
20+
context.Set(r, "task_id", taskID)
21+
next.ServeHTTP(w, r)
22+
})
23+
}
24+
25+
type taskLocation string
26+
27+
const (
28+
taskQueue taskLocation = "queue"
29+
taskRunning taskLocation = "running"
30+
)
31+
32+
type taskRes struct {
33+
TaskID int `json:"task_id"`
34+
ProjectID int `json:"project_id"`
35+
Username string `json:"username,omitempty"`
36+
RunnerID int `json:"runner_id,omitempty"`
37+
Status task_logger.TaskStatus `json:"status"`
38+
Location taskLocation `json:"location"`
39+
RunnerName string `json:"runner_name,omitempty"`
40+
ProjectName string `json:"project_name,omitempty"`
41+
}
42+
43+
func GetTasks(w http.ResponseWriter, r *http.Request) {
44+
pool := context.Get(r, "task_pool").(*task2.TaskPool)
45+
46+
res := []taskRes{}
47+
48+
for _, task := range pool.Queue {
49+
res = append(res, taskRes{
50+
TaskID: task.Task.ID,
51+
ProjectID: task.Task.ProjectID,
52+
RunnerID: task.RunnerID,
53+
Username: task.Username,
54+
Status: task.Task.Status,
55+
Location: taskQueue,
56+
})
57+
}
58+
59+
for _, task := range pool.RunningTasks {
60+
res = append(res, taskRes{
61+
TaskID: task.Task.ID,
62+
ProjectID: task.Task.ProjectID,
63+
RunnerID: task.RunnerID,
64+
Username: task.Username,
65+
Status: task.Task.Status,
66+
Location: taskRunning,
67+
})
68+
}
69+
70+
helpers.WriteJSON(w, http.StatusOK, res)
71+
}
72+
73+
func DeleteTask(w http.ResponseWriter, r *http.Request) {
74+
75+
taskID := context.Get(r, "task_id").(int)
76+
77+
pool := context.Get(r, "task_pool").(*task2.TaskPool)
78+
79+
var task *db.Task
80+
81+
for _, t := range pool.Queue {
82+
if t.Task.ID == taskID {
83+
task = &t.Task
84+
break
85+
}
86+
}
87+
88+
if task == nil {
89+
for _, t := range pool.RunningTasks {
90+
if t.Task.ID == taskID {
91+
task = &t.Task
92+
break
93+
}
94+
}
95+
}
96+
97+
if task != nil {
98+
err := pool.StopTask(*task, false)
99+
if err != nil {
100+
helpers.WriteErrorStatus(w, err.Error(), http.StatusInternalServerError)
101+
return
102+
}
103+
}
104+
105+
helpers.WriteJSON(w, http.StatusNoContent, nil)
106+
}

cli/cmd/root.go

+25-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package cmd
22

33
import (
44
"fmt"
5+
"net/http"
6+
"os"
7+
"strings"
8+
59
"github.com/ansible-semaphore/semaphore/api"
610
"github.com/ansible-semaphore/semaphore/api/sockets"
711
"github.com/ansible-semaphore/semaphore/db"
@@ -13,13 +17,13 @@ import (
1317
"github.com/gorilla/handlers"
1418
log "github.com/sirupsen/logrus"
1519
"github.com/spf13/cobra"
16-
"net/http"
17-
"os"
18-
"strings"
1920
)
2021

21-
var configPath string
22-
var noConfig bool
22+
var persistentFlags struct {
23+
configPath string
24+
noConfig bool
25+
logLevel string
26+
}
2327

2428
var rootCmd = &cobra.Command{
2529
Use: "semaphore",
@@ -31,11 +35,24 @@ Complete documentation is available at https://ansible-semaphore.com.`,
3135
_ = cmd.Help()
3236
os.Exit(0)
3337
},
38+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
39+
if persistentFlags.logLevel == "" {
40+
return
41+
}
42+
43+
lvl, err := log.ParseLevel(persistentFlags.logLevel)
44+
if err != nil {
45+
log.Panic(err)
46+
}
47+
48+
log.SetLevel(lvl)
49+
},
3450
}
3551

3652
func Execute() {
37-
rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "Configuration file path")
38-
rootCmd.PersistentFlags().BoolVar(&noConfig, "no-config", false, "Don't use configuration file")
53+
rootCmd.PersistentFlags().StringVar(&persistentFlags.logLevel, "log-level", "", "Log level: DEBUG, INFO, WARN, ERROR, FATAL, PANIC")
54+
rootCmd.PersistentFlags().StringVar(&persistentFlags.configPath, "config", "", "Configuration file path")
55+
rootCmd.PersistentFlags().BoolVar(&persistentFlags.noConfig, "no-config", false, "Don't use configuration file")
3956
if err := rootCmd.Execute(); err != nil {
4057
_, _ = fmt.Fprintln(os.Stderr, err)
4158
os.Exit(1)
@@ -98,7 +115,7 @@ func runService() {
98115
}
99116

100117
func createStore(token string) db.Store {
101-
util.ConfigInit(configPath, noConfig)
118+
util.ConfigInit(persistentFlags.configPath, persistentFlags.noConfig)
102119

103120
store := factory.CreateStore()
104121

cli/cmd/runner_register.go

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,39 @@
11
package cmd
22

33
import (
4+
"io"
5+
"os"
6+
47
"github.com/ansible-semaphore/semaphore/services/runners"
58
"github.com/ansible-semaphore/semaphore/util"
69
"github.com/spf13/cobra"
710
)
811

12+
var runnerRegisterArgs struct {
13+
stdinRegistrationToken bool
14+
}
15+
916
func init() {
17+
runnerRegisterCmd.PersistentFlags().BoolVar(&runnerRegisterArgs.stdinRegistrationToken, "stdin-registration-token", false, "Read registration token from stdin")
1018
runnerCmd.AddCommand(runnerRegisterCmd)
1119
}
1220

1321
func registerRunner() {
14-
util.ConfigInit(configPath, noConfig)
22+
23+
util.ConfigInit(persistentFlags.configPath, persistentFlags.noConfig)
24+
25+
if runnerRegisterArgs.stdinRegistrationToken {
26+
tokenBytes, err := io.ReadAll(os.Stdin)
27+
if err != nil {
28+
panic(err)
29+
}
30+
31+
if len(tokenBytes) == 0 {
32+
panic("Empty token")
33+
}
34+
35+
util.Config.Runner.Token = string(tokenBytes)
36+
}
1537

1638
taskPool := runners.JobPool{}
1739
err := taskPool.Register()

cli/cmd/runner_setup.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"fmt"
5+
56
"github.com/ansible-semaphore/semaphore/cli/setup"
67
"github.com/ansible-semaphore/semaphore/util"
78
"github.com/spf13/cobra"
@@ -21,12 +22,11 @@ var runnerSetupCmd = &cobra.Command{
2122

2223
// nolint: gocyclo
2324
func doRunnerSetup() int {
24-
var config *util.ConfigType
25-
config = &util.ConfigType{}
25+
config := &util.ConfigType{}
2626

2727
setup.InteractiveRunnerSetup(config)
2828

29-
resultConfigPath := setup.SaveConfig(config, "config-runner.json", configPath)
29+
resultConfigPath := setup.SaveConfig(config, "config-runner.json", persistentFlags.configPath)
3030

3131
util.ConfigInit(resultConfigPath, false)
3232

cli/cmd/runner_start.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func init() {
1111
}
1212

1313
func runRunner() {
14-
util.ConfigInit(configPath, noConfig)
14+
util.ConfigInit(persistentFlags.configPath, persistentFlags.noConfig)
1515

1616
taskPool := runners.JobPool{}
1717

0 commit comments

Comments
 (0)