Initial commit

This commit is contained in:
Adam Štrauch 2024-12-08 02:30:07 +01:00
commit f8b9c4f748
Signed by: cx
GPG key ID: 7262DAFE292BCE20
20 changed files with 1494 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
tmp/
__debug*
main

54
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,54 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Master",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cli",
"env": {
"DUMP_PATH": "../tmp/nodes.json",
"CONFIG_PATH": "../tmp/config.json",
"NODE_DIR_PATH": "../tmp/node",
"TOKEN": "abcd"
},
"args": [
"master"
]
},
{
"name": "Node",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cli",
"env": {
"DUMP_PATH": "../tmp/nodes.json",
"CONFIG_PATH": "../tmp/config.json",
"NODE_DIR_PATH": "../tmp/node"
},
"args": [
"node"
]
},
{
"name": "Print",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cli",
"env": {
"DUMP_PATH": "../tmp/nodes.json",
"CONFIG_PATH": "../tmp/config.json",
"NODE_DIR_PATH": "../tmp/node"
},
"args": [
"print"
]
}
]
}

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Lobby 2 - simple service discovery
This is second version of my Lobby projects that doesn't require NATS. All clients uses single service discovery server that keeps track of their presence.
Each instance, except its liveness can share labels which describe what's hosted on this instance and that can used by others instances for various things. There is also a simple KV store for additional info.

9
Taskfile.yml Normal file
View file

@ -0,0 +1,9 @@
# https://taskfile.dev
version: '3'
tasks:
docs:
cmds:
- swag i --parseDependency --dir api
silent: true

41
api/auth.go Normal file
View file

@ -0,0 +1,41 @@
package api
import (
"net/http"
"strings"
"github.com/labstack/echo/v4"
)
func tokenMiddlware(configuredToken string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Ignore token check for swagger URLs
if strings.HasPrefix(c.Request().URL.Path, "/swagger") || c.Request().URL.Path == "/" {
return next(c)
}
// Check for token in the Authorization header
authHeader := c.Request().Header.Get("Authorization")
if authHeader == "" {
return echo.NewHTTPError(http.StatusUnauthorized, "please provide valid token")
}
// The Authorization header should be in the format "Bearer <token>"
parts := strings.Split(authHeader, " ")
if len(parts) == 1 && parts[0] == configuredToken {
return next(c)
}
if len(parts) != 2 || parts[0] != "Bearer" {
return echo.NewHTTPError(http.StatusUnauthorized, "please provide valid token")
}
if parts[1] != configuredToken {
return echo.NewHTTPError(http.StatusUnauthorized, "please provide valid token")
}
return next(c)
}
}
}

113
api/main.go Normal file
View file

@ -0,0 +1,113 @@
package api
import (
"log"
"net/http"
_ "gitea.ceperka.net/rosti/lobby2/docs" // This line is necessary for swag to find your docs!
"gitea.ceperka.net/rosti/lobby2/nodes"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
echoSwagger "github.com/swaggo/echo-swagger"
)
// @title Lobby2 API
// @version 2.0
// @description API of Lobby 2 project that helps to discover and connect to other nodes and their services.
// @BasePath /
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
type API struct {
listen string
token string
np *nodes.NodesProcessor
e *echo.Echo
}
func NewAPI(np *nodes.NodesProcessor, listen string, token string) *API {
return &API{
listen: listen,
token: token,
np: np,
}
}
func (a *API) Run() error {
if a.token == "" {
log.Fatalln("TOKEN is required")
}
a.e = echo.New()
a.e.Use(middleware.Logger())
a.e.Use(tokenMiddlware(a.token))
a.e.GET("/", func(c echo.Context) error {
return c.Redirect(http.StatusTemporaryRedirect, "/swagger/index.html")
})
a.e.GET("/swagger/*", echoSwagger.WrapHandler)
a.e.GET("/nodes", a.listHandler)
a.e.GET("/nodes/:hostname", a.getHandler)
a.e.POST("/nodes/:hostname", a.refreshHandler)
// Start the server in a goroutine so that it doesn't block the signal listening
return a.e.Start(a.listen)
}
// @Summary List of nodes
// @Description List of all discovered nodes and their labels.
// @Produce application/json
// @Success 200 {object} nodes.Nodes "List of nodes"
// @Failure 401 {object} Message "Forbidden access"
// @Security Bearer
// @Router /nodes [get]
func (a *API) listHandler(c echo.Context) error {
return c.JSON(http.StatusOK, a.np.List())
}
// @Summary Get node
// @Description Return one nodes based on given hostname
// @Produce application/json
// @Param hostname path string true "Node hostname"
// @Success 200 {array} nodes.Node "Node details"
// @Failure 401 {object} Message "Forbidden access"
// @Security Bearer
// @Router /nodes/{hostname} [get]
func (a *API) getHandler(c echo.Context) error {
hostname := c.Param("hostname")
node, ok := a.np.Get(hostname)
if !ok {
return echo.NewHTTPError(http.StatusNotFound, "node not found")
}
return c.JSON(http.StatusOK, node)
}
// @Summary Refresh node
// @Description Send new data or update existing data about a node
// @Produce application/json
// @Param hostname path string true "Node hostname"
// @Param labels body nodes.Labels true "Node labels"
// @Param kv body nodes.KV true "Key-value"
// @Success 200 {array} nodes.Node "Node details"
// @Failure 401 {object} Message "Forbidden access"
// @Security Bearer
// @Router /nodes/{hostname} [post]
func (a *API) refreshHandler(c echo.Context) error {
hostname := c.Param("hostname")
params := params{}
err := c.Bind(&params)
if err != nil {
return c.JSON(http.StatusBadRequest, Message{Message: err.Error()})
}
a.np.Refresh(hostname, params.Labels, params.KV)
return c.NoContent(http.StatusNoContent)
}

12
api/types.go Normal file
View file

@ -0,0 +1,12 @@
package api
import "gitea.ceperka.net/rosti/lobby2/nodes"
type Message struct {
Message string `json:"message"`
}
type params struct {
Labels nodes.Labels `json:"labels"`
KV nodes.KV `json:"kv"`
}

54
cli/actions.go Normal file
View file

@ -0,0 +1,54 @@
package main
import (
"encoding/json"
"fmt"
"os"
"gitea.ceperka.net/rosti/lobby2/api"
"gitea.ceperka.net/rosti/lobby2/nodes"
"gitea.ceperka.net/rosti/lobby2/refresher"
"github.com/urfave/cli/v2"
)
func masterAction(c *cli.Context) error {
cfg := GetConfig()
np := nodes.NewNodesProcessor(cfg.DumpPath, cfg.DropAfterSeconds)
go np.DumpLoop()
go np.GarbageCollection()
api := api.NewAPI(np, cfg.APIListen, cfg.APIToken)
api.Run()
return nil
}
func nodeAction(c *cli.Context) error {
cfg := GetConfig()
r := refresher.NewRefresher(cfg.NodeDirPath, cfg.ConfigPath)
r.Loop()
return nil
}
func printAction(c *cli.Context) error {
cfg := GetConfig()
np := nodes.NewNodesProcessor(cfg.DumpPath, cfg.DropAfterSeconds)
nodes := np.List()
for _, node := range nodes {
body, err := json.MarshalIndent(node, "", " ")
if err != nil {
fmt.Printf("failed to marshal node: %v\n", err)
os.Exit(1)
}
fmt.Println(string(body))
}
return nil
}

28
cli/config.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"log"
"github.com/kelseyhightower/envconfig"
)
type Config struct {
APIListen string `envconfig:"LISTEN" default:"0.0.0.0:1352"`
APIToken string `envconfig:"TOKEN" default:""`
DumpPath string `envconfig:"DUMP_PATH" default:"/var/lib/lobby2/nodes.json"`
ConfigPath string `envconfig:"CONFIG_PATH" default:"/var/lib/lobby2/config.json"`
NodeDirPath string `envconfig:"NODE_DIR_PATH" default:"/var/lib/lobby2/node"`
DropAfterSeconds int64 `envconfig:"DROP_AFTER_SECONDS" default:"60"`
}
func GetConfig() Config {
var cfg Config
err := envconfig.Process("", &cfg)
if err != nil {
log.Fatal(err)
}
return cfg
}

37
cli/main.go Normal file
View file

@ -0,0 +1,37 @@
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "",
Usage: "",
Commands: []*cli.Command{
{
Name: "master",
Usage: "Runs master node API",
Action: masterAction,
},
{
Name: "node",
Usage: "Runs node on local machine",
Action: nodeAction,
},
{
Name: "print",
Usage: "Prints all discovered nodes",
Action: printAction,
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}

209
docs/docs.go Normal file
View file

@ -0,0 +1,209 @@
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/nodes": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "List of all discovered nodes and their labels.",
"produces": [
"application/json"
],
"summary": "List of nodes",
"responses": {
"200": {
"description": "List of nodes",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/nodes.Node"
}
}
},
"401": {
"description": "Forbidden access",
"schema": {
"$ref": "#/definitions/api.Message"
}
}
}
}
},
"/nodes/{hostname}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Return one nodes based on given hostname",
"produces": [
"application/json"
],
"summary": "Get node",
"parameters": [
{
"type": "string",
"description": "Node hostname",
"name": "hostname",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Node details",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/nodes.Node"
}
}
},
"401": {
"description": "Forbidden access",
"schema": {
"$ref": "#/definitions/api.Message"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Send new data or update existing data about a node",
"produces": [
"application/json"
],
"summary": "Refresh node",
"parameters": [
{
"type": "string",
"description": "Node hostname",
"name": "hostname",
"in": "path",
"required": true
},
{
"description": "Node labels",
"name": "labels",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
{
"description": "Key-value",
"name": "kv",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/nodes.KV"
}
}
],
"responses": {
"200": {
"description": "Node details",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/nodes.Node"
}
}
},
"401": {
"description": "Forbidden access",
"schema": {
"$ref": "#/definitions/api.Message"
}
}
}
}
}
},
"definitions": {
"api.Message": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"nodes.KV": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"nodes.Node": {
"type": "object",
"properties": {
"hostname": {
"type": "string"
},
"kv": {
"$ref": "#/definitions/nodes.KV"
},
"labels": {
"type": "array",
"items": {
"type": "string"
}
},
"last_update": {
"type": "integer"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "2.0",
Host: "",
BasePath: "/",
Schemes: []string{},
Title: "Lobby2 API",
Description: "API of Lobby 2 project that helps to discover and connect to other nodes and their services.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

185
docs/swagger.json Normal file
View file

@ -0,0 +1,185 @@
{
"swagger": "2.0",
"info": {
"description": "API of Lobby 2 project that helps to discover and connect to other nodes and their services.",
"title": "Lobby2 API",
"contact": {},
"version": "2.0"
},
"basePath": "/",
"paths": {
"/nodes": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "List of all discovered nodes and their labels.",
"produces": [
"application/json"
],
"summary": "List of nodes",
"responses": {
"200": {
"description": "List of nodes",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/nodes.Node"
}
}
},
"401": {
"description": "Forbidden access",
"schema": {
"$ref": "#/definitions/api.Message"
}
}
}
}
},
"/nodes/{hostname}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Return one nodes based on given hostname",
"produces": [
"application/json"
],
"summary": "Get node",
"parameters": [
{
"type": "string",
"description": "Node hostname",
"name": "hostname",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Node details",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/nodes.Node"
}
}
},
"401": {
"description": "Forbidden access",
"schema": {
"$ref": "#/definitions/api.Message"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Send new data or update existing data about a node",
"produces": [
"application/json"
],
"summary": "Refresh node",
"parameters": [
{
"type": "string",
"description": "Node hostname",
"name": "hostname",
"in": "path",
"required": true
},
{
"description": "Node labels",
"name": "labels",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
{
"description": "Key-value",
"name": "kv",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/nodes.KV"
}
}
],
"responses": {
"200": {
"description": "Node details",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/nodes.Node"
}
}
},
"401": {
"description": "Forbidden access",
"schema": {
"$ref": "#/definitions/api.Message"
}
}
}
}
}
},
"definitions": {
"api.Message": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"nodes.KV": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"nodes.Node": {
"type": "object",
"properties": {
"hostname": {
"type": "string"
},
"kv": {
"$ref": "#/definitions/nodes.KV"
},
"labels": {
"type": "array",
"items": {
"type": "string"
}
},
"last_update": {
"type": "integer"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}

119
docs/swagger.yaml Normal file
View file

@ -0,0 +1,119 @@
basePath: /
definitions:
api.Message:
properties:
message:
type: string
type: object
nodes.KV:
additionalProperties:
type: string
type: object
nodes.Node:
properties:
hostname:
type: string
kv:
$ref: '#/definitions/nodes.KV'
labels:
items:
type: string
type: array
last_update:
type: integer
type: object
info:
contact: {}
description: API of Lobby 2 project that helps to discover and connect to other
nodes and their services.
title: Lobby2 API
version: "2.0"
paths:
/nodes:
get:
description: List of all discovered nodes and their labels.
produces:
- application/json
responses:
"200":
description: List of nodes
schema:
items:
$ref: '#/definitions/nodes.Node'
type: array
"401":
description: Forbidden access
schema:
$ref: '#/definitions/api.Message'
security:
- Bearer: []
summary: List of nodes
/nodes/{hostname}:
get:
description: Return one nodes based on given hostname
parameters:
- description: Node hostname
in: path
name: hostname
required: true
type: string
produces:
- application/json
responses:
"200":
description: Node details
schema:
items:
$ref: '#/definitions/nodes.Node'
type: array
"401":
description: Forbidden access
schema:
$ref: '#/definitions/api.Message'
security:
- Bearer: []
summary: Get node
post:
description: Send new data or update existing data about a node
parameters:
- description: Node hostname
in: path
name: hostname
required: true
type: string
- description: Node labels
in: body
name: labels
required: true
schema:
items:
type: string
type: array
- description: Key-value
in: body
name: kv
required: true
schema:
$ref: '#/definitions/nodes.KV'
produces:
- application/json
responses:
"200":
description: Node details
schema:
items:
$ref: '#/definitions/nodes.Node'
type: array
"401":
description: Forbidden access
schema:
$ref: '#/definitions/api.Message'
security:
- Bearer: []
summary: Refresh node
securityDefinitions:
Bearer:
in: header
name: Authorization
type: apiKey
swagger: "2.0"

46
go.mod Normal file
View file

@ -0,0 +1,46 @@
module gitea.ceperka.net/rosti/lobby2
go 1.20
require (
github.com/kelseyhightower/envconfig v1.4.0
github.com/labstack/echo/v4 v4.12.0
github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/stretchr/testify v1.8.4
github.com/swaggo/echo-swagger v1.4.1
github.com/swaggo/swag v1.16.3
github.com/urfave/cli/v2 v2.3.0
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.7.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

111
go.sum Normal file
View file

@ -0,0 +1,111 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

176
nodes/main.go Normal file
View file

@ -0,0 +1,176 @@
package nodes
import (
"encoding/json"
"fmt"
"log"
"os"
"path"
"strings"
"time"
"github.com/puzpuzpuz/xsync/v3"
)
const dumpIntervalSeconds = 120
// Base nodes structure that covers all operations above nodes.
type NodesProcessor struct {
nodes *xsync.MapOf[string, Node]
dumpPath string
garbageCollectAfterSeconds int64
}
func NewNodesProcessor(dumpPath string, garbageCollectAfterSeconds int64) *NodesProcessor {
p := &NodesProcessor{
dumpPath: dumpPath,
garbageCollectAfterSeconds: garbageCollectAfterSeconds,
nodes: xsync.NewMapOf[string, Node](),
}
err := p.Load()
if err != nil {
log.Printf("failed to load nodes: %v\n", err)
}
return p
}
func (np *NodesProcessor) DumpLoop() {
log.Println(".. starting dump loop")
time.Sleep(time.Second * dumpIntervalSeconds)
for {
log.Println(".. dumping nodes")
err := np.Dump()
if err != nil {
log.Printf("failed to dump nodes: %v\n", err)
}
time.Sleep(time.Second * dumpIntervalSeconds)
}
}
// GarbageCollection removes nodes that haven't been updated for more than garbageCollectAfterSeconds.
func (np *NodesProcessor) GarbageCollection() {
for {
np.garbageCollect()
time.Sleep(time.Second * 10)
}
}
func (np *NodesProcessor) garbageCollect() {
np.nodes.Range(func(key string, value Node) bool {
if time.Now().Unix()-value.LastUpdate > np.garbageCollectAfterSeconds {
np.nodes.Delete(value.HostName)
}
return true
})
}
// Dumps content of np.nodes to np.dumpPath.
func (np *NodesProcessor) Dump() error {
ns := []Node{}
np.nodes.Range(func(key string, value Node) bool {
ns = append(ns, value)
return true
})
body, err := json.Marshal(ns)
if err != nil {
return fmt.Errorf("failed to marshal nodes: %w", err)
}
// Create directory if it doesn't exist.
if strings.Contains(np.dumpPath, "/") {
path := np.dumpPath[:strings.LastIndex(np.dumpPath, "/")]
if len(path) != 0 {
err = os.MkdirAll(path, 0755)
if err != nil {
return fmt.Errorf("failed to create directory for nodes.json: %w", err)
}
}
}
err = os.WriteFile(np.dumpPath, body, 0644)
if err != nil {
return fmt.Errorf("failed to write nodes.json: %w", err)
}
return nil
}
// Loads content of np.dumpPath to np.nodes.
func (np *NodesProcessor) Load() error {
filePath := path.Join(np.dumpPath)
_, err := os.Stat(filePath)
if os.IsNotExist(err) {
return nil
}
body, err := os.ReadFile(np.dumpPath)
if err != nil {
return fmt.Errorf("failed to read nodes.json: %w", err)
}
np.Reset()
ns := []Node{}
err = json.Unmarshal(body, &ns)
if err != nil {
return fmt.Errorf("failed to unmarshal nodes: %w", err)
}
for _, n := range ns {
np.nodes.Store(n.HostName, n)
}
return nil
}
func (np *NodesProcessor) Reset() {
np.nodes.Clear()
}
// Returns string with Prometheus metrics
// TODO: finish this
func (np *NodesProcessor) GetMetrics() string {
return ""
}
// List returns all nodes.
func (np *NodesProcessor) List() Nodes {
nodes := Nodes{}
np.nodes.Range(func(key string, value Node) bool {
nodes = append(nodes, value)
return true
})
return nodes
}
// Get returns a node by hostname.
func (np *NodesProcessor) Get(hostname string) (Node, bool) {
node, ok := np.nodes.Load(hostname)
return node, ok
}
// Refresh updates node with hostname and labels. If it doesn't exists it's created.
func (np *NodesProcessor) Refresh(hostname string, labels Labels, kv KV) {
node, ok := np.nodes.Load(hostname)
if !ok {
node = Node{}
}
node.LastUpdate = time.Now().Unix()
node.HostName = hostname
node.Labels = labels
node.KV = kv
np.nodes.Store(hostname, node)
}
// Drop removes node with given hostname.
func (np *NodesProcessor) Drop(hostname string) {
np.nodes.Delete(hostname)
}

92
nodes/main_test.go Normal file
View file

@ -0,0 +1,92 @@
package nodes
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
var np *NodesProcessor
const testDumpFile = "nodes.json"
func TestMain(m *testing.M) {
np = NewNodesProcessor(testDumpFile, 2)
os.Exit(m.Run())
}
func TestNodesProcessor_GarbageCollect(t *testing.T) {
np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
assert.Equal(t, 1, len(np.List()))
time.Sleep(5 * time.Second)
np.garbageCollect()
assert.Equal(t, 0, len(np.List()))
}
func TestNodesProcessor_Refresh(t *testing.T) {
np.Reset()
np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
np.Refresh("test2", Labels{"mylabel3"}, KV{"mykey3": "my value3"})
nodes := np.List()
assert.Equal(t, "test", nodes[0].HostName)
assert.Contains(t, nodes[0].Labels, "mylabel")
assert.Equal(t, "test2", nodes[1].HostName)
assert.Contains(t, nodes[1].Labels, "mylabel3")
}
func TestNodesProcessor_DumpLoad(t *testing.T) {
np.Reset()
np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
np.Refresh("test2", Labels{"mylabel3"}, KV{"mykey3": "my value3"})
os.Remove(testDumpFile)
np.Dump()
np.Reset()
assert.Equal(t, 0, len(np.List()))
np.Load()
assert.Equal(t, 2, len(np.List()))
nodes := np.List()
assert.Len(t, nodes, 2)
}
func TestNodesProcessor_List(t *testing.T) {
np.Reset()
np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
nodes := np.List()
assert.Equal(t, "test", nodes[0].HostName)
assert.Contains(t, nodes[0].Labels, "mylabel")
}
func TestNodesProcessor_Get(t *testing.T) {
np.Reset()
np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
node, _ := np.Get("test")
assert.Contains(t, node.Labels, "mylabel")
}
func TestNodesProcessor_Drop(t *testing.T) {
np.Reset()
np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
node, _ := np.Get("test")
assert.Contains(t, node.Labels, "mylabel")
np.Drop("test")
assert.Zero(t, len(np.List()))
}
// TODO: TestNodesProcessor_GetMetrics

14
nodes/types.go Normal file
View file

@ -0,0 +1,14 @@
package nodes
type KV map[string]string
type Labels []string
type JSONData map[string]interface{}
type Node struct {
LastUpdate int64 `json:"last_update,omitempty"`
HostName string `json:"hostname"`
Labels Labels `json:"labels,omitempty"`
KV KV `json:"kv,omitempty"`
}
type Nodes []Node

178
refresher/main.go Normal file
View file

@ -0,0 +1,178 @@
package refresher
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path"
"time"
"gitea.ceperka.net/rosti/lobby2/nodes"
)
const refreshIntervalSeconds = 15
const nodeFileName = "node.json"
// Refresher loads local node info and sends them to the master node.
type Refresher struct {
configPath string
nodeDirPath string
}
func NewRefresher(nodeDirPath string, configPath string) *Refresher {
return &Refresher{
nodeDirPath: nodeDirPath,
configPath: configPath,
}
}
// Run loop the process that updates node in the master node.
func (r *Refresher) Loop() {
log.Println("Start refresher loop")
for {
log.Println("Refreshing node")
err := r.Refresh()
if err != nil {
log.Printf("failed to refresh node: %v\n", err)
}
time.Sleep(time.Second * refreshIntervalSeconds)
}
}
// Refresh loads labels from the local filesystem and sends them to the master node.
func (r *Refresher) Refresh() error {
// Load config
cfg, err := r.getConfig()
if err != nil {
return err
}
// Load labels
node, err := r.loadNode()
if err != nil {
return err
}
nodeBytes, err := json.Marshal(node)
if err != nil {
return fmt.Errorf("failed to marshal labels: %w", err)
}
// Send labels to master
req, err := http.NewRequest("POST", fmt.Sprintf("%s://%s:%d/nodes/%s", cfg.MasterProto, cfg.MasterHost, cfg.MasterPort, node.HostName), bytes.NewBuffer(nodeBytes))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+cfg.MasterToken)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("unexpected status: %s", resp.Status)
}
return nil
}
// Returns the node config.
func (r *Refresher) getConfig() (NodeConfig, error) {
cfg := NodeConfig{}
content, err := os.ReadFile(r.configPath)
if err != nil {
return cfg, fmt.Errorf("failed to read config file: %w", err)
}
err = json.Unmarshal(content, &cfg)
if err != nil {
return cfg, fmt.Errorf("failed to unmarshal config: %w", err)
}
if cfg.MasterProto == "" {
cfg.MasterProto = "http"
}
if cfg.MasterPort == 0 {
cfg.MasterPort = 1352
}
return cfg, nil
}
// TODO: rewrite this to load Node structure
func (r *Refresher) loadNode() (*nodes.Node, error) {
filePath := path.Join(r.nodeDirPath, nodeFileName)
_, err := os.Stat(filePath)
if os.IsNotExist(err) {
err = r.initNodeFile()
if err != nil {
return nil, err
}
}
content, err := os.ReadFile(path.Join(r.nodeDirPath, nodeFileName))
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
node := &nodes.Node{}
err = json.Unmarshal(content, &node)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal node: %w", err)
}
return node, err
}
func (r *Refresher) initNodeFile() error {
err := r.createNodePath()
if err != nil {
return fmt.Errorf("failed to create node path: %w", err)
}
hostname, err := os.Hostname()
if err != nil {
return fmt.Errorf("failed to get hostname: %w", err)
}
node := &nodes.Node{
HostName: hostname,
}
nodeBytes, err := json.MarshalIndent(node, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal node: %w", err)
}
err = os.WriteFile(path.Join(r.nodeDirPath, nodeFileName), nodeBytes, 0640)
if err != nil {
return fmt.Errorf("failed to write node file: %w", err)
}
return nil
}
func (r *Refresher) createNodePath() error {
_, err := os.Stat(r.nodeDirPath)
if os.IsNotExist(err) {
err = os.MkdirAll(r.nodeDirPath, 0755)
if err != nil {
return fmt.Errorf("failed to create node dir: %w", err)
}
}
return nil
}

8
refresher/types.go Normal file
View file

@ -0,0 +1,8 @@
package refresher
type NodeConfig struct {
MasterHost string `json:"master_host"`
MasterProto string `json:"master_proto"`
MasterToken string `json:"master_token"`
MasterPort int `json:"master_port"`
}