- Add configrefresh package for config classification, request handling - Add bootstrap refresh_admin.go for admin-level config refresh - Add edgevalidate package for config validation - Update edge bootstrap runtime with refresh capabilities - Update edge service layer with config refresh integration - Update openai layer routes/chat_handler/server for config refresh - Update edgecmd config handling with refresh support - Update input manager, transport server, status provider - Add edge.yaml config updates and go/config package changes - Add hostsetup templates and test updates - Update roadmap and phase documentation for runtime-reconnect-config-refresh
114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"iop/apps/edge/internal/configrefresh"
|
|
)
|
|
|
|
// RefreshAdminServer is the Edge-local HTTP admin server for config refresh.
|
|
// It listens on a loopback address and accepts POST /refresh requests from
|
|
// the local CLI (iop-edge config refresh). The server is only started when
|
|
// refresh.enabled=true in the Edge config.
|
|
type RefreshAdminServer struct {
|
|
listen string
|
|
runtime *Runtime
|
|
logger *zap.Logger
|
|
server *http.Server
|
|
ln net.Listener
|
|
}
|
|
|
|
func newRefreshAdminServer(listen string, rt *Runtime, logger *zap.Logger) *RefreshAdminServer {
|
|
return &RefreshAdminServer{
|
|
listen: listen,
|
|
runtime: rt,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (s *RefreshAdminServer) Start(_ context.Context) error {
|
|
ln, err := net.Listen("tcp", s.listen)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.ln = ln
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/refresh", s.handleRefresh)
|
|
|
|
s.server = &http.Server{
|
|
Handler: mux,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
}
|
|
go func() {
|
|
if err := s.server.Serve(ln); err != nil && err != http.ErrServerClosed {
|
|
s.logger.Warn("refresh admin server exited", zap.Error(err))
|
|
}
|
|
}()
|
|
s.logger.Info("refresh admin server listening", zap.String("addr", ln.Addr().String()))
|
|
return nil
|
|
}
|
|
|
|
func (s *RefreshAdminServer) Stop(ctx context.Context) error {
|
|
if s.server == nil {
|
|
return nil
|
|
}
|
|
return s.server.Shutdown(ctx)
|
|
}
|
|
|
|
// Addr returns the actual bound address. Useful for port-0 tests.
|
|
func (s *RefreshAdminServer) Addr() string {
|
|
if s.ln == nil {
|
|
return ""
|
|
}
|
|
return s.ln.Addr().String()
|
|
}
|
|
|
|
func (s *RefreshAdminServer) handleRefresh(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var req configrefresh.Request
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, configrefresh.Result{
|
|
Status: configrefresh.StatusRejected,
|
|
Mode: req.Mode,
|
|
Summary: "invalid request body: " + err.Error(),
|
|
Changes: []configrefresh.Change{},
|
|
})
|
|
return
|
|
}
|
|
|
|
result, err := s.runtime.RefreshConfig(r.Context(), req)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, configrefresh.Result{
|
|
Status: configrefresh.StatusRejected,
|
|
Mode: req.Mode,
|
|
RequestID: req.RequestID,
|
|
Summary: "refresh error: " + err.Error(),
|
|
Changes: []configrefresh.Change{},
|
|
})
|
|
return
|
|
}
|
|
|
|
httpStatus := http.StatusOK
|
|
if result.Status == configrefresh.StatusRejected {
|
|
httpStatus = http.StatusUnprocessableEntity
|
|
}
|
|
writeJSON(w, httpStatus, result)
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|