Go client for the API, lobbyctl tool

This commit is contained in:
Adam Štrauch 2021-09-05 01:27:39 +02:00
parent 0276465876
commit 5b7459afb5
Signed by: cx
GPG Key ID: 018304FFA8988F8D
8 changed files with 381 additions and 3 deletions

View File

@ -8,6 +8,6 @@ clean:
.PHONY: build .PHONY: build
build: build:
mkdir -p ./bin mkdir -p ./bin
export CGO_ENABLED=0 export CGO_ENABLED=0 && go build -o ./bin/lobbyd daemon/*.go
go build -o ./bin/lobbyd daemon/*.go export CGO_ENABLED=0 && go build -o ./bin/lobbyctl ctl/*.go

176
client/main.go Normal file
View 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
View 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
View 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
View 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
View File

@ -4,6 +4,7 @@ go 1.16
require ( require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 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/golang/protobuf v1.5.2 // indirect
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/labstack/echo v3.3.10+incompatible github.com/labstack/echo v3.3.10+incompatible
@ -13,6 +14,5 @@ require (
github.com/shirou/gopsutil/v3 v3.21.7 github.com/shirou/gopsutil/v3 v3.21.7
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/valyala/fasttemplate v1.2.1 // indirect 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 google.golang.org/protobuf v1.27.1 // indirect
) )

2
go.sum
View File

@ -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/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 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 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/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.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=

View File

@ -9,3 +9,14 @@ func (l Label) String() string {
// Labels stores multiple Label records // Labels stores multiple Label records
type Labels []Label 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
}