simplify agents

This commit is contained in:
d34dscene
2025-06-21 19:27:12 +02:00
parent 9c6dfd4a0b
commit 2bfae0c49d
31 changed files with 2728 additions and 3505 deletions

View File

@@ -1,176 +0,0 @@
package client
import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"strings"
"sync"
"connectrpc.com/connect"
"github.com/mizuchilabs/mantrae/pkg/meta"
mantraev1 "github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1"
"github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1/mantraev1connect"
)
const tokenFile = "data/.mantrae-token"
type TokenSource struct {
mu sync.Mutex
client mantraev1connect.AgentServiceClient
token string
fallback bool
}
func NewTokenSource() *TokenSource {
return &TokenSource{fallback: false}
}
// SetToken loads the token from disk or env
func (ts *TokenSource) SetToken(ctx context.Context) error {
ts.mu.Lock()
if ts.token != "" {
ts.mu.Unlock()
return nil
}
// Try to load from disk
data, err := os.ReadFile(tokenFile)
if err == nil {
ts.token = strings.TrimSpace(string(data))
}
// Fallback to env
if ts.token == "" {
ts.token = strings.TrimSpace(os.Getenv("TOKEN"))
}
if ts.token == "" {
ts.mu.Unlock()
return errors.New("no token found in environment or file")
}
// Write it back
_ = os.MkdirAll("data", 0o755)
if err := os.WriteFile(tokenFile, []byte(ts.token), 0o600); err != nil {
slog.Warn("could not write token file", "error", err)
}
ts.mu.Unlock()
return ts.SetClient()
}
// SetClient initializes the client
func (ts *TokenSource) SetClient() error {
ts.mu.Lock()
if ts.token == "" {
ts.mu.Unlock()
return errors.New("no token")
}
claims, err := DecodeJWT(ts.token)
if err != nil {
ts.mu.Unlock()
return err
}
ts.client = mantraev1connect.NewAgentServiceClient(
http.DefaultClient,
claims.ServerURL,
connect.WithInterceptors(ts.Interceptor()),
)
ts.mu.Unlock()
return ts.Refresh(context.Background()) // Check health
}
// Refresh calls HealthCheck and handles token rotation
func (ts *TokenSource) Refresh(ctx context.Context) error {
if ts.client == nil {
return errors.New("no client")
}
req := connect.NewRequest(&mantraev1.HealthCheckRequest{})
req.Header().Set("Authorization", "Bearer "+ts.token)
if claims, err := DecodeJWT(ts.token); err == nil {
req.Header().Set(meta.HeaderAgentID, claims.AgentID)
}
resp, err := ts.client.HealthCheck(ctx, req)
if err != nil {
// Try fallback to env after removing token
if connect.CodeOf(err) == connect.CodeUnauthenticated {
if err := os.Remove(tokenFile); err != nil {
return err
}
if !ts.fallback {
ts.fallback = true
return ts.SetToken(ctx)
}
return errors.New("unauthenticated and no fallback $TOKEN available")
}
return err
}
// Shutdown on agent deletion
if !resp.Msg.Ok {
return errors.New("agent deleted")
}
// Handle token rotation
if newToken := resp.Msg.GetToken(); newToken != "" && newToken != ts.token {
ts.mu.Lock()
ts.token = newToken
ts.fallback = false
_ = os.WriteFile(tokenFile, []byte(newToken), 0o600)
ts.mu.Unlock()
}
return nil
}
// Interceptor injects Authorization header, auto-refreshing on 401.
func (ts *TokenSource) Interceptor() connect.UnaryInterceptorFunc {
return func(next connect.UnaryFunc) connect.UnaryFunc {
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
if err := ts.SetToken(ctx); err != nil {
return nil, connect.NewError(connect.CodeUnauthenticated, err)
}
req.Header().Set("Authorization", "Bearer "+ts.token)
if claims, err := DecodeJWT(ts.token); err == nil {
req.Header().Set(meta.HeaderAgentID, claims.AgentID)
}
resp, err := next(ctx, req)
if connect.CodeOf(err) == connect.CodeUnauthenticated {
ts.mu.Lock()
ts.token = ""
ts.mu.Unlock()
}
return resp, err
}
}
}
func (ts *TokenSource) GetToken() string {
ts.mu.Lock()
defer ts.mu.Unlock()
return ts.token
}
func (ts *TokenSource) GetClient() mantraev1connect.AgentServiceClient {
ts.mu.Lock()
defer ts.mu.Unlock()
return ts.client
}
func (ts *TokenSource) PrintConnection() {
ts.mu.Lock()
defer ts.mu.Unlock()
if ts.client != nil {
claims, err := DecodeJWT(ts.token)
if err == nil {
slog.Info("Connected", "server", claims.ServerURL)
}
}
}

View File

@@ -1,197 +0,0 @@
package client
import (
"context"
"errors"
"log/slog"
"os"
"strconv"
"strings"
"time"
"connectrpc.com/connect"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
mantraev1 "github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1"
"github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1/mantraev1connect"
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
tokenDir = "data"
tokenPath = tokenDir + "/.mantrae-token"
)
func Client(quit chan os.Signal) {
ts := NewTokenSource()
if err := ts.SetToken(context.Background()); err != nil {
slog.Error("Failed to connect to server", "error", err)
return
}
ts.PrintConnection()
// Prepare tickers
healthTicker := time.NewTicker(15 * time.Second)
defer healthTicker.Stop()
containerTicker := time.NewTicker(10 * time.Second)
defer containerTicker.Stop()
for {
select {
case <-healthTicker.C:
if err := ts.Refresh(context.Background()); err != nil {
slog.Error("Failed to refresh token", "error", err)
return
}
case <-containerTicker.C:
doContainer(ts.client, quit)
case <-quit:
slog.Info("Shutting down agent...")
return
}
}
}
// doContainer invokes GetContainer using current token/claims
func doContainer(
client mantraev1connect.AgentServiceClient,
quit chan os.Signal,
) {
// build payload
req := sendContainerRequest()
if req == nil {
return
}
_, err := client.GetContainer(context.Background(), req)
switch connect.CodeOf(err) {
case connect.CodeNotFound:
slog.Warn("Agent deleted by server, shutting down")
quit <- os.Interrupt
case connect.CodeInternal:
slog.Error("GetContainer server error", "error", err)
case connect.CodeUnauthenticated:
slog.Warn("Token invalid, will pick up on next health tick")
default:
if err != nil {
slog.Warn("GetContainer error", "error", err)
}
}
}
// sendContainer creates a GetContainerRequest with information about the local machine
func sendContainerRequest() *connect.Request[mantraev1.GetContainerRequest] {
var request mantraev1.GetContainerRequest
// Get hostname
hostname, err := os.Hostname()
if err != nil {
request.Hostname = "unknown"
}
request.Hostname = hostname
request.PublicIp, err = GetPublicIP()
if err != nil {
slog.Error("Failed to get public IP", "error", err)
}
request.PrivateIps, err = GetPrivateIP()
if err != nil {
slog.Error("Failed to get local IP", "error", err)
}
request.Containers, err = getContainers()
if err != nil {
slog.Error("Failed to get containers", "error", err)
}
request.Updated = timestamppb.New(time.Now())
req := connect.NewRequest(&request)
return req
}
// getContainers retrieves all containers and their info on the local machine
func getContainers() ([]*mantraev1.Container, error) {
// Create a new Docker client
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, errors.New("failed to create Docker client")
}
// Get all containers
containers, err := cli.ContainerList(context.Background(), container.ListOptions{})
if err != nil {
return nil, errors.New("failed to list containers")
}
var result []*mantraev1.Container
portMap := make(map[int32]int32)
// Iterate over each container and populate the Container struct
for _, c := range containers {
// Retrieve the container details
containerJSON, err := cli.ContainerInspect(context.Background(), c.ID)
if err != nil {
slog.Error("Failed to inspect container", "container", c.ID, "error", err)
continue
}
// Skip Traefik
skipTraefik := os.Getenv("SKIP_TRAEFIK")
if skipTraefik == "true" {
if strings.Contains(strings.ToLower(containerJSON.Config.Image), "traefik") ||
(len(c.Names) > 0 && strings.Contains(strings.ToLower(c.Names[0]), "traefik")) {
continue
}
}
// Populate PortInfo
for port, bindings := range containerJSON.NetworkSettings.Ports {
for _, binding := range bindings {
// Get external port
externalPort, err := strconv.ParseInt(binding.HostPort, 10, 32)
if err != nil {
slog.Error(
"Failed to parse external port",
"port",
binding.HostPort,
"error",
err,
)
continue
}
// Get internal port from the port key
internalPort, err := strconv.ParseInt(
port.Port(),
10,
32,
) // port is of type nat.Port
if err != nil {
slog.Error("Failed to parse internal port", "port", port.Port(), "error", err)
continue
}
// Map internal port to external port
portMap[int32(internalPort)] = int32(externalPort)
}
}
created, err := time.Parse(time.RFC3339, containerJSON.Created)
if err != nil {
slog.Error("Failed to parse created time", "time", containerJSON.Created, "error", err)
}
// Populate the Container struct
container := &mantraev1.Container{
Id: c.ID,
Name: c.Names[0], // Take the first name if multiple exist
Labels: containerJSON.Config.Labels,
Image: containerJSON.Config.Image,
Portmap: portMap,
Status: containerJSON.State.Status,
Created: timestamppb.New(created),
}
result = append(result, container)
}
return result, nil
}

View File

@@ -1,111 +0,0 @@
package client
import (
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"strings"
"time"
"golang.org/x/exp/slices"
)
// Public IP APIs
var ipAPIs = []string{
"https://api.ipify.org?format=text",
"https://ifconfig.co/ip",
"https://checkip.amazonaws.com",
"https://ipinfo.io/ip",
}
func getIP(url string) (string, error) {
client := &http.Client{
Timeout: 3 * time.Second,
}
resp, err := client.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", errors.New("non-200 response from API")
}
ip, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(ip), nil
}
func isValidPublicIP(ip string) bool {
if ip == "" {
return false
}
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return false
}
// Check if it's a private or loopback IP
if parsedIP.IsLoopback() || parsedIP.IsPrivate() {
return false
}
return true
}
func GetPublicIP() (string, error) {
for _, api := range ipAPIs {
ip, err := getIP(api)
if err == nil && isValidPublicIP(ip) {
return ip, nil
}
slog.Warn("Failed to query API", "API", api, "Error", err)
}
return "", fmt.Errorf("failed to get public IP")
}
func GetPrivateIP() ([]string, error) {
var ips []string
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
excluded := []string{"lo", "docker", "br-", "veth", "kube", "cni"}
for _, iface := range interfaces {
if slices.ContainsFunc(excluded, func(s string) bool {
return strings.Contains(iface.Name, s)
}) || iface.Flags&net.FlagUp == 0 {
continue
} else {
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
ips = append(ips, ipnet.IP.String())
}
}
}
}
}
if len(ips) == 0 {
return nil, errors.New("no private IP addresses found")
}
return ips, nil
}

View File

@@ -1,26 +0,0 @@
package client
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
AgentID string `json:"agentId,omitempty"`
ProfileID int64 `json:"profileId,omitempty"`
ServerURL string `json:"serverUrl,omitempty"`
jwt.RegisteredClaims
}
func DecodeJWT(tokenString string) (*Claims, error) {
// Decode without verifying
claims := &Claims{}
parser := &jwt.Parser{}
_, _, err := parser.ParseUnverified(tokenString, claims)
if err != nil {
return nil, fmt.Errorf("failed to decode JWT without verification: %w", err)
}
return claims, nil
}

View File

@@ -1,6 +1,7 @@
package main
import (
"context"
"flag"
"fmt"
"log/slog"
@@ -8,7 +9,7 @@ import (
"os/signal"
"syscall"
"github.com/mizuchilabs/mantrae/agent/client"
"github.com/mizuchilabs/mantrae/agent/internal/client"
"github.com/mizuchilabs/mantrae/pkg/build"
"github.com/mizuchilabs/mantrae/pkg/logger"
)
@@ -29,5 +30,10 @@ func main() {
logger.Setup()
slog.Info("Starting agent...")
client.Client(quit)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go client.Client(ctx, quit)
<-ctx.Done()
}

View File

@@ -0,0 +1,188 @@
package client
import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"strings"
"sync"
"connectrpc.com/connect"
"github.com/mizuchilabs/mantrae/agent/internal/collector"
"github.com/mizuchilabs/mantrae/pkg/meta"
"github.com/mizuchilabs/mantrae/pkg/util"
mantraev1 "github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1"
"github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1/mantraev1connect"
)
const tokenFile = "data/.mantrae-token"
type TokenSource struct {
mu sync.Mutex
client mantraev1connect.AgentServiceClient
token string
claims *meta.AgentClaims
activeIP string
fallback bool
}
func NewTokenSource() *TokenSource {
return &TokenSource{fallback: false}
}
// SetToken loads the token from disk or env
func (t *TokenSource) SetToken(ctx context.Context) error {
t.mu.Lock()
if t.token != "" {
t.mu.Unlock()
return nil
}
// Try to load from disk
data, err := os.ReadFile(tokenFile)
if err == nil {
t.token = strings.TrimSpace(string(data))
}
// Fallback to env
if t.token == "" {
t.token = strings.TrimSpace(os.Getenv("TOKEN"))
}
if t.token == "" {
t.mu.Unlock()
return errors.New("no token found in environment or file")
}
// Write it back
_ = os.MkdirAll("data", 0o755)
if err := os.WriteFile(tokenFile, []byte(t.token), 0o600); err != nil {
slog.Warn("could not write token file", "error", err)
}
t.mu.Unlock()
return t.SetClient()
}
// SetClient initializes the client
func (t *TokenSource) SetClient() error {
t.mu.Lock()
if t.token == "" {
t.mu.Unlock()
return errors.New("no token")
}
claims, err := util.DecodeUnsafeJWT[*meta.AgentClaims](t.token)
if err != nil {
t.mu.Unlock()
return err
}
t.claims = claims
t.client = mantraev1connect.NewAgentServiceClient(
http.DefaultClient,
claims.ServerURL,
connect.WithInterceptors(t.Interceptor()),
)
t.mu.Unlock()
return t.Refresh(context.Background()) // Check health
}
// Refresh calls HealthCheck and handles token rotation
func (t *TokenSource) Refresh(ctx context.Context) error {
if t.client == nil {
return errors.New("no client")
}
info := collector.GetMachineInfo()
req := connect.NewRequest(&mantraev1.HealthCheckRequest{
PublicIp: info.PublicIPs.IPv4,
PrivateIp: info.PrivateIPs.IPv4,
})
req.Header().Set("Authorization", "Bearer "+t.token)
req.Header().Set(meta.HeaderAgentID, t.claims.AgentID)
resp, err := t.client.HealthCheck(ctx, req)
if err != nil {
// Try fallback to env after removing token
if connect.CodeOf(err) == connect.CodeUnauthenticated {
if err := os.Remove(tokenFile); err != nil {
return err
}
if !t.fallback {
t.fallback = true
return t.SetToken(ctx)
}
return errors.New("unauthenticated and no fallback $TOKEN available")
}
return err
}
// Shutdown on agent deletion
if resp.Msg.Agent == nil {
return errors.New("agent deleted")
}
// Handle token rotation
newToken := resp.Msg.Agent.Token
if newToken != "" && newToken != t.token {
t.mu.Lock()
t.token = newToken
t.fallback = false
_ = os.WriteFile(tokenFile, []byte(newToken), 0o600)
t.mu.Unlock()
}
// Handle active IP rotation
newIP := resp.Msg.Agent.ActiveIp
if newIP != "" && newIP != t.activeIP {
t.mu.Lock()
t.activeIP = newIP
t.mu.Unlock()
}
return nil
}
// Interceptor injects Authorization header, auto-refreshing on 401.
func (t *TokenSource) Interceptor() connect.UnaryInterceptorFunc {
return func(next connect.UnaryFunc) connect.UnaryFunc {
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
if err := t.SetToken(ctx); err != nil {
return nil, connect.NewError(connect.CodeUnauthenticated, err)
}
req.Header().Set("Authorization", "Bearer "+t.token)
req.Header().Set(meta.HeaderAgentID, t.claims.AgentID)
resp, err := next(ctx, req)
if connect.CodeOf(err) == connect.CodeUnauthenticated {
t.mu.Lock()
t.token = ""
t.mu.Unlock()
}
return resp, err
}
}
}
func (t *TokenSource) GetToken() string {
t.mu.Lock()
defer t.mu.Unlock()
return t.token
}
func (t *TokenSource) GetClient() mantraev1connect.AgentServiceClient {
t.mu.Lock()
defer t.mu.Unlock()
return t.client
}
func (t *TokenSource) PrintConnection() {
t.mu.Lock()
defer t.mu.Unlock()
if t.client != nil {
slog.Info("Connected", "server", t.claims.ServerURL)
}
}

View File

@@ -0,0 +1,260 @@
package client
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"os"
"strconv"
"time"
"connectrpc.com/connect"
"github.com/mizuchilabs/mantrae/agent/internal/collector"
mantraev1 "github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1"
"github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1/mantraev1connect"
"github.com/traefik/paerser/parser"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"google.golang.org/protobuf/types/known/structpb"
)
func Client(ctx context.Context, quit chan os.Signal) {
t := NewTokenSource()
if err := t.SetToken(ctx); err != nil {
slog.Error("Failed to connect to server", "error", err)
return
}
t.PrintConnection()
// Prepare tickers
healthTicker := time.NewTicker(15 * time.Second)
defer healthTicker.Stop()
containerTicker := time.NewTicker(10 * time.Second)
defer containerTicker.Stop()
for {
select {
case <-healthTicker.C:
if err := t.Refresh(ctx); err != nil {
slog.Error("Failed to refresh token", "error", err)
return
}
case <-containerTicker.C:
t.Update(ctx)
case <-quit:
slog.Info("Shutting down agent...")
return
}
}
}
func (t *TokenSource) Update(ctx context.Context) error {
containers, err := collector.GetContainers()
if err != nil {
return err
}
routerClient := mantraev1connect.NewRouterServiceClient(
http.DefaultClient,
t.claims.ServerURL,
connect.WithInterceptors(t.Interceptor()),
)
serviceClient := mantraev1connect.NewServiceServiceClient(
http.DefaultClient,
t.claims.ServerURL,
connect.WithInterceptors(t.Interceptor()),
)
middlewareClient := mantraev1connect.NewMiddlewareServiceClient(
http.DefaultClient,
t.claims.ServerURL,
connect.WithInterceptors(t.Interceptor()),
)
for _, container := range containers {
dynConfig := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{},
TCP: &dynamic.TCPConfiguration{},
UDP: &dynamic.UDPConfiguration{},
TLS: &dynamic.TLSConfiguration{},
}
err := parser.Decode(
container.Labels,
dynConfig,
parser.DefaultRootName,
"traefik.http",
"traefik.tcp",
"traefik.udp",
"traefik.tls.stores.default",
)
if err != nil {
return err
}
// Use the first public port
publicPort := container.Ports[0].PublicPort
// Routers ------------------------------------------------------------
for name, config := range dynConfig.HTTP.Routers {
wrapped, err := ToProtoStruct(config)
if err != nil {
return err
}
req := connect.NewRequest(&mantraev1.CreateRouterRequest{
Name: name,
Config: wrapped,
ProfileId: t.claims.ProfileID,
AgentId: t.claims.AgentID,
Type: mantraev1.RouterType_ROUTER_TYPE_HTTP,
})
if _, err := routerClient.CreateRouter(ctx, req); err != nil {
return err
}
}
for name, config := range dynConfig.TCP.Routers {
wrapped, err := ToProtoStruct(config)
if err != nil {
return err
}
req := connect.NewRequest(&mantraev1.CreateRouterRequest{
Name: name,
Config: wrapped,
ProfileId: t.claims.ProfileID,
AgentId: t.claims.AgentID,
Type: mantraev1.RouterType_ROUTER_TYPE_TCP,
})
if _, err := routerClient.CreateRouter(ctx, req); err != nil {
return err
}
}
for name, config := range dynConfig.UDP.Routers {
wrapped, err := ToProtoStruct(config)
if err != nil {
return err
}
req := connect.NewRequest(&mantraev1.CreateRouterRequest{
Name: name,
Config: wrapped,
ProfileId: t.claims.ProfileID,
AgentId: t.claims.AgentID,
Type: mantraev1.RouterType_ROUTER_TYPE_UDP,
})
if _, err := routerClient.CreateRouter(ctx, req); err != nil {
return err
}
}
// Services -----------------------------------------------------------
for name, config := range dynConfig.HTTP.Services {
config.LoadBalancer.Servers = []dynamic.Server{{
URL: t.activeIP,
Port: strconv.Itoa(int(publicPort)),
}}
wrapped, err := ToProtoStruct(config)
if err != nil {
return err
}
req := connect.NewRequest(&mantraev1.CreateServiceRequest{
Name: name,
Config: wrapped,
ProfileId: t.claims.ProfileID,
AgentId: t.claims.AgentID,
Type: mantraev1.ServiceType_SERVICE_TYPE_HTTP,
})
if _, err := serviceClient.CreateService(ctx, req); err != nil {
return err
}
}
for name, config := range dynConfig.TCP.Services {
config.LoadBalancer.Servers = []dynamic.TCPServer{{
Address: t.activeIP,
Port: strconv.Itoa(int(publicPort)),
}}
wrapped, err := ToProtoStruct(config)
if err != nil {
return err
}
req := connect.NewRequest(&mantraev1.CreateServiceRequest{
Name: name,
Config: wrapped,
ProfileId: t.claims.ProfileID,
AgentId: t.claims.AgentID,
Type: mantraev1.ServiceType_SERVICE_TYPE_TCP,
})
if _, err := serviceClient.CreateService(ctx, req); err != nil {
return err
}
}
for name, config := range dynConfig.UDP.Services {
config.LoadBalancer.Servers = []dynamic.UDPServer{{
Address: t.activeIP,
Port: strconv.Itoa(int(publicPort)),
}}
wrapped, err := ToProtoStruct(config)
if err != nil {
return err
}
req := connect.NewRequest(&mantraev1.CreateServiceRequest{
Name: name,
Config: wrapped,
ProfileId: t.claims.ProfileID,
AgentId: t.claims.AgentID,
Type: mantraev1.ServiceType_SERVICE_TYPE_UDP,
})
if _, err := serviceClient.CreateService(ctx, req); err != nil {
return err
}
}
// Middlewares --------------------------------------------------------
for name, config := range dynConfig.HTTP.Middlewares {
wrapped, err := ToProtoStruct(config)
if err != nil {
return err
}
req := connect.NewRequest(&mantraev1.CreateMiddlewareRequest{
Name: name,
Config: wrapped,
ProfileId: t.claims.ProfileID,
AgentId: t.claims.AgentID,
Type: mantraev1.MiddlewareType_MIDDLEWARE_TYPE_HTTP,
})
if _, err := middlewareClient.CreateMiddleware(ctx, req); err != nil {
return err
}
}
for name, config := range dynConfig.TCP.Middlewares {
wrapped, err := ToProtoStruct(config)
if err != nil {
return err
}
req := connect.NewRequest(&mantraev1.CreateMiddlewareRequest{
Name: name,
Config: wrapped,
ProfileId: t.claims.ProfileID,
AgentId: t.claims.AgentID,
Type: mantraev1.MiddlewareType_MIDDLEWARE_TYPE_TCP,
})
if _, err := middlewareClient.CreateMiddleware(ctx, req); err != nil {
return err
}
}
}
return nil
}
// ToProtoStruct converts any Go struct to *structpb.Struct
func ToProtoStruct(v any) (*structpb.Struct, error) {
data, err := json.Marshal(v)
if err != nil {
return nil, err
}
var mapData map[string]interface{}
if err := json.Unmarshal(data, &mapData); err != nil {
return nil, err
}
return structpb.NewStruct(mapData)
}

View File

@@ -0,0 +1,48 @@
package collector
import (
"context"
"errors"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
// GetContainers retrieves all local containers
func GetContainers() ([]types.Container, error) {
// Create a new Docker client
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, errors.New("failed to create Docker client")
}
// Get all containers
containers, err := cli.ContainerList(context.Background(), container.ListOptions{})
if err != nil {
return nil, errors.New("failed to list containers")
}
var result []types.Container
// portMap := make(map[int32]int32)
// Iterate over each container and populate the Container struct
for _, c := range containers {
// Skip Traefik
for _, name := range c.Names {
if strings.Contains(strings.ToLower(name), "traefik") {
continue
}
}
// Populate PortInfo
// for _, p := range c.Ports {
// fmt.Printf("%s:%d -> %d/%s\n", p.IP, p.PublicPort, p.PrivatePort, p.Type)
// // portMap[int32(p.PublicPort)] = int32(p.PrivatePort)
// }
result = append(result, c)
}
return result, nil
}

View File

@@ -0,0 +1,50 @@
package collector
import (
"log/slog"
"os"
"github.com/mizuchilabs/mantrae/pkg/util"
)
type Machine struct {
MachineID string
Hostname string
PrivateIPs util.IPAddresses
PublicIPs util.IPAddresses
}
// GetMachineInfo retrieves information about the local machine
func GetMachineInfo() *Machine {
var result Machine
var err error
id, err := os.ReadFile("/etc/machine-id")
if err != nil {
id, err = os.ReadFile("/var/lib/dbus/machine-id")
if err != nil {
slog.Error("Failed to read machine ID", "error", err)
}
}
if len(id) > 0 {
result.MachineID = string(id)
}
result.Hostname, err = os.Hostname()
if err != nil {
result.Hostname = "unknown"
slog.Error("Failed to get hostname", "error", err)
}
result.PrivateIPs, err = util.GetPrivateIPs()
if err != nil {
slog.Error("Failed to get local IP", "error", err)
}
result.PublicIPs, err = util.GetPublicIPs()
if err != nil {
slog.Error("Failed to get public IP", "error", err)
}
return &result
}

6
buf.lock Normal file
View File

@@ -0,0 +1,6 @@
# Generated by buf. DO NOT EDIT.
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: 9f2d3c737feb481a83375159c0733275
digest: b5:19d3b83f7df2d284ff5935f4622d7f27e7464a93c210edb536e92a52bcc69b2a18da1312e96b5461601eba7b3764d5e90321bd62e6966870e7dbc2e4dedd98d6

View File

@@ -152,7 +152,6 @@ func (s *Server) registerServices() {
mantraev1connect.SettingServiceName,
mantraev1connect.DnsProviderServiceName,
mantraev1connect.AgentServiceName,
mantraev1connect.AgentManagementServiceName,
mantraev1connect.RouterServiceName,
mantraev1connect.ServiceServiceName,
mantraev1connect.MiddlewareServiceName,
@@ -205,10 +204,6 @@ func (s *Server) registerServices() {
service.NewAgentService(s.app),
opts...,
))
s.mux.Handle(mantraev1connect.NewAgentManagementServiceHandler(
service.NewAgentManagementService(s.app),
opts...,
))
s.mux.Handle(mantraev1connect.NewRouterServiceHandler(
service.NewRouterService(s.app),
opts...,

View File

@@ -9,16 +9,13 @@ import (
"connectrpc.com/connect"
"github.com/golang-jwt/jwt/v5"
"github.com/mizuchilabs/mantrae/internal/api/middlewares"
"github.com/google/uuid"
"github.com/mizuchilabs/mantrae/internal/config"
"github.com/mizuchilabs/mantrae/internal/settings"
"github.com/mizuchilabs/mantrae/internal/store/db"
"github.com/mizuchilabs/mantrae/internal/store/schema"
"github.com/mizuchilabs/mantrae/internal/util"
"github.com/mizuchilabs/mantrae/pkg/meta"
"github.com/mizuchilabs/mantrae/pkg/util"
mantraev1 "github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1"
"github.com/traefik/paerser/parser"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
)
type AgentService struct {
@@ -29,88 +26,291 @@ func NewAgentService(app *config.App) *AgentService {
return &AgentService{app: app}
}
func (s *AgentService) GetAgent(
ctx context.Context,
req *connect.Request[mantraev1.GetAgentRequest],
) (*connect.Response[mantraev1.GetAgentResponse], error) {
agent, err := s.app.Conn.GetQuery().GetAgent(ctx, req.Msg.Id)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// var containers []*mantraev1.Container
// err = json.Unmarshal(containers, &agent.Containers)
// if err != nil {
// return nil, connect.NewError(connect.CodeInternal, err)
// }
return connect.NewResponse(&mantraev1.GetAgentResponse{
Agent: &mantraev1.Agent{
Id: agent.ID,
ProfileId: agent.ProfileID,
Hostname: SafeString(agent.Hostname),
PublicIp: SafeString(agent.PublicIp),
PrivateIp: SafeString(agent.PrivateIp),
ActiveIp: SafeString(agent.ActiveIp),
Token: agent.Token,
// Containers: containers,
CreatedAt: SafeTimestamp(agent.CreatedAt),
UpdatedAt: SafeTimestamp(agent.UpdatedAt),
},
}), nil
}
func (s *AgentService) CreateAgent(
ctx context.Context,
req *connect.Request[mantraev1.CreateAgentRequest],
) (*connect.Response[mantraev1.CreateAgentResponse], error) {
if req.Msg.ProfileId == 0 {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("profile id is required"),
)
}
id, err := uuid.NewV7()
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
serverUrl, err := s.app.Conn.GetQuery().GetSetting(ctx, settings.KeyServerURL)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
if serverUrl.Value == "" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("server url is required, check your settings"),
)
}
token, err := s.createToken(ctx, id.String())
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
agent, err := s.app.Conn.GetQuery().CreateAgent(ctx, db.CreateAgentParams{
ID: id.String(),
ProfileID: req.Msg.ProfileId,
Token: *token,
})
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.CreateAgentResponse{
Agent: &mantraev1.Agent{
Id: agent.ID,
ProfileId: agent.ProfileID,
Token: agent.Token,
},
}), nil
}
func (s *AgentService) UpdateAgentIP(
ctx context.Context,
req *connect.Request[mantraev1.UpdateAgentIPRequest],
) (*connect.Response[mantraev1.UpdateAgentIPResponse], error) {
if err := s.app.Conn.GetQuery().UpdateAgentIP(ctx, db.UpdateAgentIPParams{
ID: req.Msg.Id,
ActiveIp: &req.Msg.Ip,
}); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
agent, err := s.app.Conn.GetQuery().GetAgent(ctx, req.Msg.Id)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.UpdateAgentIPResponse{
Agent: &mantraev1.Agent{
Id: agent.ID,
ProfileId: agent.ProfileID,
Hostname: SafeString(agent.Hostname),
PublicIp: SafeString(agent.PublicIp),
PrivateIp: SafeString(agent.PrivateIp),
ActiveIp: SafeString(agent.ActiveIp),
Token: agent.Token,
CreatedAt: SafeTimestamp(agent.CreatedAt),
UpdatedAt: SafeTimestamp(agent.UpdatedAt),
},
}), nil
}
func (s *AgentService) DeleteAgent(
ctx context.Context,
req *connect.Request[mantraev1.DeleteAgentRequest],
) (*connect.Response[mantraev1.DeleteAgentResponse], error) {
if err := s.app.Conn.GetQuery().DeleteAgent(ctx, req.Msg.Id); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.DeleteAgentResponse{}), nil
}
func (s *AgentService) ListAgents(
ctx context.Context,
req *connect.Request[mantraev1.ListAgentsRequest],
) (*connect.Response[mantraev1.ListAgentsResponse], error) {
if req.Msg.ProfileId == 0 {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("profile id is required"),
)
}
var params db.ListAgentsParams
params.ProfileID = req.Msg.ProfileId
if req.Msg.Limit == nil {
params.Limit = 100
} else {
params.Limit = *req.Msg.Limit
}
if req.Msg.Offset == nil {
params.Offset = 0
} else {
params.Offset = *req.Msg.Offset
}
dbAgents, err := s.app.Conn.GetQuery().ListAgents(ctx, params)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
totalCount, err := s.app.Conn.GetQuery().CountAgents(ctx)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
var agents []*mantraev1.Agent
for _, agent := range dbAgents {
agents = append(agents, &mantraev1.Agent{
Id: agent.ID,
ProfileId: agent.ProfileID,
Hostname: SafeString(agent.Hostname),
PublicIp: SafeString(agent.PublicIp),
PrivateIp: SafeString(agent.PrivateIp),
ActiveIp: SafeString(agent.ActiveIp),
Token: agent.Token,
CreatedAt: SafeTimestamp(agent.CreatedAt),
UpdatedAt: SafeTimestamp(agent.UpdatedAt),
})
}
return connect.NewResponse(&mantraev1.ListAgentsResponse{
Agents: agents,
TotalCount: totalCount,
}), nil
}
func (s *AgentService) HealthCheck(
ctx context.Context,
req *connect.Request[mantraev1.HealthCheckRequest],
) (*connect.Response[mantraev1.HealthCheckResponse], error) {
// Rotate Token
token, err := s.updateToken(ctx, req.Header().Get(meta.HeaderAgentID))
agent, err := s.app.Conn.GetQuery().GetAgent(ctx, req.Header().Get(meta.HeaderAgentID))
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
util.Broadcast <- util.EventMessage{
Type: util.EventTypeUpdate,
Category: util.EventCategoryAgent,
// Rotate Token if it's close to expiring
if _, err := s.updateToken(ctx, &agent); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.HealthCheckResponse{Ok: true, Token: *token}), nil
// Update Agent
var params db.UpdateAgentParams
params.ID = agent.ID
if req.Msg.PublicIp != "" {
params.PublicIp = &req.Msg.PublicIp
}
if req.Msg.PrivateIp != "" {
params.PrivateIp = &req.Msg.PrivateIp
}
agentNew, err := s.app.Conn.GetQuery().UpdateAgent(ctx, params)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.HealthCheckResponse{Agent: &mantraev1.Agent{
Id: agentNew.ID,
ProfileId: agentNew.ProfileID,
ActiveIp: SafeString(agentNew.ActiveIp),
Token: agentNew.Token,
}}), nil
}
func (s *AgentService) GetContainer(
func (s *AgentService) RotateAgentToken(
ctx context.Context,
req *connect.Request[mantraev1.GetContainerRequest],
) (*connect.Response[mantraev1.GetContainerResponse], error) {
agentID, ok := middlewares.GetAgentIDFromContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeInternal, errors.New("agent context missing"))
}
// Upsert agent
params := db.UpdateAgentParams{
ID: agentID,
Hostname: &req.Msg.Hostname,
PublicIp: &req.Msg.PublicIp,
}
// if agent.ActiveIp == nil {
// params.ActiveIp = &req.Msg.PublicIp
// }
privateIPs := schema.AgentPrivateIPs{
IPs: make([]string, len(req.Msg.PrivateIps)),
}
privateIPs.IPs = req.Msg.PrivateIps
params.PrivateIps = &privateIPs
var containers schema.AgentContainers
for _, container := range req.Msg.Containers {
created := container.Created.AsTime()
containers = append(containers, schema.AgentContainer{
ID: container.Id,
Name: container.Name,
Labels: container.Labels,
Image: container.Image,
Portmap: container.Portmap,
Status: container.Status,
Created: &created,
})
}
params.Containers = &containers
q := s.app.Conn.GetQuery()
updatedAgent, err := q.UpdateAgent(ctx, params)
req *connect.Request[mantraev1.RotateAgentTokenRequest],
) (*connect.Response[mantraev1.RotateAgentTokenResponse], error) {
agent, err := s.app.Conn.GetQuery().GetAgent(ctx, req.Msg.Id)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// Update dynamic config
if err = s.DecodeAgentConfig(updatedAgent); err != nil {
_, err = s.updateToken(ctx, &agent)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
util.Broadcast <- util.EventMessage{
Type: util.EventTypeUpdate,
Category: util.EventCategoryAgent,
}
return connect.NewResponse(&mantraev1.GetContainerResponse{}), nil
return connect.NewResponse(&mantraev1.RotateAgentTokenResponse{}), nil
}
func (s *AgentService) updateToken(ctx context.Context, id string) (*string, error) {
q := s.app.Conn.GetQuery()
agent, err := q.GetAgent(ctx, id)
if err != nil {
return nil, err
func (s *AgentService) BootstrapAgent(
ctx context.Context,
req *connect.Request[mantraev1.BootstrapAgentRequest],
) (*connect.Response[mantraev1.BootstrapAgentResponse], error) {
enabled, ok := s.app.SM.Get(settings.KeyAgentBootstrapEnabled)
if !ok {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("failed to get agent bootstrap enabled setting"),
)
}
if enabled != "true" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("agent bootstrap is disabled, check your settings"),
)
}
if req.Msg.Token == "" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("token is required"),
)
}
claims, err := DecodeJWT(agent.Token, s.app.Secret)
// Check if token is valid
bootstrapToken, ok := s.app.SM.Get(settings.KeyAgentBootstrapToken)
if !ok {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("failed to get agent bootstrap token setting"),
)
}
if bootstrapToken != req.Msg.Token {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("invalid token"),
)
}
// Token is valid, create agent
agent, err := s.app.Conn.GetQuery().CreateAgent(ctx, db.CreateAgentParams{
ProfileID: req.Msg.ProfileId,
})
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
token, err := s.updateToken(ctx, &agent)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.BootstrapAgentResponse{Token: *token}), nil
}
func (s *AgentService) updateToken(ctx context.Context, agent *db.Agent) (*string, error) {
claims, err := util.DecodeJWT[*meta.AgentClaims](agent.Token, s.app.Secret)
if err != nil {
return nil, err
}
@@ -119,7 +319,34 @@ func (s *AgentService) updateToken(ctx context.Context, id string) (*string, err
lifetime := claims.ExpiresAt.Sub(claims.IssuedAt.Time)
remaining := time.Until(claims.ExpiresAt.Time)
if remaining > lifetime/4 {
return &agent.Token, nil // Still valid
return &agent.Token, nil // Token is still valid
}
token, err := s.createToken(ctx, agent.ID)
if err != nil {
return nil, err
}
if err = s.app.Conn.GetQuery().UpdateAgentToken(ctx, db.UpdateAgentTokenParams{
ID: agent.ID,
Token: *token,
}); err != nil {
return nil, err
}
slog.Info("Rotating agent token", "agentID", agent.ID, "token", token)
return token, nil
}
func (s *AgentService) createToken(ctx context.Context, id string) (*string, error) {
agent, err := s.app.Conn.GetQuery().GetAgent(ctx, id)
if err != nil {
return nil, errors.New("agent not found")
}
serverUrl, ok := s.app.SM.Get(settings.KeyServerURL)
if !ok {
return nil, errors.New("failed to get server url setting")
}
agentInterval, ok := s.app.SM.Get(settings.KeyAgentCleanupInterval)
@@ -127,161 +354,19 @@ func (s *AgentService) updateToken(ctx context.Context, id string) (*string, err
return nil, errors.New("failed to get agent cleanup interval setting")
}
token, err := claims.EncodeJWT(s.app.Secret, settings.AsDuration(agentInterval))
if err != nil {
return nil, err
}
err = q.UpdateAgentToken(ctx, db.UpdateAgentTokenParams{ID: agent.ID, Token: token})
if err != nil {
return nil, err
}
slog.Info("Rotating agent token", "agentID", agent.ID, "token", token)
return &token, nil
}
// Helpers --------------------------------------------------------------------
type AgentClaims struct {
AgentID string `json:"agentId,omitempty"`
ProfileID int64 `json:"profileId,omitempty"`
ServerURL string `json:"serverUrl,omitempty"`
jwt.RegisteredClaims
}
// EncodeJWT generates a JWT for agents
func (a *AgentClaims) EncodeJWT(secret string, expirationTime time.Duration) (string, error) {
if a.ServerURL == "" || a.ProfileID == 0 {
return "", errors.New("serverUrl and profileID cannot be empty")
}
if expirationTime == 0 {
expirationTime = time.Hour * 24
}
claims := &AgentClaims{
AgentID: a.AgentID,
ProfileID: a.ProfileID,
ServerURL: a.ServerURL,
claims := &meta.AgentClaims{
AgentID: agent.ID,
ProfileID: agent.ProfileID,
ServerURL: serverUrl,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expirationTime)),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(settings.AsDuration(agentInterval))),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}
// DecodeJWT decodes the agent token and returns claims if valid
func DecodeJWT(tokenString, secret string) (*AgentClaims, error) {
claims := &AgentClaims{}
token, err := jwt.ParseWithClaims(
tokenString,
claims,
func(token *jwt.Token) (any, error) {
return []byte(secret), nil
},
)
if err != nil || !token.Valid {
token, err := util.EncodeJWT[*meta.AgentClaims](claims, s.app.Secret)
if err != nil {
return nil, err
}
return claims, nil
}
func (s *AgentService) DecodeAgentConfig(agent db.Agent) error {
ctx := context.Background()
q := s.app.Conn.GetQuery()
for _, container := range *agent.Containers {
dynConfig := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{},
TCP: &dynamic.TCPConfiguration{},
UDP: &dynamic.UDPConfiguration{},
TLS: &dynamic.TLSConfiguration{},
}
err := parser.Decode(
container.Labels,
dynConfig,
parser.DefaultRootName,
"traefik.http",
"traefik.tcp",
"traefik.udp",
"traefik.tls.stores.default",
)
if err != nil {
return err
}
for k, r := range dynConfig.HTTP.Routers {
q.CreateHttpRouter(ctx, db.CreateHttpRouterParams{
ProfileID: agent.ProfileID,
AgentID: &agent.ID,
Name: k,
Config: schema.WrapRouter(r),
})
}
for k, r := range dynConfig.TCP.Routers {
q.CreateTcpRouter(ctx, db.CreateTcpRouterParams{
ProfileID: agent.ProfileID,
AgentID: &agent.ID,
Name: k,
Config: schema.WrapTCPRouter(r),
})
}
for k, r := range dynConfig.UDP.Routers {
q.CreateUdpRouter(ctx, db.CreateUdpRouterParams{
ProfileID: agent.ProfileID,
AgentID: &agent.ID,
Name: k,
Config: schema.WrapUDPRouter(r),
})
}
for k, r := range dynConfig.HTTP.Services {
q.CreateHttpService(ctx, db.CreateHttpServiceParams{
ProfileID: agent.ProfileID,
AgentID: &agent.ID,
Name: k,
Config: schema.WrapService(r),
})
}
for k, r := range dynConfig.TCP.Services {
q.CreateTcpService(ctx, db.CreateTcpServiceParams{
ProfileID: agent.ProfileID,
AgentID: &agent.ID,
Name: k,
Config: schema.WrapTCPService(r),
})
}
for k, r := range dynConfig.UDP.Services {
q.CreateUdpService(ctx, db.CreateUdpServiceParams{
ProfileID: agent.ProfileID,
AgentID: &agent.ID,
Name: k,
Config: schema.WrapUDPService(r),
})
}
for k, r := range dynConfig.HTTP.Middlewares {
q.CreateHttpMiddleware(ctx, db.CreateHttpMiddlewareParams{
ProfileID: agent.ProfileID,
AgentID: &agent.ID,
Name: k,
Config: schema.WrapMiddleware(r),
})
}
for k, r := range dynConfig.TCP.Middlewares {
q.CreateTcpMiddleware(ctx, db.CreateTcpMiddlewareParams{
ProfileID: agent.ProfileID,
AgentID: &agent.ID,
Name: k,
Config: schema.WrapTCPMiddleware(r),
})
}
}
return nil
return &token, nil
}

View File

@@ -1,318 +0,0 @@
package service
import (
"context"
"errors"
"time"
"connectrpc.com/connect"
"github.com/google/uuid"
"github.com/mizuchilabs/mantrae/internal/config"
"github.com/mizuchilabs/mantrae/internal/settings"
"github.com/mizuchilabs/mantrae/internal/store/db"
mantraev1 "github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1"
)
type AgentManagementService struct {
app *config.App
}
func NewAgentManagementService(app *config.App) *AgentManagementService {
return &AgentManagementService{app: app}
}
func (s *AgentManagementService) GetAgent(
ctx context.Context,
req *connect.Request[mantraev1.GetAgentRequest],
) (*connect.Response[mantraev1.GetAgentResponse], error) {
agent, err := s.app.Conn.GetQuery().GetAgent(ctx, req.Msg.Id)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// var containers []*mantraev1.Container
// err = json.Unmarshal(containers, &agent.Containers)
// if err != nil {
// return nil, connect.NewError(connect.CodeInternal, err)
// }
return connect.NewResponse(&mantraev1.GetAgentResponse{
Agent: &mantraev1.Agent{
Id: agent.ID,
ProfileId: agent.ProfileID,
Hostname: SafeString(agent.Hostname),
PublicIp: SafeString(agent.PublicIp),
ActiveIp: SafeString(agent.ActiveIp),
Token: agent.Token,
PrivateIps: agent.PrivateIps.IPs,
// Containers: containers,
CreatedAt: SafeTimestamp(agent.CreatedAt),
UpdatedAt: SafeTimestamp(agent.UpdatedAt),
},
}), nil
}
func (s *AgentManagementService) CreateAgent(
ctx context.Context,
req *connect.Request[mantraev1.CreateAgentRequest],
) (*connect.Response[mantraev1.CreateAgentResponse], error) {
if req.Msg.ProfileId == 0 {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("profile id is required"),
)
}
id, err := uuid.NewV7()
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
serverUrl, err := s.app.Conn.GetQuery().GetSetting(ctx, settings.KeyServerURL)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
if serverUrl.Value == "" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("server url is required, check your settings"),
)
}
claims := &AgentClaims{
AgentID: id.String(),
ProfileID: req.Msg.ProfileId,
ServerURL: serverUrl.Value,
}
token, err := claims.EncodeJWT(s.app.Secret, time.Hour*72)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
agent, err := s.app.Conn.GetQuery().CreateAgent(ctx, db.CreateAgentParams{
ID: claims.AgentID,
ProfileID: claims.ProfileID,
Token: token,
})
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.CreateAgentResponse{
Agent: &mantraev1.Agent{
Id: agent.ID,
ProfileId: agent.ProfileID,
Token: agent.Token,
},
}), nil
}
func (s *AgentManagementService) UpdateAgentIP(
ctx context.Context,
req *connect.Request[mantraev1.UpdateAgentIPRequest],
) (*connect.Response[mantraev1.UpdateAgentIPResponse], error) {
if err := s.app.Conn.GetQuery().UpdateAgentIP(ctx, db.UpdateAgentIPParams{
ID: req.Msg.Id,
ActiveIp: &req.Msg.Ip,
}); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
agent, err := s.app.Conn.GetQuery().GetAgent(ctx, req.Msg.Id)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.UpdateAgentIPResponse{
Agent: &mantraev1.Agent{
Id: agent.ID,
ProfileId: agent.ProfileID,
Hostname: SafeString(agent.Hostname),
PublicIp: SafeString(agent.PublicIp),
ActiveIp: SafeString(agent.ActiveIp),
Token: agent.Token,
PrivateIps: agent.PrivateIps.IPs,
CreatedAt: SafeTimestamp(agent.CreatedAt),
UpdatedAt: SafeTimestamp(agent.UpdatedAt),
},
}), nil
}
func (s *AgentManagementService) DeleteAgent(
ctx context.Context,
req *connect.Request[mantraev1.DeleteAgentRequest],
) (*connect.Response[mantraev1.DeleteAgentResponse], error) {
if err := s.app.Conn.GetQuery().DeleteAgent(ctx, req.Msg.Id); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.DeleteAgentResponse{}), nil
}
func (s *AgentManagementService) ListAgents(
ctx context.Context,
req *connect.Request[mantraev1.ListAgentsRequest],
) (*connect.Response[mantraev1.ListAgentsResponse], error) {
if req.Msg.ProfileId == 0 {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("profile id is required"),
)
}
var params db.ListAgentsParams
params.ProfileID = req.Msg.ProfileId
if req.Msg.Limit == nil {
params.Limit = 100
} else {
params.Limit = *req.Msg.Limit
}
if req.Msg.Offset == nil {
params.Offset = 0
} else {
params.Offset = *req.Msg.Offset
}
dbAgents, err := s.app.Conn.GetQuery().ListAgents(ctx, params)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
totalCount, err := s.app.Conn.GetQuery().CountAgents(ctx)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
var agents []*mantraev1.Agent
for _, agent := range dbAgents {
agents = append(agents, &mantraev1.Agent{
Id: agent.ID,
ProfileId: agent.ProfileID,
Hostname: SafeString(agent.Hostname),
PublicIp: SafeString(agent.PublicIp),
ActiveIp: SafeString(agent.ActiveIp),
Token: agent.Token,
CreatedAt: SafeTimestamp(agent.CreatedAt),
UpdatedAt: SafeTimestamp(agent.UpdatedAt),
})
if agent.PrivateIps != nil {
for _, ip := range agent.PrivateIps.IPs {
agents[len(agents)-1].PrivateIps = append(agents[len(agents)-1].PrivateIps, ip)
}
}
}
return connect.NewResponse(&mantraev1.ListAgentsResponse{
Agents: agents,
TotalCount: totalCount,
}), nil
}
func (s *AgentManagementService) RotateAgentToken(
ctx context.Context,
req *connect.Request[mantraev1.RotateAgentTokenRequest],
) (*connect.Response[mantraev1.RotateAgentTokenResponse], error) {
agent, err := s.app.Conn.GetQuery().GetAgent(ctx, req.Msg.Id)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
serverUrl, err := s.app.Conn.GetQuery().GetSetting(ctx, settings.KeyServerURL)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
claims := &AgentClaims{
AgentID: agent.ID,
ProfileID: agent.ProfileID,
ServerURL: serverUrl.Value,
}
token, err := claims.EncodeJWT(s.app.Secret, time.Hour*72)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
if err := s.app.Conn.GetQuery().UpdateAgentToken(ctx, db.UpdateAgentTokenParams{
ID: agent.ID,
Token: token,
}); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.RotateAgentTokenResponse{}), nil
}
func (s *AgentManagementService) BootstrapAgent(
ctx context.Context,
req *connect.Request[mantraev1.BootstrapAgentRequest],
) (*connect.Response[mantraev1.BootstrapAgentResponse], error) {
enabled, ok := s.app.SM.Get(settings.KeyAgentBootstrapEnabled)
if !ok {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("failed to get agent bootstrap enabled setting"),
)
}
if enabled != "true" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("agent bootstrap is disabled, check your settings"),
)
}
if req.Msg.Token == "" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("token is required"),
)
}
// Check if token is valid
bootstrapToken, ok := s.app.SM.Get(settings.KeyAgentBootstrapToken)
if !ok {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("failed to get agent bootstrap token setting"),
)
}
if bootstrapToken != req.Msg.Token {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("invalid token"),
)
}
// Toke is valid, create agent
agent, err := s.app.Conn.GetQuery().CreateAgent(ctx, db.CreateAgentParams{
ProfileID: req.Msg.ProfileId,
})
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
serverUrl, err := s.app.Conn.GetQuery().GetSetting(ctx, settings.KeyServerURL)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
if serverUrl.Value == "" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("server url is required, check your settings"),
)
}
claims := &AgentClaims{
AgentID: agent.ID,
ProfileID: agent.ProfileID,
ServerURL: serverUrl.Value,
}
token, err := claims.EncodeJWT(s.app.Secret, time.Hour*72)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
if err := s.app.Conn.GetQuery().UpdateAgentToken(ctx, db.UpdateAgentTokenParams{
ID: agent.ID,
Token: token,
}); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
return connect.NewResponse(&mantraev1.BootstrapAgentResponse{Token: token}), nil
}

View File

@@ -193,7 +193,7 @@ CREATE TABLE agents (
profile_id INTEGER NOT NULL,
hostname TEXT,
public_ip TEXT,
private_ips TEXT,
private_ip TEXT,
containers TEXT,
active_ip TEXT,
token TEXT NOT NULL DEFAULT '',

View File

@@ -29,7 +29,7 @@ const createAgent = `-- name: CreateAgent :one
INSERT INTO
agents (id, profile_id, token, created_at)
VALUES
(?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, profile_id, hostname, public_ip, private_ips, containers, active_ip, token, created_at, updated_at
(?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, profile_id, hostname, public_ip, containers, active_ip, token, created_at, updated_at, private_ip
`
type CreateAgentParams struct {
@@ -46,12 +46,12 @@ func (q *Queries) CreateAgent(ctx context.Context, arg CreateAgentParams) (Agent
&i.ProfileID,
&i.Hostname,
&i.PublicIp,
&i.PrivateIps,
&i.Containers,
&i.ActiveIp,
&i.Token,
&i.CreatedAt,
&i.UpdatedAt,
&i.PrivateIp,
)
return i, err
}
@@ -69,7 +69,7 @@ func (q *Queries) DeleteAgent(ctx context.Context, id string) error {
const getAgent = `-- name: GetAgent :one
SELECT
id, profile_id, hostname, public_ip, private_ips, containers, active_ip, token, created_at, updated_at
id, profile_id, hostname, public_ip, containers, active_ip, token, created_at, updated_at, private_ip
FROM
agents
WHERE
@@ -84,19 +84,19 @@ func (q *Queries) GetAgent(ctx context.Context, id string) (Agent, error) {
&i.ProfileID,
&i.Hostname,
&i.PublicIp,
&i.PrivateIps,
&i.Containers,
&i.ActiveIp,
&i.Token,
&i.CreatedAt,
&i.UpdatedAt,
&i.PrivateIp,
)
return i, err
}
const listAgents = `-- name: ListAgents :many
SELECT
id, profile_id, hostname, public_ip, private_ips, containers, active_ip, token, created_at, updated_at
id, profile_id, hostname, public_ip, containers, active_ip, token, created_at, updated_at, private_ip
FROM
agents
WHERE
@@ -129,12 +129,12 @@ func (q *Queries) ListAgents(ctx context.Context, arg ListAgentsParams) ([]Agent
&i.ProfileID,
&i.Hostname,
&i.PublicIp,
&i.PrivateIps,
&i.Containers,
&i.ActiveIp,
&i.Token,
&i.CreatedAt,
&i.UpdatedAt,
&i.PrivateIp,
); err != nil {
return nil, err
}
@@ -154,20 +154,20 @@ UPDATE agents
SET
hostname = COALESCE(?, hostname),
public_ip = COALESCE(?, public_ip),
private_ips = COALESCE(?, private_ips),
containers = COALESCE(?, containers),
private_ip = COALESCE(?, private_ip),
active_ip = COALESCE(?, active_ip),
containers = COALESCE(?, containers),
updated_at = CURRENT_TIMESTAMP
WHERE
id = ? RETURNING id, profile_id, hostname, public_ip, private_ips, containers, active_ip, token, created_at, updated_at
id = ? RETURNING id, profile_id, hostname, public_ip, containers, active_ip, token, created_at, updated_at, private_ip
`
type UpdateAgentParams struct {
Hostname *string `json:"hostname"`
PublicIp *string `json:"publicIp"`
PrivateIps *schema.AgentPrivateIPs `json:"privateIps"`
Containers *schema.AgentContainers `json:"containers"`
PrivateIp *string `json:"privateIp"`
ActiveIp *string `json:"activeIp"`
Containers *schema.AgentContainers `json:"containers"`
ID string `json:"id"`
}
@@ -175,9 +175,9 @@ func (q *Queries) UpdateAgent(ctx context.Context, arg UpdateAgentParams) (Agent
row := q.queryRow(ctx, q.updateAgentStmt, updateAgent,
arg.Hostname,
arg.PublicIp,
arg.PrivateIps,
arg.Containers,
arg.PrivateIp,
arg.ActiveIp,
arg.Containers,
arg.ID,
)
var i Agent
@@ -186,12 +186,12 @@ func (q *Queries) UpdateAgent(ctx context.Context, arg UpdateAgentParams) (Agent
&i.ProfileID,
&i.Hostname,
&i.PublicIp,
&i.PrivateIps,
&i.Containers,
&i.ActiveIp,
&i.Token,
&i.CreatedAt,
&i.UpdatedAt,
&i.PrivateIp,
)
return i, err
}

View File

@@ -15,12 +15,12 @@ type Agent struct {
ProfileID int64 `json:"profileId"`
Hostname *string `json:"hostname"`
PublicIp *string `json:"publicIp"`
PrivateIps *schema.AgentPrivateIPs `json:"privateIps"`
Containers *schema.AgentContainers `json:"containers"`
ActiveIp *string `json:"activeIp"`
Token string `json:"token"`
CreatedAt *time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt"`
PrivateIp *string `json:"privateIp"`
}
type DnsProvider struct {

View File

@@ -188,15 +188,17 @@ ADD COLUMN description TEXT;
-- Update agents table - change JSON columns to TEXT
UPDATE agents
SET
private_ips = CASE
WHEN private_ips IS NOT NULL THEN private_ips
ELSE NULL
END,
containers = CASE
WHEN containers IS NOT NULL THEN containers
ELSE NULL
END;
ALTER TABLE agents
DROP COLUMN private_ips;
ALTER TABLE agents
ADD COLUMN private_ip TEXT;
-- Update users table - change id to TEXT and remove AUTOINCREMENT
CREATE TABLE users_new (
id TEXT PRIMARY KEY,

View File

@@ -37,9 +37,9 @@ UPDATE agents
SET
hostname = COALESCE(?, hostname),
public_ip = COALESCE(?, public_ip),
private_ips = COALESCE(?, private_ips),
containers = COALESCE(?, containers),
private_ip = COALESCE(?, private_ip),
active_ip = COALESCE(?, active_ip),
containers = COALESCE(?, containers),
updated_at = CURRENT_TIMESTAMP
WHERE
id = ? RETURNING *;

View File

@@ -1,93 +0,0 @@
package util
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"sync"
)
type EventMessage struct {
Type string `json:"type"`
Category string `json:"category"`
Message string `json:"message"`
}
const (
// Types
EventTypeError = "error"
EventTypeInfo = "info"
EventTypeCreate = "create"
EventTypeUpdate = "update"
EventTypeDelete = "delete"
// Categories
EventCategoryProfile = "profile"
EventCategoryTraefik = "traefik"
EventCategoryDNS = "dns"
EventCategoryUser = "user"
EventCategoryAgent = "agent"
EventCategorySetting = "setting"
)
var (
Broadcast = make(chan EventMessage, 100)
SSEDone = make(chan struct{})
Clients = make(map[http.ResponseWriter]bool)
ClientsMutex = &sync.Mutex{}
)
func StartEventProcessor(ctx context.Context) {
go func() {
defer close(SSEDone)
for {
select {
case msg := <-Broadcast:
ClientsMutex.Lock()
for client := range Clients {
// Non-blocking send to each client
go func(w http.ResponseWriter, message EventMessage) {
if err := SendEventToClient(w, message); err != nil {
slog.Error("Failed to send event", "error", err)
// Remove failed clients
ClientsMutex.Lock()
delete(Clients, w)
ClientsMutex.Unlock()
}
}(client, msg)
}
ClientsMutex.Unlock()
// If no clients, log the dropped event
if len(Clients) == 0 {
slog.Debug("Event dropped - no clients connected",
"type", msg.Type,
"message", msg.Message)
}
case <-ctx.Done():
return
}
}
}()
}
func SendEventToClient(w http.ResponseWriter, msg EventMessage) error {
// Implementation of sending event to a single client
data, err := json.Marshal(msg)
if err != nil {
return err
}
_, err = fmt.Fprintf(w, "data: %s\n\n", data)
if err != nil {
return err
}
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
return nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -33,18 +33,40 @@ const (
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
// period.
const (
// AgentServiceGetContainerProcedure is the fully-qualified name of the AgentService's GetContainer
// AgentServiceGetAgentProcedure is the fully-qualified name of the AgentService's GetAgent RPC.
AgentServiceGetAgentProcedure = "/mantrae.v1.AgentService/GetAgent"
// AgentServiceCreateAgentProcedure is the fully-qualified name of the AgentService's CreateAgent
// RPC.
AgentServiceGetContainerProcedure = "/mantrae.v1.AgentService/GetContainer"
AgentServiceCreateAgentProcedure = "/mantrae.v1.AgentService/CreateAgent"
// AgentServiceUpdateAgentIPProcedure is the fully-qualified name of the AgentService's
// UpdateAgentIP RPC.
AgentServiceUpdateAgentIPProcedure = "/mantrae.v1.AgentService/UpdateAgentIP"
// AgentServiceDeleteAgentProcedure is the fully-qualified name of the AgentService's DeleteAgent
// RPC.
AgentServiceDeleteAgentProcedure = "/mantrae.v1.AgentService/DeleteAgent"
// AgentServiceListAgentsProcedure is the fully-qualified name of the AgentService's ListAgents RPC.
AgentServiceListAgentsProcedure = "/mantrae.v1.AgentService/ListAgents"
// AgentServiceHealthCheckProcedure is the fully-qualified name of the AgentService's HealthCheck
// RPC.
AgentServiceHealthCheckProcedure = "/mantrae.v1.AgentService/HealthCheck"
// AgentServiceBootstrapAgentProcedure is the fully-qualified name of the AgentService's
// BootstrapAgent RPC.
AgentServiceBootstrapAgentProcedure = "/mantrae.v1.AgentService/BootstrapAgent"
// AgentServiceRotateAgentTokenProcedure is the fully-qualified name of the AgentService's
// RotateAgentToken RPC.
AgentServiceRotateAgentTokenProcedure = "/mantrae.v1.AgentService/RotateAgentToken"
)
// AgentServiceClient is a client for the mantrae.v1.AgentService service.
type AgentServiceClient interface {
GetContainer(context.Context, *connect.Request[v1.GetContainerRequest]) (*connect.Response[v1.GetContainerResponse], error)
GetAgent(context.Context, *connect.Request[v1.GetAgentRequest]) (*connect.Response[v1.GetAgentResponse], error)
CreateAgent(context.Context, *connect.Request[v1.CreateAgentRequest]) (*connect.Response[v1.CreateAgentResponse], error)
UpdateAgentIP(context.Context, *connect.Request[v1.UpdateAgentIPRequest]) (*connect.Response[v1.UpdateAgentIPResponse], error)
DeleteAgent(context.Context, *connect.Request[v1.DeleteAgentRequest]) (*connect.Response[v1.DeleteAgentResponse], error)
ListAgents(context.Context, *connect.Request[v1.ListAgentsRequest]) (*connect.Response[v1.ListAgentsResponse], error)
HealthCheck(context.Context, *connect.Request[v1.HealthCheckRequest]) (*connect.Response[v1.HealthCheckResponse], error)
BootstrapAgent(context.Context, *connect.Request[v1.BootstrapAgentRequest]) (*connect.Response[v1.BootstrapAgentResponse], error)
RotateAgentToken(context.Context, *connect.Request[v1.RotateAgentTokenRequest]) (*connect.Response[v1.RotateAgentTokenResponse], error)
}
// NewAgentServiceClient constructs a client for the mantrae.v1.AgentService service. By default, it
@@ -58,10 +80,36 @@ func NewAgentServiceClient(httpClient connect.HTTPClient, baseURL string, opts .
baseURL = strings.TrimRight(baseURL, "/")
agentServiceMethods := v1.File_mantrae_v1_agent_proto.Services().ByName("AgentService").Methods()
return &agentServiceClient{
getContainer: connect.NewClient[v1.GetContainerRequest, v1.GetContainerResponse](
getAgent: connect.NewClient[v1.GetAgentRequest, v1.GetAgentResponse](
httpClient,
baseURL+AgentServiceGetContainerProcedure,
connect.WithSchema(agentServiceMethods.ByName("GetContainer")),
baseURL+AgentServiceGetAgentProcedure,
connect.WithSchema(agentServiceMethods.ByName("GetAgent")),
connect.WithIdempotency(connect.IdempotencyNoSideEffects),
connect.WithClientOptions(opts...),
),
createAgent: connect.NewClient[v1.CreateAgentRequest, v1.CreateAgentResponse](
httpClient,
baseURL+AgentServiceCreateAgentProcedure,
connect.WithSchema(agentServiceMethods.ByName("CreateAgent")),
connect.WithClientOptions(opts...),
),
updateAgentIP: connect.NewClient[v1.UpdateAgentIPRequest, v1.UpdateAgentIPResponse](
httpClient,
baseURL+AgentServiceUpdateAgentIPProcedure,
connect.WithSchema(agentServiceMethods.ByName("UpdateAgentIP")),
connect.WithClientOptions(opts...),
),
deleteAgent: connect.NewClient[v1.DeleteAgentRequest, v1.DeleteAgentResponse](
httpClient,
baseURL+AgentServiceDeleteAgentProcedure,
connect.WithSchema(agentServiceMethods.ByName("DeleteAgent")),
connect.WithClientOptions(opts...),
),
listAgents: connect.NewClient[v1.ListAgentsRequest, v1.ListAgentsResponse](
httpClient,
baseURL+AgentServiceListAgentsProcedure,
connect.WithSchema(agentServiceMethods.ByName("ListAgents")),
connect.WithIdempotency(connect.IdempotencyNoSideEffects),
connect.WithClientOptions(opts...),
),
healthCheck: connect.NewClient[v1.HealthCheckRequest, v1.HealthCheckResponse](
@@ -70,18 +118,56 @@ func NewAgentServiceClient(httpClient connect.HTTPClient, baseURL string, opts .
connect.WithSchema(agentServiceMethods.ByName("HealthCheck")),
connect.WithClientOptions(opts...),
),
bootstrapAgent: connect.NewClient[v1.BootstrapAgentRequest, v1.BootstrapAgentResponse](
httpClient,
baseURL+AgentServiceBootstrapAgentProcedure,
connect.WithSchema(agentServiceMethods.ByName("BootstrapAgent")),
connect.WithClientOptions(opts...),
),
rotateAgentToken: connect.NewClient[v1.RotateAgentTokenRequest, v1.RotateAgentTokenResponse](
httpClient,
baseURL+AgentServiceRotateAgentTokenProcedure,
connect.WithSchema(agentServiceMethods.ByName("RotateAgentToken")),
connect.WithClientOptions(opts...),
),
}
}
// agentServiceClient implements AgentServiceClient.
type agentServiceClient struct {
getContainer *connect.Client[v1.GetContainerRequest, v1.GetContainerResponse]
healthCheck *connect.Client[v1.HealthCheckRequest, v1.HealthCheckResponse]
getAgent *connect.Client[v1.GetAgentRequest, v1.GetAgentResponse]
createAgent *connect.Client[v1.CreateAgentRequest, v1.CreateAgentResponse]
updateAgentIP *connect.Client[v1.UpdateAgentIPRequest, v1.UpdateAgentIPResponse]
deleteAgent *connect.Client[v1.DeleteAgentRequest, v1.DeleteAgentResponse]
listAgents *connect.Client[v1.ListAgentsRequest, v1.ListAgentsResponse]
healthCheck *connect.Client[v1.HealthCheckRequest, v1.HealthCheckResponse]
bootstrapAgent *connect.Client[v1.BootstrapAgentRequest, v1.BootstrapAgentResponse]
rotateAgentToken *connect.Client[v1.RotateAgentTokenRequest, v1.RotateAgentTokenResponse]
}
// GetContainer calls mantrae.v1.AgentService.GetContainer.
func (c *agentServiceClient) GetContainer(ctx context.Context, req *connect.Request[v1.GetContainerRequest]) (*connect.Response[v1.GetContainerResponse], error) {
return c.getContainer.CallUnary(ctx, req)
// GetAgent calls mantrae.v1.AgentService.GetAgent.
func (c *agentServiceClient) GetAgent(ctx context.Context, req *connect.Request[v1.GetAgentRequest]) (*connect.Response[v1.GetAgentResponse], error) {
return c.getAgent.CallUnary(ctx, req)
}
// CreateAgent calls mantrae.v1.AgentService.CreateAgent.
func (c *agentServiceClient) CreateAgent(ctx context.Context, req *connect.Request[v1.CreateAgentRequest]) (*connect.Response[v1.CreateAgentResponse], error) {
return c.createAgent.CallUnary(ctx, req)
}
// UpdateAgentIP calls mantrae.v1.AgentService.UpdateAgentIP.
func (c *agentServiceClient) UpdateAgentIP(ctx context.Context, req *connect.Request[v1.UpdateAgentIPRequest]) (*connect.Response[v1.UpdateAgentIPResponse], error) {
return c.updateAgentIP.CallUnary(ctx, req)
}
// DeleteAgent calls mantrae.v1.AgentService.DeleteAgent.
func (c *agentServiceClient) DeleteAgent(ctx context.Context, req *connect.Request[v1.DeleteAgentRequest]) (*connect.Response[v1.DeleteAgentResponse], error) {
return c.deleteAgent.CallUnary(ctx, req)
}
// ListAgents calls mantrae.v1.AgentService.ListAgents.
func (c *agentServiceClient) ListAgents(ctx context.Context, req *connect.Request[v1.ListAgentsRequest]) (*connect.Response[v1.ListAgentsResponse], error) {
return c.listAgents.CallUnary(ctx, req)
}
// HealthCheck calls mantrae.v1.AgentService.HealthCheck.
@@ -89,10 +175,26 @@ func (c *agentServiceClient) HealthCheck(ctx context.Context, req *connect.Reque
return c.healthCheck.CallUnary(ctx, req)
}
// BootstrapAgent calls mantrae.v1.AgentService.BootstrapAgent.
func (c *agentServiceClient) BootstrapAgent(ctx context.Context, req *connect.Request[v1.BootstrapAgentRequest]) (*connect.Response[v1.BootstrapAgentResponse], error) {
return c.bootstrapAgent.CallUnary(ctx, req)
}
// RotateAgentToken calls mantrae.v1.AgentService.RotateAgentToken.
func (c *agentServiceClient) RotateAgentToken(ctx context.Context, req *connect.Request[v1.RotateAgentTokenRequest]) (*connect.Response[v1.RotateAgentTokenResponse], error) {
return c.rotateAgentToken.CallUnary(ctx, req)
}
// AgentServiceHandler is an implementation of the mantrae.v1.AgentService service.
type AgentServiceHandler interface {
GetContainer(context.Context, *connect.Request[v1.GetContainerRequest]) (*connect.Response[v1.GetContainerResponse], error)
GetAgent(context.Context, *connect.Request[v1.GetAgentRequest]) (*connect.Response[v1.GetAgentResponse], error)
CreateAgent(context.Context, *connect.Request[v1.CreateAgentRequest]) (*connect.Response[v1.CreateAgentResponse], error)
UpdateAgentIP(context.Context, *connect.Request[v1.UpdateAgentIPRequest]) (*connect.Response[v1.UpdateAgentIPResponse], error)
DeleteAgent(context.Context, *connect.Request[v1.DeleteAgentRequest]) (*connect.Response[v1.DeleteAgentResponse], error)
ListAgents(context.Context, *connect.Request[v1.ListAgentsRequest]) (*connect.Response[v1.ListAgentsResponse], error)
HealthCheck(context.Context, *connect.Request[v1.HealthCheckRequest]) (*connect.Response[v1.HealthCheckResponse], error)
BootstrapAgent(context.Context, *connect.Request[v1.BootstrapAgentRequest]) (*connect.Response[v1.BootstrapAgentResponse], error)
RotateAgentToken(context.Context, *connect.Request[v1.RotateAgentTokenRequest]) (*connect.Response[v1.RotateAgentTokenResponse], error)
}
// NewAgentServiceHandler builds an HTTP handler from the service implementation. It returns the
@@ -102,10 +204,36 @@ type AgentServiceHandler interface {
// and JSON codecs. They also support gzip compression.
func NewAgentServiceHandler(svc AgentServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
agentServiceMethods := v1.File_mantrae_v1_agent_proto.Services().ByName("AgentService").Methods()
agentServiceGetContainerHandler := connect.NewUnaryHandler(
AgentServiceGetContainerProcedure,
svc.GetContainer,
connect.WithSchema(agentServiceMethods.ByName("GetContainer")),
agentServiceGetAgentHandler := connect.NewUnaryHandler(
AgentServiceGetAgentProcedure,
svc.GetAgent,
connect.WithSchema(agentServiceMethods.ByName("GetAgent")),
connect.WithIdempotency(connect.IdempotencyNoSideEffects),
connect.WithHandlerOptions(opts...),
)
agentServiceCreateAgentHandler := connect.NewUnaryHandler(
AgentServiceCreateAgentProcedure,
svc.CreateAgent,
connect.WithSchema(agentServiceMethods.ByName("CreateAgent")),
connect.WithHandlerOptions(opts...),
)
agentServiceUpdateAgentIPHandler := connect.NewUnaryHandler(
AgentServiceUpdateAgentIPProcedure,
svc.UpdateAgentIP,
connect.WithSchema(agentServiceMethods.ByName("UpdateAgentIP")),
connect.WithHandlerOptions(opts...),
)
agentServiceDeleteAgentHandler := connect.NewUnaryHandler(
AgentServiceDeleteAgentProcedure,
svc.DeleteAgent,
connect.WithSchema(agentServiceMethods.ByName("DeleteAgent")),
connect.WithHandlerOptions(opts...),
)
agentServiceListAgentsHandler := connect.NewUnaryHandler(
AgentServiceListAgentsProcedure,
svc.ListAgents,
connect.WithSchema(agentServiceMethods.ByName("ListAgents")),
connect.WithIdempotency(connect.IdempotencyNoSideEffects),
connect.WithHandlerOptions(opts...),
)
agentServiceHealthCheckHandler := connect.NewUnaryHandler(
@@ -114,12 +242,36 @@ func NewAgentServiceHandler(svc AgentServiceHandler, opts ...connect.HandlerOpti
connect.WithSchema(agentServiceMethods.ByName("HealthCheck")),
connect.WithHandlerOptions(opts...),
)
agentServiceBootstrapAgentHandler := connect.NewUnaryHandler(
AgentServiceBootstrapAgentProcedure,
svc.BootstrapAgent,
connect.WithSchema(agentServiceMethods.ByName("BootstrapAgent")),
connect.WithHandlerOptions(opts...),
)
agentServiceRotateAgentTokenHandler := connect.NewUnaryHandler(
AgentServiceRotateAgentTokenProcedure,
svc.RotateAgentToken,
connect.WithSchema(agentServiceMethods.ByName("RotateAgentToken")),
connect.WithHandlerOptions(opts...),
)
return "/mantrae.v1.AgentService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case AgentServiceGetContainerProcedure:
agentServiceGetContainerHandler.ServeHTTP(w, r)
case AgentServiceGetAgentProcedure:
agentServiceGetAgentHandler.ServeHTTP(w, r)
case AgentServiceCreateAgentProcedure:
agentServiceCreateAgentHandler.ServeHTTP(w, r)
case AgentServiceUpdateAgentIPProcedure:
agentServiceUpdateAgentIPHandler.ServeHTTP(w, r)
case AgentServiceDeleteAgentProcedure:
agentServiceDeleteAgentHandler.ServeHTTP(w, r)
case AgentServiceListAgentsProcedure:
agentServiceListAgentsHandler.ServeHTTP(w, r)
case AgentServiceHealthCheckProcedure:
agentServiceHealthCheckHandler.ServeHTTP(w, r)
case AgentServiceBootstrapAgentProcedure:
agentServiceBootstrapAgentHandler.ServeHTTP(w, r)
case AgentServiceRotateAgentTokenProcedure:
agentServiceRotateAgentTokenHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
@@ -129,10 +281,34 @@ func NewAgentServiceHandler(svc AgentServiceHandler, opts ...connect.HandlerOpti
// UnimplementedAgentServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedAgentServiceHandler struct{}
func (UnimplementedAgentServiceHandler) GetContainer(context.Context, *connect.Request[v1.GetContainerRequest]) (*connect.Response[v1.GetContainerResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.GetContainer is not implemented"))
func (UnimplementedAgentServiceHandler) GetAgent(context.Context, *connect.Request[v1.GetAgentRequest]) (*connect.Response[v1.GetAgentResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.GetAgent is not implemented"))
}
func (UnimplementedAgentServiceHandler) CreateAgent(context.Context, *connect.Request[v1.CreateAgentRequest]) (*connect.Response[v1.CreateAgentResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.CreateAgent is not implemented"))
}
func (UnimplementedAgentServiceHandler) UpdateAgentIP(context.Context, *connect.Request[v1.UpdateAgentIPRequest]) (*connect.Response[v1.UpdateAgentIPResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.UpdateAgentIP is not implemented"))
}
func (UnimplementedAgentServiceHandler) DeleteAgent(context.Context, *connect.Request[v1.DeleteAgentRequest]) (*connect.Response[v1.DeleteAgentResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.DeleteAgent is not implemented"))
}
func (UnimplementedAgentServiceHandler) ListAgents(context.Context, *connect.Request[v1.ListAgentsRequest]) (*connect.Response[v1.ListAgentsResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.ListAgents is not implemented"))
}
func (UnimplementedAgentServiceHandler) HealthCheck(context.Context, *connect.Request[v1.HealthCheckRequest]) (*connect.Response[v1.HealthCheckResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.HealthCheck is not implemented"))
}
func (UnimplementedAgentServiceHandler) BootstrapAgent(context.Context, *connect.Request[v1.BootstrapAgentRequest]) (*connect.Response[v1.BootstrapAgentResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.BootstrapAgent is not implemented"))
}
func (UnimplementedAgentServiceHandler) RotateAgentToken(context.Context, *connect.Request[v1.RotateAgentTokenRequest]) (*connect.Response[v1.RotateAgentTokenResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentService.RotateAgentToken is not implemented"))
}

View File

@@ -1,288 +0,0 @@
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
//
// Source: mantrae/v1/agent_management.proto
package mantraev1connect
import (
connect "connectrpc.com/connect"
context "context"
errors "errors"
v1 "github.com/mizuchilabs/mantrae/proto/gen/mantrae/v1"
http "net/http"
strings "strings"
)
// This is a compile-time assertion to ensure that this generated file and the connect package are
// compatible. If you get a compiler error that this constant is not defined, this code was
// generated with a version of connect newer than the one compiled into your binary. You can fix the
// problem by either regenerating this code with an older version of connect or updating the connect
// version compiled into your binary.
const _ = connect.IsAtLeastVersion1_13_0
const (
// AgentManagementServiceName is the fully-qualified name of the AgentManagementService service.
AgentManagementServiceName = "mantrae.v1.AgentManagementService"
)
// These constants are the fully-qualified names of the RPCs defined in this package. They're
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
//
// Note that these are different from the fully-qualified method names used by
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
// period.
const (
// AgentManagementServiceGetAgentProcedure is the fully-qualified name of the
// AgentManagementService's GetAgent RPC.
AgentManagementServiceGetAgentProcedure = "/mantrae.v1.AgentManagementService/GetAgent"
// AgentManagementServiceCreateAgentProcedure is the fully-qualified name of the
// AgentManagementService's CreateAgent RPC.
AgentManagementServiceCreateAgentProcedure = "/mantrae.v1.AgentManagementService/CreateAgent"
// AgentManagementServiceUpdateAgentIPProcedure is the fully-qualified name of the
// AgentManagementService's UpdateAgentIP RPC.
AgentManagementServiceUpdateAgentIPProcedure = "/mantrae.v1.AgentManagementService/UpdateAgentIP"
// AgentManagementServiceDeleteAgentProcedure is the fully-qualified name of the
// AgentManagementService's DeleteAgent RPC.
AgentManagementServiceDeleteAgentProcedure = "/mantrae.v1.AgentManagementService/DeleteAgent"
// AgentManagementServiceListAgentsProcedure is the fully-qualified name of the
// AgentManagementService's ListAgents RPC.
AgentManagementServiceListAgentsProcedure = "/mantrae.v1.AgentManagementService/ListAgents"
// AgentManagementServiceRotateAgentTokenProcedure is the fully-qualified name of the
// AgentManagementService's RotateAgentToken RPC.
AgentManagementServiceRotateAgentTokenProcedure = "/mantrae.v1.AgentManagementService/RotateAgentToken"
// AgentManagementServiceBootstrapAgentProcedure is the fully-qualified name of the
// AgentManagementService's BootstrapAgent RPC.
AgentManagementServiceBootstrapAgentProcedure = "/mantrae.v1.AgentManagementService/BootstrapAgent"
)
// AgentManagementServiceClient is a client for the mantrae.v1.AgentManagementService service.
type AgentManagementServiceClient interface {
GetAgent(context.Context, *connect.Request[v1.GetAgentRequest]) (*connect.Response[v1.GetAgentResponse], error)
CreateAgent(context.Context, *connect.Request[v1.CreateAgentRequest]) (*connect.Response[v1.CreateAgentResponse], error)
UpdateAgentIP(context.Context, *connect.Request[v1.UpdateAgentIPRequest]) (*connect.Response[v1.UpdateAgentIPResponse], error)
DeleteAgent(context.Context, *connect.Request[v1.DeleteAgentRequest]) (*connect.Response[v1.DeleteAgentResponse], error)
ListAgents(context.Context, *connect.Request[v1.ListAgentsRequest]) (*connect.Response[v1.ListAgentsResponse], error)
RotateAgentToken(context.Context, *connect.Request[v1.RotateAgentTokenRequest]) (*connect.Response[v1.RotateAgentTokenResponse], error)
BootstrapAgent(context.Context, *connect.Request[v1.BootstrapAgentRequest]) (*connect.Response[v1.BootstrapAgentResponse], error)
}
// NewAgentManagementServiceClient constructs a client for the mantrae.v1.AgentManagementService
// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for
// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply
// the connect.WithGRPC() or connect.WithGRPCWeb() options.
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewAgentManagementServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) AgentManagementServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
agentManagementServiceMethods := v1.File_mantrae_v1_agent_management_proto.Services().ByName("AgentManagementService").Methods()
return &agentManagementServiceClient{
getAgent: connect.NewClient[v1.GetAgentRequest, v1.GetAgentResponse](
httpClient,
baseURL+AgentManagementServiceGetAgentProcedure,
connect.WithSchema(agentManagementServiceMethods.ByName("GetAgent")),
connect.WithIdempotency(connect.IdempotencyNoSideEffects),
connect.WithClientOptions(opts...),
),
createAgent: connect.NewClient[v1.CreateAgentRequest, v1.CreateAgentResponse](
httpClient,
baseURL+AgentManagementServiceCreateAgentProcedure,
connect.WithSchema(agentManagementServiceMethods.ByName("CreateAgent")),
connect.WithClientOptions(opts...),
),
updateAgentIP: connect.NewClient[v1.UpdateAgentIPRequest, v1.UpdateAgentIPResponse](
httpClient,
baseURL+AgentManagementServiceUpdateAgentIPProcedure,
connect.WithSchema(agentManagementServiceMethods.ByName("UpdateAgentIP")),
connect.WithClientOptions(opts...),
),
deleteAgent: connect.NewClient[v1.DeleteAgentRequest, v1.DeleteAgentResponse](
httpClient,
baseURL+AgentManagementServiceDeleteAgentProcedure,
connect.WithSchema(agentManagementServiceMethods.ByName("DeleteAgent")),
connect.WithClientOptions(opts...),
),
listAgents: connect.NewClient[v1.ListAgentsRequest, v1.ListAgentsResponse](
httpClient,
baseURL+AgentManagementServiceListAgentsProcedure,
connect.WithSchema(agentManagementServiceMethods.ByName("ListAgents")),
connect.WithIdempotency(connect.IdempotencyNoSideEffects),
connect.WithClientOptions(opts...),
),
rotateAgentToken: connect.NewClient[v1.RotateAgentTokenRequest, v1.RotateAgentTokenResponse](
httpClient,
baseURL+AgentManagementServiceRotateAgentTokenProcedure,
connect.WithSchema(agentManagementServiceMethods.ByName("RotateAgentToken")),
connect.WithClientOptions(opts...),
),
bootstrapAgent: connect.NewClient[v1.BootstrapAgentRequest, v1.BootstrapAgentResponse](
httpClient,
baseURL+AgentManagementServiceBootstrapAgentProcedure,
connect.WithSchema(agentManagementServiceMethods.ByName("BootstrapAgent")),
connect.WithClientOptions(opts...),
),
}
}
// agentManagementServiceClient implements AgentManagementServiceClient.
type agentManagementServiceClient struct {
getAgent *connect.Client[v1.GetAgentRequest, v1.GetAgentResponse]
createAgent *connect.Client[v1.CreateAgentRequest, v1.CreateAgentResponse]
updateAgentIP *connect.Client[v1.UpdateAgentIPRequest, v1.UpdateAgentIPResponse]
deleteAgent *connect.Client[v1.DeleteAgentRequest, v1.DeleteAgentResponse]
listAgents *connect.Client[v1.ListAgentsRequest, v1.ListAgentsResponse]
rotateAgentToken *connect.Client[v1.RotateAgentTokenRequest, v1.RotateAgentTokenResponse]
bootstrapAgent *connect.Client[v1.BootstrapAgentRequest, v1.BootstrapAgentResponse]
}
// GetAgent calls mantrae.v1.AgentManagementService.GetAgent.
func (c *agentManagementServiceClient) GetAgent(ctx context.Context, req *connect.Request[v1.GetAgentRequest]) (*connect.Response[v1.GetAgentResponse], error) {
return c.getAgent.CallUnary(ctx, req)
}
// CreateAgent calls mantrae.v1.AgentManagementService.CreateAgent.
func (c *agentManagementServiceClient) CreateAgent(ctx context.Context, req *connect.Request[v1.CreateAgentRequest]) (*connect.Response[v1.CreateAgentResponse], error) {
return c.createAgent.CallUnary(ctx, req)
}
// UpdateAgentIP calls mantrae.v1.AgentManagementService.UpdateAgentIP.
func (c *agentManagementServiceClient) UpdateAgentIP(ctx context.Context, req *connect.Request[v1.UpdateAgentIPRequest]) (*connect.Response[v1.UpdateAgentIPResponse], error) {
return c.updateAgentIP.CallUnary(ctx, req)
}
// DeleteAgent calls mantrae.v1.AgentManagementService.DeleteAgent.
func (c *agentManagementServiceClient) DeleteAgent(ctx context.Context, req *connect.Request[v1.DeleteAgentRequest]) (*connect.Response[v1.DeleteAgentResponse], error) {
return c.deleteAgent.CallUnary(ctx, req)
}
// ListAgents calls mantrae.v1.AgentManagementService.ListAgents.
func (c *agentManagementServiceClient) ListAgents(ctx context.Context, req *connect.Request[v1.ListAgentsRequest]) (*connect.Response[v1.ListAgentsResponse], error) {
return c.listAgents.CallUnary(ctx, req)
}
// RotateAgentToken calls mantrae.v1.AgentManagementService.RotateAgentToken.
func (c *agentManagementServiceClient) RotateAgentToken(ctx context.Context, req *connect.Request[v1.RotateAgentTokenRequest]) (*connect.Response[v1.RotateAgentTokenResponse], error) {
return c.rotateAgentToken.CallUnary(ctx, req)
}
// BootstrapAgent calls mantrae.v1.AgentManagementService.BootstrapAgent.
func (c *agentManagementServiceClient) BootstrapAgent(ctx context.Context, req *connect.Request[v1.BootstrapAgentRequest]) (*connect.Response[v1.BootstrapAgentResponse], error) {
return c.bootstrapAgent.CallUnary(ctx, req)
}
// AgentManagementServiceHandler is an implementation of the mantrae.v1.AgentManagementService
// service.
type AgentManagementServiceHandler interface {
GetAgent(context.Context, *connect.Request[v1.GetAgentRequest]) (*connect.Response[v1.GetAgentResponse], error)
CreateAgent(context.Context, *connect.Request[v1.CreateAgentRequest]) (*connect.Response[v1.CreateAgentResponse], error)
UpdateAgentIP(context.Context, *connect.Request[v1.UpdateAgentIPRequest]) (*connect.Response[v1.UpdateAgentIPResponse], error)
DeleteAgent(context.Context, *connect.Request[v1.DeleteAgentRequest]) (*connect.Response[v1.DeleteAgentResponse], error)
ListAgents(context.Context, *connect.Request[v1.ListAgentsRequest]) (*connect.Response[v1.ListAgentsResponse], error)
RotateAgentToken(context.Context, *connect.Request[v1.RotateAgentTokenRequest]) (*connect.Response[v1.RotateAgentTokenResponse], error)
BootstrapAgent(context.Context, *connect.Request[v1.BootstrapAgentRequest]) (*connect.Response[v1.BootstrapAgentResponse], error)
}
// NewAgentManagementServiceHandler builds an HTTP handler from the service implementation. It
// returns the path on which to mount the handler and the handler itself.
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewAgentManagementServiceHandler(svc AgentManagementServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
agentManagementServiceMethods := v1.File_mantrae_v1_agent_management_proto.Services().ByName("AgentManagementService").Methods()
agentManagementServiceGetAgentHandler := connect.NewUnaryHandler(
AgentManagementServiceGetAgentProcedure,
svc.GetAgent,
connect.WithSchema(agentManagementServiceMethods.ByName("GetAgent")),
connect.WithIdempotency(connect.IdempotencyNoSideEffects),
connect.WithHandlerOptions(opts...),
)
agentManagementServiceCreateAgentHandler := connect.NewUnaryHandler(
AgentManagementServiceCreateAgentProcedure,
svc.CreateAgent,
connect.WithSchema(agentManagementServiceMethods.ByName("CreateAgent")),
connect.WithHandlerOptions(opts...),
)
agentManagementServiceUpdateAgentIPHandler := connect.NewUnaryHandler(
AgentManagementServiceUpdateAgentIPProcedure,
svc.UpdateAgentIP,
connect.WithSchema(agentManagementServiceMethods.ByName("UpdateAgentIP")),
connect.WithHandlerOptions(opts...),
)
agentManagementServiceDeleteAgentHandler := connect.NewUnaryHandler(
AgentManagementServiceDeleteAgentProcedure,
svc.DeleteAgent,
connect.WithSchema(agentManagementServiceMethods.ByName("DeleteAgent")),
connect.WithHandlerOptions(opts...),
)
agentManagementServiceListAgentsHandler := connect.NewUnaryHandler(
AgentManagementServiceListAgentsProcedure,
svc.ListAgents,
connect.WithSchema(agentManagementServiceMethods.ByName("ListAgents")),
connect.WithIdempotency(connect.IdempotencyNoSideEffects),
connect.WithHandlerOptions(opts...),
)
agentManagementServiceRotateAgentTokenHandler := connect.NewUnaryHandler(
AgentManagementServiceRotateAgentTokenProcedure,
svc.RotateAgentToken,
connect.WithSchema(agentManagementServiceMethods.ByName("RotateAgentToken")),
connect.WithHandlerOptions(opts...),
)
agentManagementServiceBootstrapAgentHandler := connect.NewUnaryHandler(
AgentManagementServiceBootstrapAgentProcedure,
svc.BootstrapAgent,
connect.WithSchema(agentManagementServiceMethods.ByName("BootstrapAgent")),
connect.WithHandlerOptions(opts...),
)
return "/mantrae.v1.AgentManagementService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case AgentManagementServiceGetAgentProcedure:
agentManagementServiceGetAgentHandler.ServeHTTP(w, r)
case AgentManagementServiceCreateAgentProcedure:
agentManagementServiceCreateAgentHandler.ServeHTTP(w, r)
case AgentManagementServiceUpdateAgentIPProcedure:
agentManagementServiceUpdateAgentIPHandler.ServeHTTP(w, r)
case AgentManagementServiceDeleteAgentProcedure:
agentManagementServiceDeleteAgentHandler.ServeHTTP(w, r)
case AgentManagementServiceListAgentsProcedure:
agentManagementServiceListAgentsHandler.ServeHTTP(w, r)
case AgentManagementServiceRotateAgentTokenProcedure:
agentManagementServiceRotateAgentTokenHandler.ServeHTTP(w, r)
case AgentManagementServiceBootstrapAgentProcedure:
agentManagementServiceBootstrapAgentHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedAgentManagementServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedAgentManagementServiceHandler struct{}
func (UnimplementedAgentManagementServiceHandler) GetAgent(context.Context, *connect.Request[v1.GetAgentRequest]) (*connect.Response[v1.GetAgentResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentManagementService.GetAgent is not implemented"))
}
func (UnimplementedAgentManagementServiceHandler) CreateAgent(context.Context, *connect.Request[v1.CreateAgentRequest]) (*connect.Response[v1.CreateAgentResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentManagementService.CreateAgent is not implemented"))
}
func (UnimplementedAgentManagementServiceHandler) UpdateAgentIP(context.Context, *connect.Request[v1.UpdateAgentIPRequest]) (*connect.Response[v1.UpdateAgentIPResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentManagementService.UpdateAgentIP is not implemented"))
}
func (UnimplementedAgentManagementServiceHandler) DeleteAgent(context.Context, *connect.Request[v1.DeleteAgentRequest]) (*connect.Response[v1.DeleteAgentResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentManagementService.DeleteAgent is not implemented"))
}
func (UnimplementedAgentManagementServiceHandler) ListAgents(context.Context, *connect.Request[v1.ListAgentsRequest]) (*connect.Response[v1.ListAgentsResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentManagementService.ListAgents is not implemented"))
}
func (UnimplementedAgentManagementServiceHandler) RotateAgentToken(context.Context, *connect.Request[v1.RotateAgentTokenRequest]) (*connect.Response[v1.RotateAgentTokenResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentManagementService.RotateAgentToken is not implemented"))
}
func (UnimplementedAgentManagementServiceHandler) BootstrapAgent(context.Context, *connect.Request[v1.BootstrapAgentRequest]) (*connect.Response[v1.BootstrapAgentResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mantrae.v1.AgentManagementService.BootstrapAgent is not implemented"))
}

View File

@@ -129,6 +129,68 @@ components:
the Joda Time's [`ISODateTimeFormat.dateTime()`](
http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()
) to obtain a formatter capable of generating timestamps in this format.
mantrae.v1.Agent:
type: object
properties:
id:
type: string
title: id
profileId:
type:
- integer
- string
title: profile_id
format: int64
hostname:
type: string
title: hostname
publicIp:
type: string
title: public_ip
privateIp:
type: string
title: private_ip
activeIp:
type: string
title: active_ip
token:
type: string
title: token
containers:
type: array
items:
$ref: '#/components/schemas/mantrae.v1.Container'
title: containers
createdAt:
title: created_at
$ref: '#/components/schemas/google.protobuf.Timestamp'
updatedAt:
title: updated_at
$ref: '#/components/schemas/google.protobuf.Timestamp'
title: Agent
additionalProperties: false
mantrae.v1.BootstrapAgentRequest:
type: object
properties:
profileId:
type:
- integer
- string
title: profile_id
format: int64
token:
type: string
title: token
title: BootstrapAgentRequest
additionalProperties: false
mantrae.v1.BootstrapAgentResponse:
type: object
properties:
token:
type: string
title: token
title: BootstrapAgentResponse
additionalProperties: false
mantrae.v1.Container:
type: object
properties:
@@ -186,170 +248,6 @@ components:
format: int32
title: PortmapEntry
additionalProperties: false
mantrae.v1.GetContainerRequest:
type: object
properties:
hostname:
type: string
title: hostname
publicIp:
type: string
title: public_ip
privateIps:
type: array
items:
type: string
title: private_ips
containers:
type: array
items:
$ref: '#/components/schemas/mantrae.v1.Container'
title: containers
updated:
title: updated
$ref: '#/components/schemas/google.protobuf.Timestamp'
title: GetContainerRequest
additionalProperties: false
mantrae.v1.GetContainerResponse:
type: object
title: GetContainerResponse
additionalProperties: false
mantrae.v1.HealthCheckRequest:
type: object
title: HealthCheckRequest
additionalProperties: false
mantrae.v1.HealthCheckResponse:
type: object
properties:
ok:
type: boolean
title: ok
token:
type: string
title: token
title: HealthCheckResponse
additionalProperties: false
connect-protocol-version:
type: number
title: Connect-Protocol-Version
enum:
- 1
description: Define the version of the Connect protocol
const: 1
connect-timeout-header:
type: number
title: Connect-Timeout-Ms
description: Define the timeout, in ms
connect.error:
type: object
properties:
code:
type: string
examples:
- not_found
enum:
- canceled
- unknown
- invalid_argument
- deadline_exceeded
- not_found
- already_exists
- permission_denied
- resource_exhausted
- failed_precondition
- aborted
- out_of_range
- unimplemented
- internal
- unavailable
- data_loss
- unauthenticated
description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
message:
type: string
description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
detail:
$ref: '#/components/schemas/google.protobuf.Any'
title: Connect Error
additionalProperties: true
description: 'Error type returned by Connect: https://connectrpc.com/docs/go/errors/#http-representation'
google.protobuf.Any:
type: object
properties:
type:
type: string
value:
type: string
format: binary
debug:
type: object
additionalProperties: true
additionalProperties: true
description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message.
mantrae.v1.Agent:
type: object
properties:
id:
type: string
title: id
profileId:
type:
- integer
- string
title: profile_id
format: int64
hostname:
type: string
title: hostname
publicIp:
type: string
title: public_ip
activeIp:
type: string
title: active_ip
token:
type: string
title: token
privateIps:
type: array
items:
type: string
title: private_ips
containers:
type: array
items:
$ref: '#/components/schemas/mantrae.v1.Container'
title: containers
createdAt:
title: created_at
$ref: '#/components/schemas/google.protobuf.Timestamp'
updatedAt:
title: updated_at
$ref: '#/components/schemas/google.protobuf.Timestamp'
title: Agent
additionalProperties: false
mantrae.v1.BootstrapAgentRequest:
type: object
properties:
profileId:
type:
- integer
- string
title: profile_id
format: int64
token:
type: string
title: token
title: BootstrapAgentRequest
additionalProperties: false
mantrae.v1.BootstrapAgentResponse:
type: object
properties:
token:
type: string
title: token
title: BootstrapAgentResponse
additionalProperties: false
mantrae.v1.CreateAgentRequest:
type: object
properties:
@@ -397,6 +295,25 @@ components:
$ref: '#/components/schemas/mantrae.v1.Agent'
title: GetAgentResponse
additionalProperties: false
mantrae.v1.HealthCheckRequest:
type: object
properties:
publicIp:
type: string
title: public_ip
privateIp:
type: string
title: private_ip
title: HealthCheckRequest
additionalProperties: false
mantrae.v1.HealthCheckResponse:
type: object
properties:
agent:
title: agent
$ref: '#/components/schemas/mantrae.v1.Agent'
title: HealthCheckResponse
additionalProperties: false
mantrae.v1.ListAgentsRequest:
type: object
properties:
@@ -495,6 +412,63 @@ components:
enum:
- v1
description: Define the version of the Connect protocol
connect-protocol-version:
type: number
title: Connect-Protocol-Version
enum:
- 1
description: Define the version of the Connect protocol
const: 1
connect-timeout-header:
type: number
title: Connect-Timeout-Ms
description: Define the timeout, in ms
connect.error:
type: object
properties:
code:
type: string
examples:
- not_found
enum:
- canceled
- unknown
- invalid_argument
- deadline_exceeded
- not_found
- already_exists
- permission_denied
- resource_exhausted
- failed_precondition
- aborted
- out_of_range
- unimplemented
- internal
- unavailable
- data_loss
- unauthenticated
description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
message:
type: string
description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
detail:
$ref: '#/components/schemas/google.protobuf.Any'
title: Connect Error
additionalProperties: true
description: 'Error type returned by Connect: https://connectrpc.com/docs/go/errors/#http-representation'
google.protobuf.Any:
type: object
properties:
type:
type: string
value:
type: string
format: binary
debug:
type: object
additionalProperties: true
additionalProperties: true
description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message.
mantrae.v1.Backup:
type: object
properties:
@@ -2179,82 +2153,12 @@ components:
security:
- BearerAuth: []
paths:
/mantrae.v1.AgentService/GetContainer:
post:
tags:
- mantrae.v1.AgentService
summary: GetContainer
operationId: mantrae.v1.AgentService.GetContainer
parameters:
- name: Connect-Protocol-Version
in: header
required: true
schema:
$ref: '#/components/schemas/connect-protocol-version'
- name: Connect-Timeout-Ms
in: header
schema:
$ref: '#/components/schemas/connect-timeout-header'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.GetContainerRequest'
required: true
responses:
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/connect.error'
"200":
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.GetContainerResponse'
/mantrae.v1.AgentService/HealthCheck:
post:
tags:
- mantrae.v1.AgentService
summary: HealthCheck
operationId: mantrae.v1.AgentService.HealthCheck
parameters:
- name: Connect-Protocol-Version
in: header
required: true
schema:
$ref: '#/components/schemas/connect-protocol-version'
- name: Connect-Timeout-Ms
in: header
schema:
$ref: '#/components/schemas/connect-timeout-header'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.HealthCheckRequest'
required: true
responses:
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/connect.error'
"200":
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.HealthCheckResponse'
/mantrae.v1.AgentManagementService/GetAgent:
/mantrae.v1.AgentService/GetAgent:
get:
tags:
- mantrae.v1.AgentManagementService
- mantrae.v1.AgentService
summary: GetAgent
operationId: mantrae.v1.AgentManagementService.GetAgent.get
operationId: mantrae.v1.AgentService.GetAgent.get
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2303,9 +2207,9 @@ paths:
$ref: '#/components/schemas/mantrae.v1.GetAgentResponse'
post:
tags:
- mantrae.v1.AgentManagementService
- mantrae.v1.AgentService
summary: GetAgent
operationId: mantrae.v1.AgentManagementService.GetAgent
operationId: mantrae.v1.AgentService.GetAgent
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2335,12 +2239,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.GetAgentResponse'
/mantrae.v1.AgentManagementService/CreateAgent:
/mantrae.v1.AgentService/CreateAgent:
post:
tags:
- mantrae.v1.AgentManagementService
- mantrae.v1.AgentService
summary: CreateAgent
operationId: mantrae.v1.AgentManagementService.CreateAgent
operationId: mantrae.v1.AgentService.CreateAgent
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2370,12 +2274,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.CreateAgentResponse'
/mantrae.v1.AgentManagementService/UpdateAgentIP:
/mantrae.v1.AgentService/UpdateAgentIP:
post:
tags:
- mantrae.v1.AgentManagementService
- mantrae.v1.AgentService
summary: UpdateAgentIP
operationId: mantrae.v1.AgentManagementService.UpdateAgentIP
operationId: mantrae.v1.AgentService.UpdateAgentIP
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2405,12 +2309,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.UpdateAgentIPResponse'
/mantrae.v1.AgentManagementService/DeleteAgent:
/mantrae.v1.AgentService/DeleteAgent:
post:
tags:
- mantrae.v1.AgentManagementService
- mantrae.v1.AgentService
summary: DeleteAgent
operationId: mantrae.v1.AgentManagementService.DeleteAgent
operationId: mantrae.v1.AgentService.DeleteAgent
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2440,12 +2344,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.DeleteAgentResponse'
/mantrae.v1.AgentManagementService/ListAgents:
/mantrae.v1.AgentService/ListAgents:
get:
tags:
- mantrae.v1.AgentManagementService
- mantrae.v1.AgentService
summary: ListAgents
operationId: mantrae.v1.AgentManagementService.ListAgents.get
operationId: mantrae.v1.AgentService.ListAgents.get
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2494,9 +2398,9 @@ paths:
$ref: '#/components/schemas/mantrae.v1.ListAgentsResponse'
post:
tags:
- mantrae.v1.AgentManagementService
- mantrae.v1.AgentService
summary: ListAgents
operationId: mantrae.v1.AgentManagementService.ListAgents
operationId: mantrae.v1.AgentService.ListAgents
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2526,12 +2430,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.ListAgentsResponse'
/mantrae.v1.AgentManagementService/RotateAgentToken:
/mantrae.v1.AgentService/HealthCheck:
post:
tags:
- mantrae.v1.AgentManagementService
summary: RotateAgentToken
operationId: mantrae.v1.AgentManagementService.RotateAgentToken
- mantrae.v1.AgentService
summary: HealthCheck
operationId: mantrae.v1.AgentService.HealthCheck
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2546,7 +2450,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.RotateAgentTokenRequest'
$ref: '#/components/schemas/mantrae.v1.HealthCheckRequest'
required: true
responses:
default:
@@ -2560,13 +2464,13 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.RotateAgentTokenResponse'
/mantrae.v1.AgentManagementService/BootstrapAgent:
$ref: '#/components/schemas/mantrae.v1.HealthCheckResponse'
/mantrae.v1.AgentService/BootstrapAgent:
post:
tags:
- mantrae.v1.AgentManagementService
- mantrae.v1.AgentService
summary: BootstrapAgent
operationId: mantrae.v1.AgentManagementService.BootstrapAgent
operationId: mantrae.v1.AgentService.BootstrapAgent
parameters:
- name: Connect-Protocol-Version
in: header
@@ -2596,6 +2500,41 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.BootstrapAgentResponse'
/mantrae.v1.AgentService/RotateAgentToken:
post:
tags:
- mantrae.v1.AgentService
summary: RotateAgentToken
operationId: mantrae.v1.AgentService.RotateAgentToken
parameters:
- name: Connect-Protocol-Version
in: header
required: true
schema:
$ref: '#/components/schemas/connect-protocol-version'
- name: Connect-Timeout-Ms
in: header
schema:
$ref: '#/components/schemas/connect-timeout-header'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.RotateAgentTokenRequest'
required: true
responses:
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/connect.error'
"200":
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/mantrae.v1.RotateAgentTokenResponse'
/mantrae.v1.BackupService/CreateBackup:
post:
tags:
@@ -5427,7 +5366,6 @@ paths:
$ref: '#/components/schemas/mantrae.v1.GetPublicIPResponse'
tags:
- name: mantrae.v1.AgentService
- name: mantrae.v1.AgentManagementService
- name: mantrae.v1.BackupService
- name: mantrae.v1.DnsProviderService
- name: mantrae.v1.EntryPointService

View File

@@ -5,8 +5,31 @@ package mantrae.v1;
import "google/protobuf/timestamp.proto";
service AgentService {
rpc GetContainer(GetContainerRequest) returns (GetContainerResponse);
rpc GetAgent(GetAgentRequest) returns (GetAgentResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
rpc CreateAgent(CreateAgentRequest) returns (CreateAgentResponse);
rpc UpdateAgentIP(UpdateAgentIPRequest) returns (UpdateAgentIPResponse);
rpc DeleteAgent(DeleteAgentRequest) returns (DeleteAgentResponse);
rpc ListAgents(ListAgentsRequest) returns (ListAgentsResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
rpc BootstrapAgent(BootstrapAgentRequest) returns (BootstrapAgentResponse);
rpc RotateAgentToken(RotateAgentTokenRequest) returns (RotateAgentTokenResponse);
}
message Agent {
string id = 1;
int64 profile_id = 2;
string hostname = 3;
string public_ip = 4;
string private_ip = 5;
string active_ip = 6;
string token = 7;
repeated Container containers = 8;
google.protobuf.Timestamp created_at = 9;
google.protobuf.Timestamp updated_at = 10;
}
message Container {
@@ -19,17 +42,62 @@ message Container {
google.protobuf.Timestamp created = 7;
}
message GetContainerRequest {
string hostname = 1;
string public_ip = 2;
repeated string private_ips = 3;
repeated Container containers = 4;
google.protobuf.Timestamp updated = 5;
message GetAgentRequest {
string id = 1;
}
message GetAgentResponse {
Agent agent = 1;
}
message GetContainerResponse {}
message HealthCheckRequest {}
message CreateAgentRequest {
int64 profile_id = 1;
}
message CreateAgentResponse {
Agent agent = 1;
}
message UpdateAgentIPRequest {
string id = 1;
string ip = 2;
}
message UpdateAgentIPResponse {
Agent agent = 1;
}
message DeleteAgentRequest {
string id = 1;
}
message DeleteAgentResponse {}
message ListAgentsRequest {
int64 profile_id = 1;
optional int64 limit = 2;
optional int64 offset = 3;
}
message ListAgentsResponse {
repeated Agent agents = 1;
int64 total_count = 2;
}
message HealthCheckRequest {
string public_ip = 1;
string private_ip = 2;
}
message HealthCheckResponse {
bool ok = 1;
Agent agent = 1;
}
message RotateAgentTokenRequest {
string id = 1;
}
message RotateAgentTokenResponse {
string token = 1;
}
message BootstrapAgentRequest {
int64 profile_id = 1;
string token = 2;
}
message BootstrapAgentResponse {
string token = 1;
}

View File

@@ -1,85 +0,0 @@
syntax = "proto3";
package mantrae.v1;
import "google/protobuf/timestamp.proto";
import "mantrae/v1/agent.proto";
service AgentManagementService {
rpc GetAgent(GetAgentRequest) returns (GetAgentResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
rpc CreateAgent(CreateAgentRequest) returns (CreateAgentResponse);
rpc UpdateAgentIP(UpdateAgentIPRequest) returns (UpdateAgentIPResponse);
rpc DeleteAgent(DeleteAgentRequest) returns (DeleteAgentResponse);
rpc ListAgents(ListAgentsRequest) returns (ListAgentsResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
rpc RotateAgentToken(RotateAgentTokenRequest) returns (RotateAgentTokenResponse);
rpc BootstrapAgent(BootstrapAgentRequest) returns (BootstrapAgentResponse);
}
message Agent {
string id = 1;
int64 profile_id = 2;
string hostname = 3;
string public_ip = 4;
string active_ip = 5;
string token = 6;
repeated string private_ips = 7;
repeated Container containers = 8;
google.protobuf.Timestamp created_at = 9;
google.protobuf.Timestamp updated_at = 10;
}
message GetAgentRequest {
string id = 1;
}
message GetAgentResponse {
Agent agent = 1;
}
message CreateAgentRequest {
int64 profile_id = 1;
}
message CreateAgentResponse {
Agent agent = 1;
}
message UpdateAgentIPRequest {
string id = 1;
string ip = 2;
}
message UpdateAgentIPResponse {
Agent agent = 1;
}
message DeleteAgentRequest {
string id = 1;
}
message DeleteAgentResponse {}
message ListAgentsRequest {
int64 profile_id = 1;
optional int64 limit = 2;
optional int64 offset = 3;
}
message ListAgentsResponse {
repeated Agent agents = 1;
int64 total_count = 2;
}
message RotateAgentTokenRequest {
string id = 1;
}
message RotateAgentTokenResponse {
string token = 1;
}
message BootstrapAgentRequest {
int64 profile_id = 1;
string token = 2;
}
message BootstrapAgentResponse {
string token = 1;
}

View File

@@ -10,11 +10,11 @@ import { ServiceService } from './gen/mantrae/v1/service_pb';
import { MiddlewareService } from './gen/mantrae/v1/middleware_pb';
import { SettingService } from './gen/mantrae/v1/setting_pb';
import { BackupService } from './gen/mantrae/v1/backup_pb';
import { AgentManagementService } from './gen/mantrae/v1/agent_management_pb';
import { EntryPointService } from './gen/mantrae/v1/entry_point_pb';
import { DnsProviderService } from './gen/mantrae/v1/dns_provider_pb';
import { UtilService } from './gen/mantrae/v1/util_pb';
import { user } from './stores/user';
import { AgentService } from './gen/mantrae/v1/agent_pb';
// Global state variables
export const BACKEND_PORT = import.meta.env.PORT || 3000;
@@ -52,7 +52,7 @@ export const profileClient = useClient(ProfileService);
export const userClient = useClient(UserService);
export const entryPointClient = useClient(EntryPointService);
export const dnsClient = useClient(DnsProviderService);
export const agentClient = useClient(AgentManagementService);
export const agentClient = useClient(AgentService);
export const routerClient = useClient(RouterService);
export const serviceClient = useClient(ServiceService);
export const middlewareClient = useClient(MiddlewareService);

View File

@@ -9,11 +9,11 @@
import CopyButton from '../ui/copy-button/copy-button.svelte';
import { DateFormat, pageIndex, pageSize } from '$lib/stores/common';
import { RotateCcw } from '@lucide/svelte';
import type { Agent } from '$lib/gen/mantrae/v1/agent_management_pb';
import { agentClient } from '$lib/api';
import { profile } from '$lib/stores/profile';
import { ConnectError } from '@connectrpc/connect';
import { timestampDate } from '@bufbuild/protobuf/wkt';
import type { Agent } from '$lib/gen/mantrae/v1/agent_pb';
interface Props {
data: Agent[];
@@ -121,19 +121,17 @@
</div>
{/if}
{#if item.privateIps?.length > 0}
{#if item.privateIp !== ''}
<div class="grid grid-cols-4 items-center gap-2">
<Label for="privateip">Private IPs</Label>
<div class="col-span-3 flex flex-wrap gap-2">
{#each item.privateIps ?? [] as ip (ip)}
{#if item.activeIp === ip}
<Badge variant="default">{ip ?? 'None'}</Badge>
{:else}
<button onclick={() => handleSubmit(ip)}>
<Badge variant="secondary">{ip}</Badge>
</button>
{/if}
{/each}
{#if item.activeIp === item.privateIp}
<Badge variant="default">{item.privateIp ?? 'None'}</Badge>
{:else}
<button onclick={() => handleSubmit(item.privateIp)}>
<Badge variant="secondary">{item.privateIp}</Badge>
</button>
{/if}
</div>
</div>
{/if}

View File

@@ -1,402 +0,0 @@
// @generated by protoc-gen-es v2.5.2 with parameter "target=ts"
// @generated from file mantrae/v1/agent_management.proto (package mantrae.v1, syntax proto3)
/* eslint-disable */
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
import type { Timestamp } from "@bufbuild/protobuf/wkt";
import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
import type { Container } from "./agent_pb";
import { file_mantrae_v1_agent } from "./agent_pb";
import type { Message } from "@bufbuild/protobuf";
/**
* Describes the file mantrae/v1/agent_management.proto.
*/
export const file_mantrae_v1_agent_management: GenFile = /*@__PURE__*/
fileDesc("CiFtYW50cmFlL3YxL2FnZW50X21hbmFnZW1lbnQucHJvdG8SCm1hbnRyYWUudjEijgIKBUFnZW50EgoKAmlkGAEgASgJEhIKCnByb2ZpbGVfaWQYAiABKAMSEAoIaG9zdG5hbWUYAyABKAkSEQoJcHVibGljX2lwGAQgASgJEhEKCWFjdGl2ZV9pcBgFIAEoCRINCgV0b2tlbhgGIAEoCRITCgtwcml2YXRlX2lwcxgHIAMoCRIpCgpjb250YWluZXJzGAggAygLMhUubWFudHJhZS52MS5Db250YWluZXISLgoKY3JlYXRlZF9hdBgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiHQoPR2V0QWdlbnRSZXF1ZXN0EgoKAmlkGAEgASgJIjQKEEdldEFnZW50UmVzcG9uc2USIAoFYWdlbnQYASABKAsyES5tYW50cmFlLnYxLkFnZW50IigKEkNyZWF0ZUFnZW50UmVxdWVzdBISCgpwcm9maWxlX2lkGAEgASgDIjcKE0NyZWF0ZUFnZW50UmVzcG9uc2USIAoFYWdlbnQYASABKAsyES5tYW50cmFlLnYxLkFnZW50Ii4KFFVwZGF0ZUFnZW50SVBSZXF1ZXN0EgoKAmlkGAEgASgJEgoKAmlwGAIgASgJIjkKFVVwZGF0ZUFnZW50SVBSZXNwb25zZRIgCgVhZ2VudBgBIAEoCzIRLm1hbnRyYWUudjEuQWdlbnQiIAoSRGVsZXRlQWdlbnRSZXF1ZXN0EgoKAmlkGAEgASgJIhUKE0RlbGV0ZUFnZW50UmVzcG9uc2UiZQoRTGlzdEFnZW50c1JlcXVlc3QSEgoKcHJvZmlsZV9pZBgBIAEoAxISCgVsaW1pdBgCIAEoA0gAiAEBEhMKBm9mZnNldBgDIAEoA0gBiAEBQggKBl9saW1pdEIJCgdfb2Zmc2V0IkwKEkxpc3RBZ2VudHNSZXNwb25zZRIhCgZhZ2VudHMYASADKAsyES5tYW50cmFlLnYxLkFnZW50EhMKC3RvdGFsX2NvdW50GAIgASgDIiUKF1JvdGF0ZUFnZW50VG9rZW5SZXF1ZXN0EgoKAmlkGAEgASgJIikKGFJvdGF0ZUFnZW50VG9rZW5SZXNwb25zZRINCgV0b2tlbhgBIAEoCSI6ChVCb290c3RyYXBBZ2VudFJlcXVlc3QSEgoKcHJvZmlsZV9pZBgBIAEoAxINCgV0b2tlbhgCIAEoCSInChZCb290c3RyYXBBZ2VudFJlc3BvbnNlEg0KBXRva2VuGAEgASgJMuQEChZBZ2VudE1hbmFnZW1lbnRTZXJ2aWNlEkoKCEdldEFnZW50EhsubWFudHJhZS52MS5HZXRBZ2VudFJlcXVlc3QaHC5tYW50cmFlLnYxLkdldEFnZW50UmVzcG9uc2UiA5ACARJOCgtDcmVhdGVBZ2VudBIeLm1hbnRyYWUudjEuQ3JlYXRlQWdlbnRSZXF1ZXN0Gh8ubWFudHJhZS52MS5DcmVhdGVBZ2VudFJlc3BvbnNlElQKDVVwZGF0ZUFnZW50SVASIC5tYW50cmFlLnYxLlVwZGF0ZUFnZW50SVBSZXF1ZXN0GiEubWFudHJhZS52MS5VcGRhdGVBZ2VudElQUmVzcG9uc2USTgoLRGVsZXRlQWdlbnQSHi5tYW50cmFlLnYxLkRlbGV0ZUFnZW50UmVxdWVzdBofLm1hbnRyYWUudjEuRGVsZXRlQWdlbnRSZXNwb25zZRJQCgpMaXN0QWdlbnRzEh0ubWFudHJhZS52MS5MaXN0QWdlbnRzUmVxdWVzdBoeLm1hbnRyYWUudjEuTGlzdEFnZW50c1Jlc3BvbnNlIgOQAgESXQoQUm90YXRlQWdlbnRUb2tlbhIjLm1hbnRyYWUudjEuUm90YXRlQWdlbnRUb2tlblJlcXVlc3QaJC5tYW50cmFlLnYxLlJvdGF0ZUFnZW50VG9rZW5SZXNwb25zZRJXCg5Cb290c3RyYXBBZ2VudBIhLm1hbnRyYWUudjEuQm9vdHN0cmFwQWdlbnRSZXF1ZXN0GiIubWFudHJhZS52MS5Cb290c3RyYXBBZ2VudFJlc3BvbnNlQq4BCg5jb20ubWFudHJhZS52MUIUQWdlbnRNYW5hZ2VtZW50UHJvdG9QAVo9Z2l0aHViLmNvbS9taXp1Y2hpbGFicy9tYW50cmFlL3Byb3RvL2dlbi9tYW50cmFlL3YxO21hbnRyYWV2MaICA01YWKoCCk1hbnRyYWUuVjHKAgpNYW50cmFlXFYx4gIWTWFudHJhZVxWMVxHUEJNZXRhZGF0YeoCC01hbnRyYWU6OlYxYgZwcm90bzM", [file_google_protobuf_timestamp, file_mantrae_v1_agent]);
/**
* @generated from message mantrae.v1.Agent
*/
export type Agent = Message<"mantrae.v1.Agent"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
/**
* @generated from field: int64 profile_id = 2;
*/
profileId: bigint;
/**
* @generated from field: string hostname = 3;
*/
hostname: string;
/**
* @generated from field: string public_ip = 4;
*/
publicIp: string;
/**
* @generated from field: string active_ip = 5;
*/
activeIp: string;
/**
* @generated from field: string token = 6;
*/
token: string;
/**
* @generated from field: repeated string private_ips = 7;
*/
privateIps: string[];
/**
* @generated from field: repeated mantrae.v1.Container containers = 8;
*/
containers: Container[];
/**
* @generated from field: google.protobuf.Timestamp created_at = 9;
*/
createdAt?: Timestamp;
/**
* @generated from field: google.protobuf.Timestamp updated_at = 10;
*/
updatedAt?: Timestamp;
};
/**
* Describes the message mantrae.v1.Agent.
* Use `create(AgentSchema)` to create a new message.
*/
export const AgentSchema: GenMessage<Agent> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 0);
/**
* @generated from message mantrae.v1.GetAgentRequest
*/
export type GetAgentRequest = Message<"mantrae.v1.GetAgentRequest"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
};
/**
* Describes the message mantrae.v1.GetAgentRequest.
* Use `create(GetAgentRequestSchema)` to create a new message.
*/
export const GetAgentRequestSchema: GenMessage<GetAgentRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 1);
/**
* @generated from message mantrae.v1.GetAgentResponse
*/
export type GetAgentResponse = Message<"mantrae.v1.GetAgentResponse"> & {
/**
* @generated from field: mantrae.v1.Agent agent = 1;
*/
agent?: Agent;
};
/**
* Describes the message mantrae.v1.GetAgentResponse.
* Use `create(GetAgentResponseSchema)` to create a new message.
*/
export const GetAgentResponseSchema: GenMessage<GetAgentResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 2);
/**
* @generated from message mantrae.v1.CreateAgentRequest
*/
export type CreateAgentRequest = Message<"mantrae.v1.CreateAgentRequest"> & {
/**
* @generated from field: int64 profile_id = 1;
*/
profileId: bigint;
};
/**
* Describes the message mantrae.v1.CreateAgentRequest.
* Use `create(CreateAgentRequestSchema)` to create a new message.
*/
export const CreateAgentRequestSchema: GenMessage<CreateAgentRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 3);
/**
* @generated from message mantrae.v1.CreateAgentResponse
*/
export type CreateAgentResponse = Message<"mantrae.v1.CreateAgentResponse"> & {
/**
* @generated from field: mantrae.v1.Agent agent = 1;
*/
agent?: Agent;
};
/**
* Describes the message mantrae.v1.CreateAgentResponse.
* Use `create(CreateAgentResponseSchema)` to create a new message.
*/
export const CreateAgentResponseSchema: GenMessage<CreateAgentResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 4);
/**
* @generated from message mantrae.v1.UpdateAgentIPRequest
*/
export type UpdateAgentIPRequest = Message<"mantrae.v1.UpdateAgentIPRequest"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
/**
* @generated from field: string ip = 2;
*/
ip: string;
};
/**
* Describes the message mantrae.v1.UpdateAgentIPRequest.
* Use `create(UpdateAgentIPRequestSchema)` to create a new message.
*/
export const UpdateAgentIPRequestSchema: GenMessage<UpdateAgentIPRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 5);
/**
* @generated from message mantrae.v1.UpdateAgentIPResponse
*/
export type UpdateAgentIPResponse = Message<"mantrae.v1.UpdateAgentIPResponse"> & {
/**
* @generated from field: mantrae.v1.Agent agent = 1;
*/
agent?: Agent;
};
/**
* Describes the message mantrae.v1.UpdateAgentIPResponse.
* Use `create(UpdateAgentIPResponseSchema)` to create a new message.
*/
export const UpdateAgentIPResponseSchema: GenMessage<UpdateAgentIPResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 6);
/**
* @generated from message mantrae.v1.DeleteAgentRequest
*/
export type DeleteAgentRequest = Message<"mantrae.v1.DeleteAgentRequest"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
};
/**
* Describes the message mantrae.v1.DeleteAgentRequest.
* Use `create(DeleteAgentRequestSchema)` to create a new message.
*/
export const DeleteAgentRequestSchema: GenMessage<DeleteAgentRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 7);
/**
* @generated from message mantrae.v1.DeleteAgentResponse
*/
export type DeleteAgentResponse = Message<"mantrae.v1.DeleteAgentResponse"> & {
};
/**
* Describes the message mantrae.v1.DeleteAgentResponse.
* Use `create(DeleteAgentResponseSchema)` to create a new message.
*/
export const DeleteAgentResponseSchema: GenMessage<DeleteAgentResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 8);
/**
* @generated from message mantrae.v1.ListAgentsRequest
*/
export type ListAgentsRequest = Message<"mantrae.v1.ListAgentsRequest"> & {
/**
* @generated from field: int64 profile_id = 1;
*/
profileId: bigint;
/**
* @generated from field: optional int64 limit = 2;
*/
limit?: bigint;
/**
* @generated from field: optional int64 offset = 3;
*/
offset?: bigint;
};
/**
* Describes the message mantrae.v1.ListAgentsRequest.
* Use `create(ListAgentsRequestSchema)` to create a new message.
*/
export const ListAgentsRequestSchema: GenMessage<ListAgentsRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 9);
/**
* @generated from message mantrae.v1.ListAgentsResponse
*/
export type ListAgentsResponse = Message<"mantrae.v1.ListAgentsResponse"> & {
/**
* @generated from field: repeated mantrae.v1.Agent agents = 1;
*/
agents: Agent[];
/**
* @generated from field: int64 total_count = 2;
*/
totalCount: bigint;
};
/**
* Describes the message mantrae.v1.ListAgentsResponse.
* Use `create(ListAgentsResponseSchema)` to create a new message.
*/
export const ListAgentsResponseSchema: GenMessage<ListAgentsResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 10);
/**
* @generated from message mantrae.v1.RotateAgentTokenRequest
*/
export type RotateAgentTokenRequest = Message<"mantrae.v1.RotateAgentTokenRequest"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
};
/**
* Describes the message mantrae.v1.RotateAgentTokenRequest.
* Use `create(RotateAgentTokenRequestSchema)` to create a new message.
*/
export const RotateAgentTokenRequestSchema: GenMessage<RotateAgentTokenRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 11);
/**
* @generated from message mantrae.v1.RotateAgentTokenResponse
*/
export type RotateAgentTokenResponse = Message<"mantrae.v1.RotateAgentTokenResponse"> & {
/**
* @generated from field: string token = 1;
*/
token: string;
};
/**
* Describes the message mantrae.v1.RotateAgentTokenResponse.
* Use `create(RotateAgentTokenResponseSchema)` to create a new message.
*/
export const RotateAgentTokenResponseSchema: GenMessage<RotateAgentTokenResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 12);
/**
* @generated from message mantrae.v1.BootstrapAgentRequest
*/
export type BootstrapAgentRequest = Message<"mantrae.v1.BootstrapAgentRequest"> & {
/**
* @generated from field: int64 profile_id = 1;
*/
profileId: bigint;
/**
* @generated from field: string token = 2;
*/
token: string;
};
/**
* Describes the message mantrae.v1.BootstrapAgentRequest.
* Use `create(BootstrapAgentRequestSchema)` to create a new message.
*/
export const BootstrapAgentRequestSchema: GenMessage<BootstrapAgentRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 13);
/**
* @generated from message mantrae.v1.BootstrapAgentResponse
*/
export type BootstrapAgentResponse = Message<"mantrae.v1.BootstrapAgentResponse"> & {
/**
* @generated from field: string token = 1;
*/
token: string;
};
/**
* Describes the message mantrae.v1.BootstrapAgentResponse.
* Use `create(BootstrapAgentResponseSchema)` to create a new message.
*/
export const BootstrapAgentResponseSchema: GenMessage<BootstrapAgentResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent_management, 14);
/**
* @generated from service mantrae.v1.AgentManagementService
*/
export const AgentManagementService: GenService<{
/**
* @generated from rpc mantrae.v1.AgentManagementService.GetAgent
*/
getAgent: {
methodKind: "unary";
input: typeof GetAgentRequestSchema;
output: typeof GetAgentResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentManagementService.CreateAgent
*/
createAgent: {
methodKind: "unary";
input: typeof CreateAgentRequestSchema;
output: typeof CreateAgentResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentManagementService.UpdateAgentIP
*/
updateAgentIP: {
methodKind: "unary";
input: typeof UpdateAgentIPRequestSchema;
output: typeof UpdateAgentIPResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentManagementService.DeleteAgent
*/
deleteAgent: {
methodKind: "unary";
input: typeof DeleteAgentRequestSchema;
output: typeof DeleteAgentResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentManagementService.ListAgents
*/
listAgents: {
methodKind: "unary";
input: typeof ListAgentsRequestSchema;
output: typeof ListAgentsResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentManagementService.RotateAgentToken
*/
rotateAgentToken: {
methodKind: "unary";
input: typeof RotateAgentTokenRequestSchema;
output: typeof RotateAgentTokenResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentManagementService.BootstrapAgent
*/
bootstrapAgent: {
methodKind: "unary";
input: typeof BootstrapAgentRequestSchema;
output: typeof BootstrapAgentResponseSchema;
},
}> = /*@__PURE__*/
serviceDesc(file_mantrae_v1_agent_management, 0);

View File

@@ -12,7 +12,69 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file mantrae/v1/agent.proto.
*/
export const file_mantrae_v1_agent: GenFile = /*@__PURE__*/
fileDesc("ChZtYW50cmFlL3YxL2FnZW50LnByb3RvEgptYW50cmFlLnYxIrgCCglDb250YWluZXISCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRIxCgZsYWJlbHMYAyADKAsyIS5tYW50cmFlLnYxLkNvbnRhaW5lci5MYWJlbHNFbnRyeRINCgVpbWFnZRgEIAEoCRIzCgdwb3J0bWFwGAUgAygLMiIubWFudHJhZS52MS5Db250YWluZXIuUG9ydG1hcEVudHJ5Eg4KBnN0YXR1cxgGIAEoCRIrCgdjcmVhdGVkGAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBotCgtMYWJlbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGi4KDFBvcnRtYXBFbnRyeRILCgNrZXkYASABKAUSDQoFdmFsdWUYAiABKAU6AjgBIqcBChNHZXRDb250YWluZXJSZXF1ZXN0EhAKCGhvc3RuYW1lGAEgASgJEhEKCXB1YmxpY19pcBgCIAEoCRITCgtwcml2YXRlX2lwcxgDIAMoCRIpCgpjb250YWluZXJzGAQgAygLMhUubWFudHJhZS52MS5Db250YWluZXISKwoHdXBkYXRlZBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiFgoUR2V0Q29udGFpbmVyUmVzcG9uc2UiFAoSSGVhbHRoQ2hlY2tSZXF1ZXN0IjAKE0hlYWx0aENoZWNrUmVzcG9uc2USCgoCb2sYASABKAgSDQoFdG9rZW4YAiABKAkysQEKDEFnZW50U2VydmljZRJRCgxHZXRDb250YWluZXISHy5tYW50cmFlLnYxLkdldENvbnRhaW5lclJlcXVlc3QaIC5tYW50cmFlLnYxLkdldENvbnRhaW5lclJlc3BvbnNlEk4KC0hlYWx0aENoZWNrEh4ubWFudHJhZS52MS5IZWFsdGhDaGVja1JlcXVlc3QaHy5tYW50cmFlLnYxLkhlYWx0aENoZWNrUmVzcG9uc2VCpAEKDmNvbS5tYW50cmFlLnYxQgpBZ2VudFByb3RvUAFaPWdpdGh1Yi5jb20vbWl6dWNoaWxhYnMvbWFudHJhZS9wcm90by9nZW4vbWFudHJhZS92MTttYW50cmFldjGiAgNNWFiqAgpNYW50cmFlLlYxygIKTWFudHJhZVxWMeICFk1hbnRyYWVcVjFcR1BCTWV0YWRhdGHqAgtNYW50cmFlOjpWMWIGcHJvdG8z", [file_google_protobuf_timestamp]);
fileDesc("ChZtYW50cmFlL3YxL2FnZW50LnByb3RvEgptYW50cmFlLnYxIo0CCgVBZ2VudBIKCgJpZBgBIAEoCRISCgpwcm9maWxlX2lkGAIgASgDEhAKCGhvc3RuYW1lGAMgASgJEhEKCXB1YmxpY19pcBgEIAEoCRISCgpwcml2YXRlX2lwGAUgASgJEhEKCWFjdGl2ZV9pcBgGIAEoCRINCgV0b2tlbhgHIAEoCRIpCgpjb250YWluZXJzGAggAygLMhUubWFudHJhZS52MS5Db250YWluZXISLgoKY3JlYXRlZF9hdBgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiuAIKCUNvbnRhaW5lchIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEjEKBmxhYmVscxgDIAMoCzIhLm1hbnRyYWUudjEuQ29udGFpbmVyLkxhYmVsc0VudHJ5Eg0KBWltYWdlGAQgASgJEjMKB3BvcnRtYXAYBSADKAsyIi5tYW50cmFlLnYxLkNvbnRhaW5lci5Qb3J0bWFwRW50cnkSDgoGc3RhdHVzGAYgASgJEisKB2NyZWF0ZWQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEaLgoMUG9ydG1hcEVudHJ5EgsKA2tleRgBIAEoBRINCgV2YWx1ZRgCIAEoBToCOAEiHQoPR2V0QWdlbnRSZXF1ZXN0EgoKAmlkGAEgASgJIjQKEEdldEFnZW50UmVzcG9uc2USIAoFYWdlbnQYASABKAsyES5tYW50cmFlLnYxLkFnZW50IigKEkNyZWF0ZUFnZW50UmVxdWVzdBISCgpwcm9maWxlX2lkGAEgASgDIjcKE0NyZWF0ZUFnZW50UmVzcG9uc2USIAoFYWdlbnQYASABKAsyES5tYW50cmFlLnYxLkFnZW50Ii4KFFVwZGF0ZUFnZW50SVBSZXF1ZXN0EgoKAmlkGAEgASgJEgoKAmlwGAIgASgJIjkKFVVwZGF0ZUFnZW50SVBSZXNwb25zZRIgCgVhZ2VudBgBIAEoCzIRLm1hbnRyYWUudjEuQWdlbnQiIAoSRGVsZXRlQWdlbnRSZXF1ZXN0EgoKAmlkGAEgASgJIhUKE0RlbGV0ZUFnZW50UmVzcG9uc2UiZQoRTGlzdEFnZW50c1JlcXVlc3QSEgoKcHJvZmlsZV9pZBgBIAEoAxISCgVsaW1pdBgCIAEoA0gAiAEBEhMKBm9mZnNldBgDIAEoA0gBiAEBQggKBl9saW1pdEIJCgdfb2Zmc2V0IkwKEkxpc3RBZ2VudHNSZXNwb25zZRIhCgZhZ2VudHMYASADKAsyES5tYW50cmFlLnYxLkFnZW50EhMKC3RvdGFsX2NvdW50GAIgASgDIjsKEkhlYWx0aENoZWNrUmVxdWVzdBIRCglwdWJsaWNfaXAYASABKAkSEgoKcHJpdmF0ZV9pcBgCIAEoCSI3ChNIZWFsdGhDaGVja1Jlc3BvbnNlEiAKBWFnZW50GAEgASgLMhEubWFudHJhZS52MS5BZ2VudCIlChdSb3RhdGVBZ2VudFRva2VuUmVxdWVzdBIKCgJpZBgBIAEoCSIpChhSb3RhdGVBZ2VudFRva2VuUmVzcG9uc2USDQoFdG9rZW4YASABKAkiOgoVQm9vdHN0cmFwQWdlbnRSZXF1ZXN0EhIKCnByb2ZpbGVfaWQYASABKAMSDQoFdG9rZW4YAiABKAkiJwoWQm9vdHN0cmFwQWdlbnRSZXNwb25zZRINCgV0b2tlbhgBIAEoCTKqBQoMQWdlbnRTZXJ2aWNlEkoKCEdldEFnZW50EhsubWFudHJhZS52MS5HZXRBZ2VudFJlcXVlc3QaHC5tYW50cmFlLnYxLkdldEFnZW50UmVzcG9uc2UiA5ACARJOCgtDcmVhdGVBZ2VudBIeLm1hbnRyYWUudjEuQ3JlYXRlQWdlbnRSZXF1ZXN0Gh8ubWFudHJhZS52MS5DcmVhdGVBZ2VudFJlc3BvbnNlElQKDVVwZGF0ZUFnZW50SVASIC5tYW50cmFlLnYxLlVwZGF0ZUFnZW50SVBSZXF1ZXN0GiEubWFudHJhZS52MS5VcGRhdGVBZ2VudElQUmVzcG9uc2USTgoLRGVsZXRlQWdlbnQSHi5tYW50cmFlLnYxLkRlbGV0ZUFnZW50UmVxdWVzdBofLm1hbnRyYWUudjEuRGVsZXRlQWdlbnRSZXNwb25zZRJQCgpMaXN0QWdlbnRzEh0ubWFudHJhZS52MS5MaXN0QWdlbnRzUmVxdWVzdBoeLm1hbnRyYWUudjEuTGlzdEFnZW50c1Jlc3BvbnNlIgOQAgESTgoLSGVhbHRoQ2hlY2sSHi5tYW50cmFlLnYxLkhlYWx0aENoZWNrUmVxdWVzdBofLm1hbnRyYWUudjEuSGVhbHRoQ2hlY2tSZXNwb25zZRJXCg5Cb290c3RyYXBBZ2VudBIhLm1hbnRyYWUudjEuQm9vdHN0cmFwQWdlbnRSZXF1ZXN0GiIubWFudHJhZS52MS5Cb290c3RyYXBBZ2VudFJlc3BvbnNlEl0KEFJvdGF0ZUFnZW50VG9rZW4SIy5tYW50cmFlLnYxLlJvdGF0ZUFnZW50VG9rZW5SZXF1ZXN0GiQubWFudHJhZS52MS5Sb3RhdGVBZ2VudFRva2VuUmVzcG9uc2VCpAEKDmNvbS5tYW50cmFlLnYxQgpBZ2VudFByb3RvUAFaPWdpdGh1Yi5jb20vbWl6dWNoaWxhYnMvbWFudHJhZS9wcm90by9nZW4vbWFudHJhZS92MTttYW50cmFldjGiAgNNWFiqAgpNYW50cmFlLlYxygIKTWFudHJhZVxWMeICFk1hbnRyYWVcVjFcR1BCTWV0YWRhdGHqAgtNYW50cmFlOjpWMWIGcHJvdG8z", [file_google_protobuf_timestamp]);
/**
* @generated from message mantrae.v1.Agent
*/
export type Agent = Message<"mantrae.v1.Agent"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
/**
* @generated from field: int64 profile_id = 2;
*/
profileId: bigint;
/**
* @generated from field: string hostname = 3;
*/
hostname: string;
/**
* @generated from field: string public_ip = 4;
*/
publicIp: string;
/**
* @generated from field: string private_ip = 5;
*/
privateIp: string;
/**
* @generated from field: string active_ip = 6;
*/
activeIp: string;
/**
* @generated from field: string token = 7;
*/
token: string;
/**
* @generated from field: repeated mantrae.v1.Container containers = 8;
*/
containers: Container[];
/**
* @generated from field: google.protobuf.Timestamp created_at = 9;
*/
createdAt?: Timestamp;
/**
* @generated from field: google.protobuf.Timestamp updated_at = 10;
*/
updatedAt?: Timestamp;
};
/**
* Describes the message mantrae.v1.Agent.
* Use `create(AgentSchema)` to create a new message.
*/
export const AgentSchema: GenMessage<Agent> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 0);
/**
* @generated from message mantrae.v1.Container
@@ -59,62 +121,207 @@ export type Container = Message<"mantrae.v1.Container"> & {
* Use `create(ContainerSchema)` to create a new message.
*/
export const ContainerSchema: GenMessage<Container> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 0);
/**
* @generated from message mantrae.v1.GetContainerRequest
*/
export type GetContainerRequest = Message<"mantrae.v1.GetContainerRequest"> & {
/**
* @generated from field: string hostname = 1;
*/
hostname: string;
/**
* @generated from field: string public_ip = 2;
*/
publicIp: string;
/**
* @generated from field: repeated string private_ips = 3;
*/
privateIps: string[];
/**
* @generated from field: repeated mantrae.v1.Container containers = 4;
*/
containers: Container[];
/**
* @generated from field: google.protobuf.Timestamp updated = 5;
*/
updated?: Timestamp;
};
/**
* Describes the message mantrae.v1.GetContainerRequest.
* Use `create(GetContainerRequestSchema)` to create a new message.
*/
export const GetContainerRequestSchema: GenMessage<GetContainerRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 1);
/**
* @generated from message mantrae.v1.GetContainerResponse
* @generated from message mantrae.v1.GetAgentRequest
*/
export type GetContainerResponse = Message<"mantrae.v1.GetContainerResponse"> & {
export type GetAgentRequest = Message<"mantrae.v1.GetAgentRequest"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
};
/**
* Describes the message mantrae.v1.GetContainerResponse.
* Use `create(GetContainerResponseSchema)` to create a new message.
* Describes the message mantrae.v1.GetAgentRequest.
* Use `create(GetAgentRequestSchema)` to create a new message.
*/
export const GetContainerResponseSchema: GenMessage<GetContainerResponse> = /*@__PURE__*/
export const GetAgentRequestSchema: GenMessage<GetAgentRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 2);
/**
* @generated from message mantrae.v1.GetAgentResponse
*/
export type GetAgentResponse = Message<"mantrae.v1.GetAgentResponse"> & {
/**
* @generated from field: mantrae.v1.Agent agent = 1;
*/
agent?: Agent;
};
/**
* Describes the message mantrae.v1.GetAgentResponse.
* Use `create(GetAgentResponseSchema)` to create a new message.
*/
export const GetAgentResponseSchema: GenMessage<GetAgentResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 3);
/**
* @generated from message mantrae.v1.CreateAgentRequest
*/
export type CreateAgentRequest = Message<"mantrae.v1.CreateAgentRequest"> & {
/**
* @generated from field: int64 profile_id = 1;
*/
profileId: bigint;
};
/**
* Describes the message mantrae.v1.CreateAgentRequest.
* Use `create(CreateAgentRequestSchema)` to create a new message.
*/
export const CreateAgentRequestSchema: GenMessage<CreateAgentRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 4);
/**
* @generated from message mantrae.v1.CreateAgentResponse
*/
export type CreateAgentResponse = Message<"mantrae.v1.CreateAgentResponse"> & {
/**
* @generated from field: mantrae.v1.Agent agent = 1;
*/
agent?: Agent;
};
/**
* Describes the message mantrae.v1.CreateAgentResponse.
* Use `create(CreateAgentResponseSchema)` to create a new message.
*/
export const CreateAgentResponseSchema: GenMessage<CreateAgentResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 5);
/**
* @generated from message mantrae.v1.UpdateAgentIPRequest
*/
export type UpdateAgentIPRequest = Message<"mantrae.v1.UpdateAgentIPRequest"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
/**
* @generated from field: string ip = 2;
*/
ip: string;
};
/**
* Describes the message mantrae.v1.UpdateAgentIPRequest.
* Use `create(UpdateAgentIPRequestSchema)` to create a new message.
*/
export const UpdateAgentIPRequestSchema: GenMessage<UpdateAgentIPRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 6);
/**
* @generated from message mantrae.v1.UpdateAgentIPResponse
*/
export type UpdateAgentIPResponse = Message<"mantrae.v1.UpdateAgentIPResponse"> & {
/**
* @generated from field: mantrae.v1.Agent agent = 1;
*/
agent?: Agent;
};
/**
* Describes the message mantrae.v1.UpdateAgentIPResponse.
* Use `create(UpdateAgentIPResponseSchema)` to create a new message.
*/
export const UpdateAgentIPResponseSchema: GenMessage<UpdateAgentIPResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 7);
/**
* @generated from message mantrae.v1.DeleteAgentRequest
*/
export type DeleteAgentRequest = Message<"mantrae.v1.DeleteAgentRequest"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
};
/**
* Describes the message mantrae.v1.DeleteAgentRequest.
* Use `create(DeleteAgentRequestSchema)` to create a new message.
*/
export const DeleteAgentRequestSchema: GenMessage<DeleteAgentRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 8);
/**
* @generated from message mantrae.v1.DeleteAgentResponse
*/
export type DeleteAgentResponse = Message<"mantrae.v1.DeleteAgentResponse"> & {
};
/**
* Describes the message mantrae.v1.DeleteAgentResponse.
* Use `create(DeleteAgentResponseSchema)` to create a new message.
*/
export const DeleteAgentResponseSchema: GenMessage<DeleteAgentResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 9);
/**
* @generated from message mantrae.v1.ListAgentsRequest
*/
export type ListAgentsRequest = Message<"mantrae.v1.ListAgentsRequest"> & {
/**
* @generated from field: int64 profile_id = 1;
*/
profileId: bigint;
/**
* @generated from field: optional int64 limit = 2;
*/
limit?: bigint;
/**
* @generated from field: optional int64 offset = 3;
*/
offset?: bigint;
};
/**
* Describes the message mantrae.v1.ListAgentsRequest.
* Use `create(ListAgentsRequestSchema)` to create a new message.
*/
export const ListAgentsRequestSchema: GenMessage<ListAgentsRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 10);
/**
* @generated from message mantrae.v1.ListAgentsResponse
*/
export type ListAgentsResponse = Message<"mantrae.v1.ListAgentsResponse"> & {
/**
* @generated from field: repeated mantrae.v1.Agent agents = 1;
*/
agents: Agent[];
/**
* @generated from field: int64 total_count = 2;
*/
totalCount: bigint;
};
/**
* Describes the message mantrae.v1.ListAgentsResponse.
* Use `create(ListAgentsResponseSchema)` to create a new message.
*/
export const ListAgentsResponseSchema: GenMessage<ListAgentsResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 11);
/**
* @generated from message mantrae.v1.HealthCheckRequest
*/
export type HealthCheckRequest = Message<"mantrae.v1.HealthCheckRequest"> & {
/**
* @generated from field: string public_ip = 1;
*/
publicIp: string;
/**
* @generated from field: string private_ip = 2;
*/
privateIp: string;
};
/**
@@ -122,16 +329,67 @@ export type HealthCheckRequest = Message<"mantrae.v1.HealthCheckRequest"> & {
* Use `create(HealthCheckRequestSchema)` to create a new message.
*/
export const HealthCheckRequestSchema: GenMessage<HealthCheckRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 3);
messageDesc(file_mantrae_v1_agent, 12);
/**
* @generated from message mantrae.v1.HealthCheckResponse
*/
export type HealthCheckResponse = Message<"mantrae.v1.HealthCheckResponse"> & {
/**
* @generated from field: bool ok = 1;
* @generated from field: mantrae.v1.Agent agent = 1;
*/
ok: boolean;
agent?: Agent;
};
/**
* Describes the message mantrae.v1.HealthCheckResponse.
* Use `create(HealthCheckResponseSchema)` to create a new message.
*/
export const HealthCheckResponseSchema: GenMessage<HealthCheckResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 13);
/**
* @generated from message mantrae.v1.RotateAgentTokenRequest
*/
export type RotateAgentTokenRequest = Message<"mantrae.v1.RotateAgentTokenRequest"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
};
/**
* Describes the message mantrae.v1.RotateAgentTokenRequest.
* Use `create(RotateAgentTokenRequestSchema)` to create a new message.
*/
export const RotateAgentTokenRequestSchema: GenMessage<RotateAgentTokenRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 14);
/**
* @generated from message mantrae.v1.RotateAgentTokenResponse
*/
export type RotateAgentTokenResponse = Message<"mantrae.v1.RotateAgentTokenResponse"> & {
/**
* @generated from field: string token = 1;
*/
token: string;
};
/**
* Describes the message mantrae.v1.RotateAgentTokenResponse.
* Use `create(RotateAgentTokenResponseSchema)` to create a new message.
*/
export const RotateAgentTokenResponseSchema: GenMessage<RotateAgentTokenResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 15);
/**
* @generated from message mantrae.v1.BootstrapAgentRequest
*/
export type BootstrapAgentRequest = Message<"mantrae.v1.BootstrapAgentRequest"> & {
/**
* @generated from field: int64 profile_id = 1;
*/
profileId: bigint;
/**
* @generated from field: string token = 2;
@@ -140,23 +398,72 @@ export type HealthCheckResponse = Message<"mantrae.v1.HealthCheckResponse"> & {
};
/**
* Describes the message mantrae.v1.HealthCheckResponse.
* Use `create(HealthCheckResponseSchema)` to create a new message.
* Describes the message mantrae.v1.BootstrapAgentRequest.
* Use `create(BootstrapAgentRequestSchema)` to create a new message.
*/
export const HealthCheckResponseSchema: GenMessage<HealthCheckResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 4);
export const BootstrapAgentRequestSchema: GenMessage<BootstrapAgentRequest> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 16);
/**
* @generated from message mantrae.v1.BootstrapAgentResponse
*/
export type BootstrapAgentResponse = Message<"mantrae.v1.BootstrapAgentResponse"> & {
/**
* @generated from field: string token = 1;
*/
token: string;
};
/**
* Describes the message mantrae.v1.BootstrapAgentResponse.
* Use `create(BootstrapAgentResponseSchema)` to create a new message.
*/
export const BootstrapAgentResponseSchema: GenMessage<BootstrapAgentResponse> = /*@__PURE__*/
messageDesc(file_mantrae_v1_agent, 17);
/**
* @generated from service mantrae.v1.AgentService
*/
export const AgentService: GenService<{
/**
* @generated from rpc mantrae.v1.AgentService.GetContainer
* @generated from rpc mantrae.v1.AgentService.GetAgent
*/
getContainer: {
getAgent: {
methodKind: "unary";
input: typeof GetContainerRequestSchema;
output: typeof GetContainerResponseSchema;
input: typeof GetAgentRequestSchema;
output: typeof GetAgentResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentService.CreateAgent
*/
createAgent: {
methodKind: "unary";
input: typeof CreateAgentRequestSchema;
output: typeof CreateAgentResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentService.UpdateAgentIP
*/
updateAgentIP: {
methodKind: "unary";
input: typeof UpdateAgentIPRequestSchema;
output: typeof UpdateAgentIPResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentService.DeleteAgent
*/
deleteAgent: {
methodKind: "unary";
input: typeof DeleteAgentRequestSchema;
output: typeof DeleteAgentResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentService.ListAgents
*/
listAgents: {
methodKind: "unary";
input: typeof ListAgentsRequestSchema;
output: typeof ListAgentsResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentService.HealthCheck
@@ -166,6 +473,22 @@ export const AgentService: GenService<{
input: typeof HealthCheckRequestSchema;
output: typeof HealthCheckResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentService.BootstrapAgent
*/
bootstrapAgent: {
methodKind: "unary";
input: typeof BootstrapAgentRequestSchema;
output: typeof BootstrapAgentResponseSchema;
},
/**
* @generated from rpc mantrae.v1.AgentService.RotateAgentToken
*/
rotateAgentToken: {
methodKind: "unary";
input: typeof RotateAgentTokenRequestSchema;
output: typeof RotateAgentTokenResponseSchema;
},
}> = /*@__PURE__*/
serviceDesc(file_mantrae_v1_agent, 0);

View File

@@ -5,7 +5,7 @@
import TableActions from '$lib/components/tables/TableActions.svelte';
import type { BulkAction } from '$lib/components/tables/types';
import { renderComponent } from '$lib/components/ui/data-table';
import type { Agent } from '$lib/gen/mantrae/v1/agent_management_pb';
import type { Agent } from '$lib/gen/mantrae/v1/agent_pb';
import { DateFormat, pageIndex, pageSize } from '$lib/stores/common';
import { profile } from '$lib/stores/profile';
import { timestampDate, type Timestamp } from '@bufbuild/protobuf/wkt';