Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
48a7c17448 | |||
ae6ca5c185 | |||
5dd38b7f18 | |||
0a1b9c1305 | |||
f7acde85c0 | |||
ec78765e4f | |||
9bbe536f39 | |||
85a6729def | |||
980a5bfa9f | |||
367265b5e6 | |||
048b9b8547 |
11 changed files with 135 additions and 45 deletions
|
@ -2,14 +2,13 @@
|
||||||
name: Build and release
|
name: Build and release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
workflow_dispatch:
|
||||||
types: [published]
|
inputs:
|
||||||
# workflow_dispatch:
|
version:
|
||||||
# inputs:
|
type: string
|
||||||
# version:
|
description: 'Version'
|
||||||
# description: 'Version'
|
required: true
|
||||||
# required: true
|
default: 'v0'
|
||||||
# default: 'v0'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
@ -24,24 +23,19 @@ jobs:
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.23'
|
||||||
|
|
||||||
# - name: Install dependencies
|
|
||||||
# run: |
|
|
||||||
# sudo apt-get update
|
|
||||||
# sudo apt-get install -y task
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: task test
|
run: task test
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
task build VERSION=${{ github.ref_name }}
|
task build VERSION=${{ github.event.inputs.version }}
|
||||||
|
|
||||||
- name: Upload Release Asset
|
- uses: actions/forgejo-release@v2
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ github.event.release.upload_url }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
asset_path: ./lobby2-${{ github.ref_name }}-amd64
|
direction: upload
|
||||||
asset_name: lobby2-${{ github.ref_name }}-amd64
|
tag: ${{ github.event.inputs.version }}
|
||||||
asset_content_type: application/octet-stream
|
title: ${{ github.event.inputs.version }}
|
||||||
|
url: https://gitea.ceperka.net
|
||||||
|
release-dir: bin/
|
||||||
|
release-notes-assistant: true
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
||||||
tmp/
|
tmp/
|
||||||
__debug*
|
__debug*
|
||||||
main
|
main
|
||||||
|
bin/
|
||||||
|
nodes.json
|
||||||
|
|
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -29,7 +29,7 @@
|
||||||
"env": {
|
"env": {
|
||||||
"DUMP_PATH": "../tmp/nodes.json",
|
"DUMP_PATH": "../tmp/nodes.json",
|
||||||
"CONFIG_PATH": "../tmp/config.json",
|
"CONFIG_PATH": "../tmp/config.json",
|
||||||
"NODE_DIR_PATH": "../tmp/node"
|
"NODE_PATH": "../tmp/node.json"
|
||||||
},
|
},
|
||||||
"args": [
|
"args": [
|
||||||
"node"
|
"node"
|
||||||
|
|
|
@ -18,4 +18,10 @@ tasks:
|
||||||
build:
|
build:
|
||||||
cmds:
|
cmds:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
- go build -o lobby2-{{ .VERSION }}-amd64 cli/*.go
|
- mkdir -p bin
|
||||||
|
- env CGO_ENABLED=0 go build -o bin/lobby2-{{ .VERSION }}-linux-amd64 cli/*.go
|
||||||
|
deploy:
|
||||||
|
cmds:
|
||||||
|
- task: build
|
||||||
|
- scp bin/lobby2-{{ .VERSION }}-linux-amd64 rosti-db:/usr/local/bin/lobby2.tmp
|
||||||
|
- ssh rosti-db mv /usr/local/bin/lobby2.tmp /usr/local/bin/lobby2
|
||||||
|
|
16
api/main.go
16
api/main.go
|
@ -53,6 +53,7 @@ func (a *API) Run() error {
|
||||||
a.e.GET("/nodes", a.listHandler)
|
a.e.GET("/nodes", a.listHandler)
|
||||||
a.e.GET("/nodes/:hostname", a.getHandler)
|
a.e.GET("/nodes/:hostname", a.getHandler)
|
||||||
a.e.POST("/nodes/:hostname", a.refreshHandler)
|
a.e.POST("/nodes/:hostname", a.refreshHandler)
|
||||||
|
a.e.GET("/prometheus/:service", a.prometheusHandler)
|
||||||
|
|
||||||
// Start the server in a goroutine so that it doesn't block the signal listening
|
// Start the server in a goroutine so that it doesn't block the signal listening
|
||||||
return a.e.Start(a.listen)
|
return a.e.Start(a.listen)
|
||||||
|
@ -111,3 +112,18 @@ func (a *API) refreshHandler(c echo.Context) error {
|
||||||
|
|
||||||
return c.NoContent(http.StatusNoContent)
|
return c.NoContent(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Prometheus service discovery
|
||||||
|
// @Description Return one nodes based on given hostname
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {array} []prometheusDiscovery "Node details"
|
||||||
|
// @Failure 401 {object} Message "Forbidden access"
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /prometheus/{service} [get]
|
||||||
|
func (a *API) prometheusHandler(c echo.Context) error {
|
||||||
|
ss := c.Param("service")
|
||||||
|
|
||||||
|
pds := nodes.GetPrometheusSD(a.np, ss)
|
||||||
|
|
||||||
|
return c.JSONPretty(http.StatusOK, pds, " ")
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ func masterAction(c *cli.Context) error {
|
||||||
func nodeAction(c *cli.Context) error {
|
func nodeAction(c *cli.Context) error {
|
||||||
cfg := GetConfig()
|
cfg := GetConfig()
|
||||||
|
|
||||||
r := refresher.NewRefresher(cfg.NodeDirPath, cfg.ConfigPath)
|
r := refresher.NewRefresher(cfg.NodePath, cfg.ConfigPath)
|
||||||
r.Loop()
|
r.Loop()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -52,3 +52,23 @@ func printAction(c *cli.Context) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prometheusAction(c *cli.Context) error {
|
||||||
|
cfg := GetConfig()
|
||||||
|
|
||||||
|
np := nodes.NewNodesProcessor(cfg.DumpPath, cfg.DropAfterSeconds)
|
||||||
|
|
||||||
|
pds := nodes.GetPrometheusSD(np, "node")
|
||||||
|
|
||||||
|
for _, pd := range pds {
|
||||||
|
body, err := json.MarshalIndent(pd, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to marshal prometheus discovery: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@ type Config struct {
|
||||||
APIListen string `envconfig:"LISTEN" default:"0.0.0.0:1352"`
|
APIListen string `envconfig:"LISTEN" default:"0.0.0.0:1352"`
|
||||||
APIToken string `envconfig:"TOKEN" default:""`
|
APIToken string `envconfig:"TOKEN" default:""`
|
||||||
|
|
||||||
DumpPath string `envconfig:"DUMP_PATH" default:"/var/lib/lobby2/nodes.json"`
|
DumpPath string `envconfig:"DUMP_PATH" default:"/var/lib/lobby2/nodes.json"`
|
||||||
ConfigPath string `envconfig:"CONFIG_PATH" default:"/var/lib/lobby2/config.json"`
|
ConfigPath string `envconfig:"CONFIG_PATH" default:"/etc/lobby2/config.json"`
|
||||||
NodeDirPath string `envconfig:"NODE_DIR_PATH" default:"/var/lib/lobby2/node"`
|
NodePath string `envconfig:"NODE_PATH" default:"/etc/lobby2/node.json"`
|
||||||
|
|
||||||
DropAfterSeconds int64 `envconfig:"DROP_AFTER_SECONDS" default:"60"`
|
DropAfterSeconds int64 `envconfig:"DROP_AFTER_SECONDS" default:"60"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,11 @@ func main() {
|
||||||
Usage: "Prints all discovered nodes",
|
Usage: "Prints all discovered nodes",
|
||||||
Action: printAction,
|
Action: printAction,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "prometheus",
|
||||||
|
Usage: "Prints Prometheus Service Discovery",
|
||||||
|
Action: prometheusAction,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,11 +32,12 @@ func TestNodesProcessor_Refresh(t *testing.T) {
|
||||||
np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
|
np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
|
||||||
np.Refresh("test2", Labels{"mylabel3"}, KV{"mykey3": "my value3"})
|
np.Refresh("test2", Labels{"mylabel3"}, KV{"mykey3": "my value3"})
|
||||||
|
|
||||||
nodes := np.List()
|
nodeTest, _ := np.Get("test")
|
||||||
assert.Equal(t, "test", nodes[0].HostName)
|
nodeTest2, _ := np.Get("test2")
|
||||||
assert.Contains(t, nodes[0].Labels, "mylabel")
|
assert.Equal(t, "test", nodeTest.HostName)
|
||||||
assert.Equal(t, "test2", nodes[1].HostName)
|
assert.Contains(t, nodeTest.Labels, "mylabel")
|
||||||
assert.Contains(t, nodes[1].Labels, "mylabel3")
|
assert.Equal(t, "test2", nodeTest2.HostName)
|
||||||
|
assert.Contains(t, nodeTest2.Labels, "mylabel3")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNodesProcessor_DumpLoad(t *testing.T) {
|
func TestNodesProcessor_DumpLoad(t *testing.T) {
|
||||||
|
|
45
nodes/prometheus.go
Normal file
45
nodes/prometheus.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package nodes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type prometheusDiscovery struct {
|
||||||
|
Labels map[string]string `json:"Labels"`
|
||||||
|
Targets []string `json:"Targets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPrometheusSD(p *NodesProcessor, ss string) []prometheusDiscovery {
|
||||||
|
ns := p.List()
|
||||||
|
|
||||||
|
pds := []prometheusDiscovery{
|
||||||
|
{
|
||||||
|
Labels: make(map[string]string),
|
||||||
|
Targets: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range ns {
|
||||||
|
port, ok := node.KV["prometheus_port"]
|
||||||
|
if !ok {
|
||||||
|
port = "9999"
|
||||||
|
}
|
||||||
|
host, ok := node.KV["prometheus_host"]
|
||||||
|
if !ok {
|
||||||
|
host = node.HostName
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := node.KV["prometheus_exporters"]
|
||||||
|
if ok {
|
||||||
|
services := strings.Split(v, ",")
|
||||||
|
for _, service := range services {
|
||||||
|
if ss == service {
|
||||||
|
pds[0].Targets = append(pds[0].Targets, fmt.Sprintf("%s:%s", host, port))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pds
|
||||||
|
}
|
|
@ -7,25 +7,24 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.ceperka.net/rosti/lobby2/nodes"
|
"gitea.ceperka.net/rosti/lobby2/nodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
const refreshIntervalSeconds = 15
|
const refreshIntervalSeconds = 15
|
||||||
const nodeFileName = "node.json"
|
|
||||||
|
|
||||||
// Refresher loads local node info and sends them to the master node.
|
// Refresher loads local node info and sends them to the master node.
|
||||||
type Refresher struct {
|
type Refresher struct {
|
||||||
configPath string
|
configPath string
|
||||||
nodeDirPath string
|
nodePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRefresher(nodeDirPath string, configPath string) *Refresher {
|
func NewRefresher(nodePath string, configPath string) *Refresher {
|
||||||
return &Refresher{
|
return &Refresher{
|
||||||
nodeDirPath: nodeDirPath,
|
nodePath: nodePath,
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +112,7 @@ func (r *Refresher) getConfig() (NodeConfig, error) {
|
||||||
|
|
||||||
// TODO: rewrite this to load Node structure
|
// TODO: rewrite this to load Node structure
|
||||||
func (r *Refresher) loadNode() (*nodes.Node, error) {
|
func (r *Refresher) loadNode() (*nodes.Node, error) {
|
||||||
filePath := path.Join(r.nodeDirPath, nodeFileName)
|
filePath := r.nodePath
|
||||||
_, err := os.Stat(filePath)
|
_, err := os.Stat(filePath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
err = r.initNodeFile()
|
err = r.initNodeFile()
|
||||||
|
@ -122,7 +121,7 @@ func (r *Refresher) loadNode() (*nodes.Node, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := os.ReadFile(path.Join(r.nodeDirPath, nodeFileName))
|
content, err := os.ReadFile(r.nodePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
return nil, fmt.Errorf("failed to read file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -156,7 +155,7 @@ func (r *Refresher) initNodeFile() error {
|
||||||
return fmt.Errorf("failed to marshal node: %w", err)
|
return fmt.Errorf("failed to marshal node: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(path.Join(r.nodeDirPath, nodeFileName), nodeBytes, 0640)
|
err = os.WriteFile(r.nodePath, nodeBytes, 0640)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write node file: %w", err)
|
return fmt.Errorf("failed to write node file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -165,9 +164,11 @@ func (r *Refresher) initNodeFile() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Refresher) createNodePath() error {
|
func (r *Refresher) createNodePath() error {
|
||||||
_, err := os.Stat(r.nodeDirPath)
|
d := filepath.Dir(r.nodePath)
|
||||||
|
|
||||||
|
_, err := os.Stat(d)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
err = os.MkdirAll(r.nodeDirPath, 0755)
|
err = os.MkdirAll(d, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create node dir: %w", err)
|
return fmt.Errorf("failed to create node dir: %w", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue