Skip to content

Commit

Permalink
Merge pull request #178 from gatewayd-io/admin-api
Browse files Browse the repository at this point in the history
Admin API
  • Loading branch information
mostafa authored Mar 5, 2023
2 parents f828d31 + 06f7b08 commit b240db2
Show file tree
Hide file tree
Showing 43 changed files with 2,587 additions and 37 deletions.
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ linters:
- maligned
- funlen
- maintidx
- musttag
23 changes: 16 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,33 @@ tidy:
@go mod tidy

build-dev:
@go mod tidy && CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X ${PACKAGE_NAME}.Version=${VERSION}"
@go mod tidy && CGO_ENABLED=0 go build -tags embed_swagger -trimpath -ldflags "-s -w -X ${PACKAGE_NAME}.Version=${VERSION}"

build-release: tidy
@mkdir -p dist

@echo "Building gatewayd ${VERSION} for linux-amd64"
@mkdir -p dist/linux-amd64
@cp README.md LICENSE gatewayd.yaml gatewayd_plugins.yaml dist/linux-amd64/
@GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w ${EXTRA_LDFLAGS}" -o dist/linux-amd64/gatewayd
@GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -tags embed_swagger -trimpath -ldflags "-s -w ${EXTRA_LDFLAGS}" -o dist/linux-amd64/gatewayd
@tar czf dist/gatewayd-linux-amd64-${VERSION}.tar.gz -C ./dist/linux-amd64/ ${FILES}

@echo "Building gatewayd ${VERSION} for linux-arm64"
@mkdir -p dist/linux-arm64
@cp README.md LICENSE gatewayd.yaml gatewayd_plugins.yaml dist/linux-arm64/
@GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w ${EXTRA_LDFLAGS}" -o dist/linux-arm64/gatewayd
@GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -tags embed_swagger -trimpath -ldflags "-s -w ${EXTRA_LDFLAGS}" -o dist/linux-arm64/gatewayd
@tar czf dist/gatewayd-linux-arm64-${VERSION}.tar.gz -C ./dist/linux-arm64/ ${FILES}

@echo "Building gatewayd ${VERSION} for darwin-amd64"
@mkdir -p dist/darwin-amd64
@cp README.md LICENSE gatewayd.yaml gatewayd_plugins.yaml dist/darwin-amd64/
@GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w ${EXTRA_LDFLAGS}" -o dist/darwin-amd64/gatewayd
@GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -tags embed_swagger -trimpath -ldflags "-s -w ${EXTRA_LDFLAGS}" -o dist/darwin-amd64/gatewayd
@tar czf dist/gatewayd-darwin-amd64-${VERSION}.tar.gz -C ./dist/darwin-amd64/ ${FILES}

@echo "Building gatewayd ${VERSION} for darwin-arm64"
@mkdir -p dist/darwin-arm64
@cp README.md LICENSE gatewayd.yaml gatewayd_plugins.yaml dist/darwin-arm64/
@GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w ${EXTRA_LDFLAGS}" -o dist/darwin-arm64/gatewayd
@GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -tags embed_swagger -trimpath -ldflags "-s -w ${EXTRA_LDFLAGS}" -o dist/darwin-arm64/gatewayd
@tar czf dist/gatewayd-darwin-arm64-${VERSION}.tar.gz -C ./dist/darwin-arm64/ ${FILES}

@echo "Generating checksums"
Expand All @@ -48,10 +48,10 @@ build-release: tidy
@sha256sum dist/gatewayd-darwin-arm64-${VERSION}.tar.gz | sed 's/dist\///g' >> dist/checksums.txt

run: tidy
@go run main.go run
@go run -tags embed_swagger main.go run

run-tracing: tidy
@go run main.go run --tracing
@go run -tags embed_swagger main.go run --tracing

clean:
@go clean -testcache
Expand All @@ -63,3 +63,12 @@ test:
update-all:
@go get -u ./...
@go mod tidy

lint:
@buf lint

gen:
@buf generate

update:
@buf mod update
172 changes: 172 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//go:build !embed

package api

import (
"context"
"encoding/json"

sdkPlugin "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin"
v1 "github.com/gatewayd-io/gatewayd/api/v1"
"github.com/gatewayd-io/gatewayd/config"
"github.com/gatewayd-io/gatewayd/network"
"github.com/gatewayd-io/gatewayd/plugin"
"github.com/gatewayd-io/gatewayd/pool"
"github.com/rs/zerolog"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/structpb"
)

type Options struct {
Logger zerolog.Logger
GRPCNetwork string
GRPCAddress string
HTTPAddress string
}
type API struct {
v1.GatewayDAdminAPIServiceServer

Options *Options

Config *config.Config
PluginRegistry *plugin.Registry
Pools map[string]*pool.Pool
Proxies map[string]*network.Proxy
Servers map[string]*network.Server
}

// Version returns the version information of the GatewayD.
func (a *API) Version(ctx context.Context, _ *emptypb.Empty) (*v1.VersionResponse, error) {
return &v1.VersionResponse{
Version: config.Version,
VersionInfo: config.VersionInfo(),
}, nil
}

// GetGlobalConfig returns the global configuration of the GatewayD.
func (a *API) GetGlobalConfig(ctx context.Context, _ *emptypb.Empty) (*structpb.Struct, error) {
jsonData, err := json.Marshal(a.Config.Global)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to marshal global config: %v", err)
}
var global map[string]interface{}
err = json.Unmarshal(jsonData, &global)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to marshal global config: %v", err)
}

globalConfig, err := structpb.NewStruct(global)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to marshal global config: %v", err)
}
return globalConfig, nil
}

// GetPluginConfig returns the plugin configuration of the GatewayD.
func (a *API) GetPluginConfig(ctx context.Context, _ *emptypb.Empty) (*structpb.Struct, error) {
pluginConfig, err := structpb.NewStruct(a.Config.PluginKoanf.All())
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to marshal plugin config: %v", err)
}
return pluginConfig, nil
}

// GetPlugins returns the active plugin configuration of the GatewayD.
func (a *API) GetPlugins(context.Context, *emptypb.Empty) (*v1.PluginConfigs, error) {
plugins := make([]*v1.PluginConfig, 0)
a.PluginRegistry.ForEach(
func(pluginID sdkPlugin.Identifier, plugIn *plugin.Plugin) {
requires := make(map[string]string, 0)
if plugIn.Requires != nil {
for _, r := range plugIn.Requires {
requires[r.Name] = r.Version
}
}
plugins = append(plugins, &v1.PluginConfig{
Id: &v1.PluginID{
Name: pluginID.Name,
Version: pluginID.Version,
RemoteUrl: pluginID.RemoteURL,
Checksum: pluginID.Checksum,
},
Description: plugIn.Description,
Authors: plugIn.Authors,
License: plugIn.License,
ProjectUrl: plugIn.ProjectURL,
Config: plugIn.Config,
Hooks: plugIn.Hooks,
Requires: requires,
Tags: plugIn.Tags,
Categories: plugIn.Categories,
})
},
)
return &v1.PluginConfigs{
Configs: plugins,
}, nil
}

// GetPools returns the pool configuration of the GatewayD.
func (a *API) GetPools(ctx context.Context, _ *emptypb.Empty) (*structpb.Struct, error) {
pools := make(map[string]interface{}, 0)
for name, p := range a.Pools {
pools[name] = map[string]interface{}{
"cap": p.Cap(),
"size": p.Size(),
}
}
poolsConfig, err := structpb.NewStruct(pools)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to marshal pools config: %v", err)
}
return poolsConfig, nil
}

// GetProxies returns the proxy configuration of the GatewayD.
func (a *API) GetProxies(ctx context.Context, _ *emptypb.Empty) (*structpb.Struct, error) {
proxies := make(map[string]interface{}, 0)
for name, proxy := range a.Proxies {
available := make([]interface{}, 0)
for _, c := range proxy.AvailableConnections() {
available = append(available, c)
}

busy := make([]interface{}, 0)
for _, conn := range proxy.BusyConnections() {
busy = append(busy, conn)
}

proxies[name] = map[string]interface{}{
"available": available,
"busy": busy,
"total": len(available) + len(busy),
}
}
proxiesConfig, err := structpb.NewStruct(proxies)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to marshal proxies config: %v", err)
}
return proxiesConfig, nil
}

// GetServers returns the server configuration of the GatewayD.
func (a *API) GetServers(ctx context.Context, _ *emptypb.Empty) (*structpb.Struct, error) {
servers := make(map[string]interface{}, 0)
for name, server := range a.Servers {
servers[name] = map[string]interface{}{
"network": server.Network,
"address": server.Address,
"status": uint(server.Status),
"softLimit": server.SoftLimit,
"hardLimit": server.HardLimit,
"tickInterval": server.TickInterval.Nanoseconds(),
}
}
serversConfig, err := structpb.NewStruct(servers)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to marshal servers config: %v", err)
}
return serversConfig, nil
}
13 changes: 13 additions & 0 deletions api/embed_swagger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build embed_swagger

package api

import "embed"

//go:embed v1/api.swagger.json
//go:embed v1/swagger-ui
var swaggerUI embed.FS

func IsSwaggerEmbedded() bool {
return true
}
24 changes: 24 additions & 0 deletions api/grpc_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package api

import (
"net"

v1 "github.com/gatewayd-io/gatewayd/api/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)

// StartGRPCAPI starts the gRPC API.
func StartGRPCAPI(api *API) {
listener, err := net.Listen(api.Options.GRPCNetwork, api.Options.GRPCAddress)
if err != nil {
api.Options.Logger.Err(err).Msg("failed to start gRPC API")
}

grpcServer := grpc.NewServer()
reflection.Register(grpcServer)
v1.RegisterGatewayDAdminAPIServiceServer(grpcServer, api)
if err := grpcServer.Serve(listener); err != nil {
api.Options.Logger.Err(err).Msg("failed to start gRPC API")
}
}
67 changes: 67 additions & 0 deletions api/http_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package api

import (
"context"
"io/fs"
"net/http"

v1 "github.com/gatewayd-io/gatewayd/api/v1"
"github.com/gatewayd-io/gatewayd/config"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

// StartHTTPAPI starts the HTTP API.
func StartHTTPAPI(options *Options) {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()

// Register gRPC server endpoint
// TODO: Make this configurable with TLS and Auth.
rmux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
err := v1.RegisterGatewayDAdminAPIServiceHandlerFromEndpoint(
ctx, rmux, options.GRPCAddress, opts)
if err != nil {
options.Logger.Err(err).Msg("failed to start HTTP API")
}

mux := http.NewServeMux()
mux.Handle("/", rmux)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})

mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
if _, err := w.Write([]byte(config.Version)); err != nil {
options.Logger.Err(err).Msg("failed to serve version")
w.WriteHeader(http.StatusInternalServerError)
}
})

if IsSwaggerEmbedded() {
mux.HandleFunc("/swagger.json", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
data, _ := swaggerUI.ReadFile("v1/api.swagger.json")
if _, err := w.Write(data); err != nil {
options.Logger.Err(err).Msg("failed to serve swagger.json")
w.WriteHeader(http.StatusInternalServerError)
}
})

fsys, err := fs.Sub(swaggerUI, "v1/swagger-ui")
if err != nil {
options.Logger.Err(err).Msg("failed to serve swagger-ui")
return
}
mux.Handle("/swagger-ui/", http.StripPrefix("/swagger-ui/", http.FileServer(http.FS(fsys))))
}

// Start HTTP server (and proxy calls to gRPC server endpoint)
if err := http.ListenAndServe(options.HTTPAddress, mux); err != nil { //nolint:gosec
options.Logger.Err(err).Msg("failed to start HTTP API")
}
}
13 changes: 13 additions & 0 deletions api/noembed_swagger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build !embed_swagger

package api

import (
"embed"
)

var swaggerUI embed.FS

func IsSwaggerEmbedded() bool {
return false
}
Loading

0 comments on commit b240db2

Please sign in to comment.