Go client for the API, lobbyctl tool
This commit is contained in:
parent
0276465876
commit
5b7459afb5
4
Makefile
4
Makefile
@ -8,6 +8,6 @@ clean:
|
||||
.PHONY: build
|
||||
build:
|
||||
mkdir -p ./bin
|
||||
export CGO_ENABLED=0
|
||||
go build -o ./bin/lobbyd daemon/*.go
|
||||
export CGO_ENABLED=0 && go build -o ./bin/lobbyd daemon/*.go
|
||||
export CGO_ENABLED=0 && go build -o ./bin/lobbyctl ctl/*.go
|
||||
|
||||
|
176
client/main.go
Normal file
176
client/main.go
Normal file
@ -0,0 +1,176 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/rosti-cz/server_lobby/server"
|
||||
)
|
||||
|
||||
// Encapsulation of Lobby's client code
|
||||
type LobbyClient struct {
|
||||
Proto string
|
||||
Host string
|
||||
Port uint
|
||||
Token string
|
||||
}
|
||||
|
||||
func (l *LobbyClient) init() {
|
||||
if len(l.Proto) == 0 {
|
||||
l.Host = "http"
|
||||
}
|
||||
if len(l.Host) == 0 {
|
||||
l.Host = "localhost"
|
||||
}
|
||||
if l.Port == 0 {
|
||||
l.Port = 1313
|
||||
}
|
||||
}
|
||||
|
||||
// calls the backend API with given method, path and request body and returns status code, response body and error if there is any.
|
||||
// Method can be GET, POST or DELETE.
|
||||
// Path should start with / and it can contain query parameters too.
|
||||
func (l *LobbyClient) call(method, path, body string) (uint, string, error) {
|
||||
client := resty.New().R()
|
||||
|
||||
if len(l.Token) != 0 {
|
||||
client = client.SetHeader("Authorization", fmt.Sprintf("Token %s", l.Token))
|
||||
}
|
||||
|
||||
if strings.ToUpper(method) == "GET" {
|
||||
resp, err := client.Get(fmt.Sprintf("%s://%s:%d%s", l.Proto, l.Host, l.Port, path))
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
return uint(resp.StatusCode()), string(resp.Body()), nil
|
||||
} else if strings.ToUpper(method) == "POST" {
|
||||
resp, err := client.SetBody(body).Post(fmt.Sprintf("%s://%s:%d%s", l.Proto, l.Host, l.Port, path))
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
return uint(resp.StatusCode()), string(resp.Body()), nil
|
||||
} else if strings.ToUpper(method) == "DELETE" {
|
||||
resp, err := client.SetBody(body).Delete(fmt.Sprintf("%s://%s:%d%s", l.Proto, l.Host, l.Port, path))
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
return uint(resp.StatusCode()), string(resp.Body()), nil
|
||||
} else {
|
||||
return 0, "", errors.New("unsupported method")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Returns discovery object of local machine
|
||||
func (l *LobbyClient) GetDiscovery() (server.Discovery, error) {
|
||||
l.init()
|
||||
|
||||
var discovery server.Discovery
|
||||
|
||||
path := "/v1/discovery"
|
||||
method := "GET"
|
||||
|
||||
status, body, err := l.call(method, path, "")
|
||||
if err != nil {
|
||||
return discovery, err
|
||||
}
|
||||
if status != 200 {
|
||||
return discovery, fmt.Errorf("non-200 response: %s", body)
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(body), &discovery)
|
||||
if err != nil {
|
||||
return discovery, fmt.Errorf("response parsing error: %v", err)
|
||||
}
|
||||
|
||||
return discovery, nil
|
||||
}
|
||||
|
||||
// Returns all registered discovery packets
|
||||
func (l *LobbyClient) GetDiscoveries() ([]server.Discovery, error) {
|
||||
l.init()
|
||||
|
||||
path := "/v1/discoveries"
|
||||
method := "GET"
|
||||
|
||||
var discoveries []server.Discovery
|
||||
|
||||
status, body, err := l.call(method, path, "")
|
||||
if err != nil {
|
||||
return discoveries, err
|
||||
}
|
||||
if status != 200 {
|
||||
return discoveries, fmt.Errorf("non-200 response: %s", body)
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(body), &discoveries)
|
||||
if err != nil {
|
||||
return discoveries, fmt.Errorf("response parsing error: %v", err)
|
||||
}
|
||||
|
||||
return discoveries, nil
|
||||
}
|
||||
|
||||
// Find discoveries by their labels
|
||||
func (l *LobbyClient) FindByLabels(labels server.Labels) (server.Discoveries, error) {
|
||||
l.init()
|
||||
|
||||
path := fmt.Sprintf("/v1/discoveries?labels=%s", strings.Join(labels.StringSlice(), ","))
|
||||
method := "GET"
|
||||
|
||||
var discoveries server.Discoveries
|
||||
|
||||
status, body, err := l.call(method, path, "")
|
||||
if err != nil {
|
||||
return discoveries, err
|
||||
}
|
||||
if status != 200 {
|
||||
return discoveries, fmt.Errorf("non-200 response: %s", body)
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(body), &discoveries)
|
||||
if err != nil {
|
||||
return discoveries, fmt.Errorf("response parsing error: %v", err)
|
||||
}
|
||||
|
||||
return discoveries, nil
|
||||
}
|
||||
|
||||
// Adds runtime labels for the local machine
|
||||
func (l *LobbyClient) AddLabels(labels server.Labels) error {
|
||||
l.init()
|
||||
|
||||
path := "/v1/labels"
|
||||
method := "POST"
|
||||
|
||||
status, body, err := l.call(method, path, strings.Join(labels.StringSlice(), "\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != 200 {
|
||||
return fmt.Errorf("non-200 response: %s", body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Removes runtime labels of the local machine
|
||||
func (l *LobbyClient) DeleteLabels(labels server.Labels) error {
|
||||
l.init()
|
||||
|
||||
path := "/v1/labels"
|
||||
method := "DELETE"
|
||||
|
||||
status, body, err := l.call(method, path, strings.Join(labels.StringSlice(), "\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != 200 {
|
||||
return fmt.Errorf("non-200 response: %s", body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
29
ctl/config.go
Normal file
29
ctl/config.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
)
|
||||
|
||||
// Config keeps info about configuration of this daemon
|
||||
type Config struct {
|
||||
Token string `envconfig:"TOKEN" required:"false"` // Authentication token, if empty auth is disabled
|
||||
Proto string `envconfig:"PROTOCOL" required:"false" default:"http"` // selected http or https protocols, default is http
|
||||
Host string `envconfig:"HOST" required:"false" default:"127.0.0.1"` // IP address or hostname where lobbyd is listening
|
||||
Port uint `envconfig:"PORT" required:"false" default:"1313"` // Same thing but the port part
|
||||
}
|
||||
|
||||
// GetConfig return configuration created based on environment variables
|
||||
func GetConfig() *Config {
|
||||
var config Config
|
||||
|
||||
err := envconfig.Process("", &config)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return &config
|
||||
}
|
43
ctl/format.go
Normal file
43
ctl/format.go
Normal file
@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/rosti-cz/server_lobby/server"
|
||||
)
|
||||
|
||||
func printDiscovery(discovery server.Discovery) {
|
||||
fmt.Printf("Hostname:\n %s\n", discovery.Hostname)
|
||||
|
||||
if len(discovery.Labels) > 0 {
|
||||
fmt.Printf("Labels:\n")
|
||||
for _, label := range discovery.Labels {
|
||||
fmt.Printf(" %s\n", label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printDiscoveries(discoveries []server.Discovery) {
|
||||
maxHostnameWidth := 0
|
||||
for _, discovery := range discoveries {
|
||||
if len(discovery.Hostname) > maxHostnameWidth {
|
||||
maxHostnameWidth = len(discovery.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
for _, discovery := range discoveries {
|
||||
if len(discovery.Labels) == 0 {
|
||||
fmt.Println(discovery.Hostname)
|
||||
} else {
|
||||
hostname := fmt.Sprintf("%"+strconv.Itoa(maxHostnameWidth)+"s", discovery.Hostname)
|
||||
fmt.Printf("%s %s\n", hostname, discovery.Labels[0].String())
|
||||
if len(discovery.Labels) > 1 {
|
||||
for _, label := range discovery.Labels[1:] {
|
||||
fmt.Printf("%"+strconv.Itoa(maxHostnameWidth+4)+"s%s\n", " ", label)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
117
ctl/main.go
Normal file
117
ctl/main.go
Normal file
@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rosti-cz/server_lobby/client"
|
||||
"github.com/rosti-cz/server_lobby/server"
|
||||
)
|
||||
|
||||
func Usage() {
|
||||
flag.Usage()
|
||||
fmt.Println("")
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" discovery returns discovery packet of the server where the client is connected to")
|
||||
fmt.Println(" discoveries returns list of all registered discovery packets")
|
||||
fmt.Println(" labels add LABEL [LABEL] ... adds new runtime labels")
|
||||
fmt.Println(" labels del LABEL [LABEL] ... deletes runtime labels")
|
||||
}
|
||||
|
||||
func main() {
|
||||
config := GetConfig()
|
||||
|
||||
// Setup flags
|
||||
proto := flag.String("proto", "", "Select HTTP or HTTPS protocol")
|
||||
host := flag.String("host", "", "Hostname or IP address of lobby daemon")
|
||||
port := flag.Uint("port", 0, "Port of lobby daemon")
|
||||
token := flag.String("token", "", "Token needed to communicate lobby daemon, if empty auth is disabled")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Replace empty values from flags by values from environment variables
|
||||
if *proto == "" {
|
||||
proto = &config.Proto
|
||||
}
|
||||
if *host == "" {
|
||||
host = &config.Host
|
||||
}
|
||||
if *port == 0 {
|
||||
port = &config.Port
|
||||
}
|
||||
if *token == "" {
|
||||
token = &config.Token
|
||||
}
|
||||
|
||||
// Validation
|
||||
if *proto != "http" && *proto != "https" {
|
||||
fmt.Println("Protocol can be only http or https")
|
||||
}
|
||||
|
||||
// Setup lobby client library
|
||||
client := client.LobbyClient{
|
||||
Proto: strings.ToLower(*proto),
|
||||
Host: *host,
|
||||
Port: *port,
|
||||
Token: *token,
|
||||
}
|
||||
|
||||
// Process rest of the arguments
|
||||
if len(flag.Args()) == 0 {
|
||||
Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
switch flag.Args()[0] {
|
||||
case "discoveries":
|
||||
discoveries, err := client.GetDiscoveries()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
printDiscoveries(discoveries)
|
||||
case "discovery":
|
||||
discovery, err := client.GetDiscovery()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
printDiscovery(discovery)
|
||||
case "labels":
|
||||
if len(flag.Args()) < 3 {
|
||||
fmt.Println("ERROR: not enough arguments for labels command")
|
||||
fmt.Println("")
|
||||
Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
labels := server.Labels{}
|
||||
labelsString := flag.Args()[2:]
|
||||
for _, labelString := range labelsString {
|
||||
labels = append(labels, server.Label(labelString))
|
||||
}
|
||||
|
||||
if flag.Args()[1] == "add" {
|
||||
err := client.AddLabels(labels)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
} else if flag.Args()[1] == "del" {
|
||||
err := client.DeleteLabels(labels)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("ERROR: wrong labels subcommand\n\n")
|
||||
Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
default:
|
||||
Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
}
|
2
go.mod
2
go.mod
@ -4,6 +4,7 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/go-resty/resty/v2 v2.6.0
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/labstack/echo v3.3.10+incompatible
|
||||
@ -13,6 +14,5 @@ require (
|
||||
github.com/shirou/gopsutil/v3 v3.21.7
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
)
|
||||
|
2
go.sum
2
go.sum
@ -6,6 +6,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
|
||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
|
@ -9,3 +9,14 @@ func (l Label) String() string {
|
||||
|
||||
// Labels stores multiple Label records
|
||||
type Labels []Label
|
||||
|
||||
// StringSlice return slice of Label as strings
|
||||
func (l *Labels) StringSlice() []string {
|
||||
labelsString := []string{}
|
||||
|
||||
for _, label := range *l {
|
||||
labelsString = append(labelsString, label.String())
|
||||
}
|
||||
|
||||
return labelsString
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user