diff --git a/backend/internal/api/handler/hosts.go b/backend/internal/api/handler/hosts.go index 24ea736..9fcece7 100644 --- a/backend/internal/api/handler/hosts.go +++ b/backend/internal/api/handler/hosts.go @@ -164,6 +164,45 @@ func DeleteHost() func(http.ResponseWriter, *http.Request) { } } +// GetHostNginxConfig will return a Host's nginx config from disk +// Route: GET /hosts/{hostID}/nginx-config +// Route: GET /hosts/{hostID}/nginx-config.txt +func GetHostNginxConfig(format string) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + var err error + var hostID int + if hostID, err = getURLParamInt(r, "hostID"); err != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) + return + } + + item, err := host.GetByID(hostID) + switch err { + case sql.ErrNoRows: + h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil) + case nil: + // Get the config from disk + content, nErr := nginx.GetHostConfigContent(item) + if nErr != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, nErr.Error(), nil) + return + } + if format == "text" { + h.ResultResponseText(w, r, http.StatusOK, content) + return + } + + j := struct { + Content string `json:"content"` + }{Content: content} + + h.ResultResponseJSON(w, r, http.StatusOK, j) + default: + h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) + } + } +} + func configureHost(h host.Model) { err := jobqueue.AddJob(jobqueue.Job{ Name: "NginxConfigureHost", diff --git a/backend/internal/api/handler/upstreams.go b/backend/internal/api/handler/upstreams.go index e0e9da4..1ee6287 100644 --- a/backend/internal/api/handler/upstreams.go +++ b/backend/internal/api/handler/upstreams.go @@ -117,6 +117,45 @@ func DeleteUpstream() func(http.ResponseWriter, *http.Request) { } } +// GetHostNginxConfig will return a Host's nginx config from disk +// Route: GET /upstreams/{upstreamID}/nginx-config +// Route: GET /upstreams/{upstreamID}/nginx-config.txt +func GetUpstreamNginxConfig(format string) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + var err error + var upstreamID int + if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) + return + } + + item, err := upstream.GetByID(upstreamID) + switch err { + case sql.ErrNoRows: + h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil) + case nil: + // Get the config from disk + content, nErr := nginx.GetUpstreamConfigContent(item) + if nErr != nil { + h.ResultErrorJSON(w, r, http.StatusBadRequest, nErr.Error(), nil) + return + } + if format == "text" { + h.ResultResponseText(w, r, http.StatusOK, content) + return + } + + j := struct { + Content string `json:"content"` + }{Content: content} + + h.ResultResponseJSON(w, r, http.StatusOK, j) + default: + h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) + } + } +} + func configureUpstream(u upstream.Model) { err := jobqueue.AddJob(jobqueue.Job{ Name: "NginxConfigureUpstream", diff --git a/backend/internal/api/http/responses.go b/backend/internal/api/http/responses.go index c65736d..881503d 100644 --- a/backend/internal/api/http/responses.go +++ b/backend/internal/api/http/responses.go @@ -81,6 +81,13 @@ func ResultErrorJSON(w http.ResponseWriter, r *http.Request, status int, message ResultResponseJSON(w, r, status, errorResponse) } +// ResultResponseText will write the result as text to the http output +func ResultResponseText(w http.ResponseWriter, r *http.Request, status int, content string) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(status) + fmt.Fprint(w, content) +} + // getPrettyPrintFromContext returns the PrettyPrint setting func getPrettyPrintFromContext(r *http.Request) bool { pretty, ok := r.Context().Value(c.PrettyPrintCtxKey).(bool) diff --git a/backend/internal/api/router.go b/backend/internal/api/router.go index e73c9e5..2ba0930 100644 --- a/backend/internal/api/router.go +++ b/backend/internal/api/router.go @@ -168,6 +168,8 @@ func applyRoutes(r chi.Router) chi.Router { Post("/", handler.CreateHost()) r.With(middleware.Enforce(user.CapabilityHostsManage)).With(middleware.EnforceRequestSchema(schema.UpdateHost())). Put("/{hostID:[0-9]+}", handler.UpdateHost()) + r.With(middleware.Enforce(user.CapabilityHostsManage)).Get("/{hostID:[0-9]+}/nginx-config", handler.GetHostNginxConfig("json")) + r.With(middleware.Enforce(user.CapabilityHostsManage)).Get("/{hostID:[0-9]+}/nginx-config.txt", handler.GetHostNginxConfig("text")) }) // Nginx Templates @@ -202,6 +204,8 @@ func applyRoutes(r chi.Router) chi.Router { r.With(middleware.Enforce(user.CapabilityHostsManage)).Delete("/{upstreamID:[0-9]+}", handler.DeleteUpstream()) r.With(middleware.Enforce(user.CapabilityHostsManage)).With(middleware.EnforceRequestSchema(schema.CreateUpstream())). Post("/", handler.CreateUpstream()) + r.With(middleware.Enforce(user.CapabilityHostsManage)).Get("/{upstreamID:[0-9]+}/nginx-config", handler.GetUpstreamNginxConfig("json")) + r.With(middleware.Enforce(user.CapabilityHostsManage)).Get("/{upstreamID:[0-9]+}/nginx-config.txt", handler.GetUpstreamNginxConfig("text")) }) }) diff --git a/backend/internal/nginx/control.go b/backend/internal/nginx/control.go index dd79d24..b57e9b7 100644 --- a/backend/internal/nginx/control.go +++ b/backend/internal/nginx/control.go @@ -185,3 +185,42 @@ func removeFiles(files []string) { } } } + +// GetHostConfigContent returns nginx config as it exists on disk +func GetHostConfigContent(h host.Model) (string, error) { + filename := getHostFilename(h, "") + if h.ErrorMessage != "" { + filename = getHostFilename(h, ErrorSuffix) + } + if h.IsDisabled { + filename = getHostFilename(h, DisabledSuffix) + } + if h.IsDeleted { + filename = getHostFilename(h, DeletedSuffix) + } + + // nolint: gosec + cnt, err := os.ReadFile(filename) + if err != nil { + return "", err + } + return string(cnt), nil +} + +// GetUpstreamConfigContent returns nginx config as it exists on disk +func GetUpstreamConfigContent(u upstream.Model) (string, error) { + filename := getUpstreamFilename(u, "") + if u.ErrorMessage != "" { + filename = getUpstreamFilename(u, ErrorSuffix) + } + if u.IsDeleted { + filename = getUpstreamFilename(u, DeletedSuffix) + } + + // nolint: gosec + cnt, err := os.ReadFile(filename) + if err != nil { + return "", err + } + return string(cnt), nil +}