-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #178 from gatewayd-io/admin-api
Admin API
- Loading branch information
Showing
43 changed files
with
2,587 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,4 @@ linters: | |
- maligned | ||
- funlen | ||
- maintidx | ||
- musttag |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.