feat(agent): serve status page
This commit is contained in:
parent
eee7e60a86
commit
117f5a05a1
|
@ -38,6 +38,7 @@ func (a *Agent) Run(ctx context.Context) error {
|
||||||
client := client.New(a.serverURL, client.WithToken(token))
|
client := client.New(a.serverURL, client.WithToken(token))
|
||||||
|
|
||||||
ctx = withClient(ctx, client)
|
ctx = withClient(ctx, client)
|
||||||
|
ctx = withThumbprint(ctx, a.thumbprint)
|
||||||
|
|
||||||
tick := func() {
|
tick := func() {
|
||||||
logger.Debug(ctx, "registering agent")
|
logger.Debug(ctx, "registering agent")
|
||||||
|
|
|
@ -11,6 +11,7 @@ type contextKey string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
contextKeyClient contextKey = "client"
|
contextKeyClient contextKey = "client"
|
||||||
|
contextKeyThumbprint contextKey = "thumbprint"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withClient(ctx context.Context, client *client.Client) context.Context {
|
func withClient(ctx context.Context, client *client.Client) context.Context {
|
||||||
|
@ -25,3 +26,16 @@ func Client(ctx context.Context) *client.Client {
|
||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withThumbprint(ctx context.Context, thumbprint string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyThumbprint, thumbprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Thumbprint(ctx context.Context) string {
|
||||||
|
thumbprint, ok := ctx.Value(contextKeyThumbprint).(string)
|
||||||
|
if !ok {
|
||||||
|
panic(errors.New("could not retrieve thumbprint from context"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbprint
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
package registration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Agent *datastore.Agent
|
||||||
|
Connected bool
|
||||||
|
Claimed bool
|
||||||
|
Thumbprint string
|
||||||
|
ServerURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
status *atomic.Value
|
||||||
|
server *atomic.Value
|
||||||
|
addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements node.Controller.
|
||||||
|
func (c *Controller) Name() string {
|
||||||
|
return "registration-controller"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile implements node.Controller.
|
||||||
|
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
||||||
|
cl := agent.Client(ctx)
|
||||||
|
thumbprint := agent.Thumbprint(ctx)
|
||||||
|
|
||||||
|
connected := true
|
||||||
|
agent, err := cl.GetAgent(
|
||||||
|
ctx,
|
||||||
|
state.AgentID(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "could not get agent", logger.E(errors.WithStack(err)))
|
||||||
|
connected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
claimed := agent != nil && agent.TenantID != nil
|
||||||
|
|
||||||
|
c.status.Store(Status{
|
||||||
|
Agent: agent,
|
||||||
|
Connected: connected,
|
||||||
|
Claimed: claimed,
|
||||||
|
Thumbprint: thumbprint,
|
||||||
|
ServerURL: cl.ServerURL(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := c.reconcileAgent(ctx, connected, claimed); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) reconcileAgent(ctx context.Context, connected bool, claimed bool) error {
|
||||||
|
shouldStart := !connected || !claimed
|
||||||
|
|
||||||
|
if shouldStart {
|
||||||
|
if err := c.startServer(ctx); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := c.stopServer(ctx); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) startServer(ctx context.Context) error {
|
||||||
|
server := c.getServer()
|
||||||
|
if server != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
server = &http.Server{
|
||||||
|
Addr: c.addr,
|
||||||
|
Handler: &Handler{
|
||||||
|
status: c.status,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer c.setServer(nil)
|
||||||
|
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
logger.Error(ctx, "could not start server", logger.E(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.setServer(server)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) stopServer(ctx context.Context) error {
|
||||||
|
server := c.getServer()
|
||||||
|
if server == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer c.setServer(nil)
|
||||||
|
|
||||||
|
if err := server.Close(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) setServer(s *http.Server) {
|
||||||
|
c.server.Store(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) getServer() *http.Server {
|
||||||
|
server, ok := c.server.Load().(*http.Server)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(addr string) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
addr: addr,
|
||||||
|
status: &atomic.Value{},
|
||||||
|
server: &atomic.Value{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ agent.Controller = &Controller{}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package registration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed templates/*.gotpl
|
||||||
|
var templates embed.FS
|
||||||
|
|
||||||
|
//go:embed public/*
|
||||||
|
var public embed.FS
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
status *atomic.Value
|
||||||
|
|
||||||
|
public http.Handler
|
||||||
|
templates *template.Template
|
||||||
|
|
||||||
|
init sync.Once
|
||||||
|
initErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements http.Handler.
|
||||||
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.init.Do(func() {
|
||||||
|
root, err := fs.Sub(public, "public")
|
||||||
|
if err != nil {
|
||||||
|
h.initErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.public = http.FileServer(http.FS(root))
|
||||||
|
|
||||||
|
tmpl, err := template.ParseFS(templates, "templates/*.gotpl")
|
||||||
|
if err != nil {
|
||||||
|
h.initErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.templates = tmpl
|
||||||
|
})
|
||||||
|
if h.initErr != nil {
|
||||||
|
logger.Error(r.Context(), "could not initialize handler", logger.E(h.initErr))
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/":
|
||||||
|
h.serveIndex(w, r)
|
||||||
|
default:
|
||||||
|
h.public.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := h.status.Load()
|
||||||
|
|
||||||
|
if err := h.templates.ExecuteTemplate(w, "index.html.gotpl", data); err != nil {
|
||||||
|
logger.Error(r.Context(), "could not render template", logger.E(errors.WithStack(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ http.Handler = &Handler{}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Emissary</h1>
|
||||||
|
<ul>
|
||||||
|
<li>Server URL: {{ .ServerURL }}</li>
|
||||||
|
<li>Claimed: {{if .Claimed}}true{{else}}false{{end}}</li>
|
||||||
|
<li>Connected: {{if .Connected}}true{{else}}false{{end}}</li>
|
||||||
|
<li>Thumbprint: <code>{{ .Thumbprint }}</code></li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/persistence"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/persistence"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/proxy"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/proxy"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/registration"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/spec"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/spec"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata/collector/buildinfo"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata/collector/buildinfo"
|
||||||
|
@ -94,6 +95,12 @@ func RunCommand() *cli.Command {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctrlConf.Registration.Enabled {
|
||||||
|
controllers = append(controllers, registration.NewController(
|
||||||
|
string(ctrlConf.Registration.Address),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
key, err := jwk.LoadOrGenerate(string(conf.Agent.PrivateKeyPath), jwk.DefaultKeySize)
|
key, err := jwk.LoadOrGenerate(string(conf.Agent.PrivateKeyPath), jwk.DefaultKeySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
|
|
|
@ -24,6 +24,7 @@ type ControllersConfig struct {
|
||||||
App AppControllerConfig `yaml:"app"`
|
App AppControllerConfig `yaml:"app"`
|
||||||
SysUpgrade SysUpgradeControllerConfig `yaml:"sysupgrade"`
|
SysUpgrade SysUpgradeControllerConfig `yaml:"sysupgrade"`
|
||||||
MDNS MDNSControllerConfig `yaml:"mdns"`
|
MDNS MDNSControllerConfig `yaml:"mdns"`
|
||||||
|
Registration RegistrationControllerConfig `yaml:"registration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PersistenceControllerConfig struct {
|
type PersistenceControllerConfig struct {
|
||||||
|
@ -60,6 +61,11 @@ type MDNSControllerConfig struct {
|
||||||
Enabled InterpolatedBool `yaml:"enabled"`
|
Enabled InterpolatedBool `yaml:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RegistrationControllerConfig struct {
|
||||||
|
Enabled InterpolatedBool `yaml:"enabled"`
|
||||||
|
Address InterpolatedString `yaml:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewDefaultAgentConfig() AgentConfig {
|
func NewDefaultAgentConfig() AgentConfig {
|
||||||
return AgentConfig{
|
return AgentConfig{
|
||||||
ServerURL: "http://127.0.0.1:3000",
|
ServerURL: "http://127.0.0.1:3000",
|
||||||
|
@ -94,6 +100,10 @@ func NewDefaultAgentConfig() AgentConfig {
|
||||||
MDNS: MDNSControllerConfig{
|
MDNS: MDNSControllerConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
|
Registration: RegistrationControllerConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Address: ":42521",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Collectors: []ShellCollectorConfig{
|
Collectors: []ShellCollectorConfig{
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,10 @@ type Client struct {
|
||||||
serverURL string
|
serverURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ServerURL() string {
|
||||||
|
return c.serverURL
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) apiGet(ctx context.Context, path string, result any, funcs ...OptionFunc) error {
|
func (c *Client) apiGet(ctx context.Context, path string, result any, funcs ...OptionFunc) error {
|
||||||
if err := c.apiDo(ctx, http.MethodGet, path, nil, result, funcs...); err != nil {
|
if err := c.apiDo(ctx, http.MethodGet, path, nil, result, funcs...); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
|
|
Loading…
Reference in New Issue